Skip to content

Commit 4e59f42

Browse files
feat(forge-inspect): add option to wrap tables to terminal width (#11138)
* feat(forge-inspect): add option to wrap tables to terminal width * chore: add doc comment * chore: add tests * chore: use portable-pty for running tests that need specific terminal size * chore: fix test * chore: remove portable-pty * chore: fix clippy * chore: revert clippy fix * chore: fix build * chore: nit --------- Co-authored-by: zerosnacks <[email protected]>
1 parent 8567c2a commit 4e59f42

File tree

2 files changed

+152
-83
lines changed

2 files changed

+152
-83
lines changed

crates/forge/src/cmd/inspect.rs

Lines changed: 116 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,15 @@ pub struct InspectArgs {
4040
/// Whether to remove comments when inspecting `ir` and `irOptimized` artifact fields.
4141
#[arg(long, short, help_heading = "Display options")]
4242
pub strip_yul_comments: bool,
43+
44+
/// Whether to wrap the table to the terminal width.
45+
#[arg(long, short, help_heading = "Display options")]
46+
pub wrap: bool,
4347
}
4448

4549
impl InspectArgs {
4650
pub fn run(self) -> Result<()> {
47-
let Self { contract, field, build, strip_yul_comments } = self;
51+
let Self { contract, field, build, strip_yul_comments, wrap } = self;
4852

4953
trace!(target: "forge", ?field, ?contract, "running forge inspect");
5054

@@ -83,7 +87,7 @@ impl InspectArgs {
8387
match field {
8488
ContractArtifactField::Abi => {
8589
let abi = artifact.abi.as_ref().ok_or_else(|| missing_error("ABI"))?;
86-
print_abi(abi)?;
90+
print_abi(abi, wrap)?;
8791
}
8892
ContractArtifactField::Bytecode => {
8993
print_json_str(&artifact.bytecode, Some("object"))?;
@@ -98,13 +102,13 @@ impl InspectArgs {
98102
print_json_str(&artifact.legacy_assembly, None)?;
99103
}
100104
ContractArtifactField::MethodIdentifiers => {
101-
print_method_identifiers(&artifact.method_identifiers)?;
105+
print_method_identifiers(&artifact.method_identifiers, wrap)?;
102106
}
103107
ContractArtifactField::GasEstimates => {
104108
print_json(&artifact.gas_estimates)?;
105109
}
106110
ContractArtifactField::StorageLayout => {
107-
print_storage_layout(artifact.storage_layout.as_ref())?;
111+
print_storage_layout(artifact.storage_layout.as_ref(), wrap)?;
108112
}
109113
ContractArtifactField::DevDoc => {
110114
print_json(&artifact.devdoc)?;
@@ -126,11 +130,11 @@ impl InspectArgs {
126130
}
127131
ContractArtifactField::Errors => {
128132
let out = artifact.abi.as_ref().map_or(Map::new(), parse_errors);
129-
print_errors_events(&out, true)?;
133+
print_errors_events(&out, true, wrap)?;
130134
}
131135
ContractArtifactField::Events => {
132136
let out = artifact.abi.as_ref().map_or(Map::new(), parse_events);
133-
print_errors_events(&out, false)?;
137+
print_errors_events(&out, false, wrap)?;
134138
}
135139
ContractArtifactField::StandardJson => {
136140
let standard_json = if let Some(version) = solc_version {
@@ -184,66 +188,70 @@ fn parse_event_params(ev_params: &[EventParam]) -> String {
184188
.join(",")
185189
}
186190

187-
fn print_abi(abi: &JsonAbi) -> Result<()> {
191+
fn print_abi(abi: &JsonAbi, should_wrap: bool) -> Result<()> {
188192
if shell::is_json() {
189193
return print_json(abi);
190194
}
191195

192196
let headers = vec![Cell::new("Type"), Cell::new("Signature"), Cell::new("Selector")];
193-
print_table(headers, |table| {
194-
// Print events
195-
for ev in abi.events.iter().flat_map(|(_, events)| events) {
196-
let types = parse_event_params(&ev.inputs);
197-
let selector = ev.selector().to_string();
198-
table.add_row(["event", &format!("{}({})", ev.name, types), &selector]);
199-
}
197+
print_table(
198+
headers,
199+
|table| {
200+
// Print events
201+
for ev in abi.events.iter().flat_map(|(_, events)| events) {
202+
let types = parse_event_params(&ev.inputs);
203+
let selector = ev.selector().to_string();
204+
table.add_row(["event", &format!("{}({})", ev.name, types), &selector]);
205+
}
200206

201-
// Print errors
202-
for er in abi.errors.iter().flat_map(|(_, errors)| errors) {
203-
let selector = er.selector().to_string();
204-
table.add_row([
205-
"error",
206-
&format!("{}({})", er.name, get_ty_sig(&er.inputs)),
207-
&selector,
208-
]);
209-
}
207+
// Print errors
208+
for er in abi.errors.iter().flat_map(|(_, errors)| errors) {
209+
let selector = er.selector().to_string();
210+
table.add_row([
211+
"error",
212+
&format!("{}({})", er.name, get_ty_sig(&er.inputs)),
213+
&selector,
214+
]);
215+
}
210216

211-
// Print functions
212-
for func in abi.functions.iter().flat_map(|(_, f)| f) {
213-
let selector = func.selector().to_string();
214-
let state_mut = func.state_mutability.as_json_str();
215-
let func_sig = if !func.outputs.is_empty() {
216-
format!(
217-
"{}({}) {state_mut} returns ({})",
218-
func.name,
219-
get_ty_sig(&func.inputs),
220-
get_ty_sig(&func.outputs)
221-
)
222-
} else {
223-
format!("{}({}) {state_mut}", func.name, get_ty_sig(&func.inputs))
224-
};
225-
table.add_row(["function", &func_sig, &selector]);
226-
}
217+
// Print functions
218+
for func in abi.functions.iter().flat_map(|(_, f)| f) {
219+
let selector = func.selector().to_string();
220+
let state_mut = func.state_mutability.as_json_str();
221+
let func_sig = if !func.outputs.is_empty() {
222+
format!(
223+
"{}({}) {state_mut} returns ({})",
224+
func.name,
225+
get_ty_sig(&func.inputs),
226+
get_ty_sig(&func.outputs)
227+
)
228+
} else {
229+
format!("{}({}) {state_mut}", func.name, get_ty_sig(&func.inputs))
230+
};
231+
table.add_row(["function", &func_sig, &selector]);
232+
}
227233

228-
if let Some(constructor) = abi.constructor() {
229-
let state_mut = constructor.state_mutability.as_json_str();
230-
table.add_row([
231-
"constructor",
232-
&format!("constructor({}) {state_mut}", get_ty_sig(&constructor.inputs)),
233-
"",
234-
]);
235-
}
234+
if let Some(constructor) = abi.constructor() {
235+
let state_mut = constructor.state_mutability.as_json_str();
236+
table.add_row([
237+
"constructor",
238+
&format!("constructor({}) {state_mut}", get_ty_sig(&constructor.inputs)),
239+
"",
240+
]);
241+
}
236242

237-
if let Some(fallback) = &abi.fallback {
238-
let state_mut = fallback.state_mutability.as_json_str();
239-
table.add_row(["fallback", &format!("fallback() {state_mut}"), ""]);
240-
}
243+
if let Some(fallback) = &abi.fallback {
244+
let state_mut = fallback.state_mutability.as_json_str();
245+
table.add_row(["fallback", &format!("fallback() {state_mut}"), ""]);
246+
}
241247

242-
if let Some(receive) = &abi.receive {
243-
let state_mut = receive.state_mutability.as_json_str();
244-
table.add_row(["receive", &format!("receive() {state_mut}"), ""]);
245-
}
246-
})
248+
if let Some(receive) = &abi.receive {
249+
let state_mut = receive.state_mutability.as_json_str();
250+
table.add_row(["receive", &format!("receive() {state_mut}"), ""]);
251+
}
252+
},
253+
should_wrap,
254+
)
247255
}
248256

249257
fn get_ty_sig(inputs: &[Param]) -> String {
@@ -271,7 +279,10 @@ fn internal_ty(ty: &InternalType) -> String {
271279
}
272280
}
273281

274-
pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<()> {
282+
pub fn print_storage_layout(
283+
storage_layout: Option<&StorageLayout>,
284+
should_wrap: bool,
285+
) -> Result<()> {
275286
let Some(storage_layout) = storage_layout else {
276287
return Err(missing_error("storage layout"));
277288
};
@@ -289,22 +300,29 @@ pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<()
289300
Cell::new("Contract"),
290301
];
291302

292-
print_table(headers, |table| {
293-
for slot in &storage_layout.storage {
294-
let storage_type = storage_layout.types.get(&slot.storage_type);
295-
table.add_row([
296-
slot.label.as_str(),
297-
storage_type.map_or("?", |t| &t.label),
298-
&slot.slot,
299-
&slot.offset.to_string(),
300-
storage_type.map_or("?", |t| &t.number_of_bytes),
301-
&slot.contract,
302-
]);
303-
}
304-
})
303+
print_table(
304+
headers,
305+
|table| {
306+
for slot in &storage_layout.storage {
307+
let storage_type = storage_layout.types.get(&slot.storage_type);
308+
table.add_row([
309+
slot.label.as_str(),
310+
storage_type.map_or("?", |t| &t.label),
311+
&slot.slot,
312+
&slot.offset.to_string(),
313+
storage_type.map_or("?", |t| &t.number_of_bytes),
314+
&slot.contract,
315+
]);
316+
}
317+
},
318+
should_wrap,
319+
)
305320
}
306321

307-
fn print_method_identifiers(method_identifiers: &Option<BTreeMap<String, String>>) -> Result<()> {
322+
fn print_method_identifiers(
323+
method_identifiers: &Option<BTreeMap<String, String>>,
324+
should_wrap: bool,
325+
) -> Result<()> {
308326
let Some(method_identifiers) = method_identifiers else {
309327
return Err(missing_error("method identifiers"));
310328
};
@@ -315,14 +333,18 @@ fn print_method_identifiers(method_identifiers: &Option<BTreeMap<String, String>
315333

316334
let headers = vec![Cell::new("Method"), Cell::new("Identifier")];
317335

318-
print_table(headers, |table| {
319-
for (method, identifier) in method_identifiers {
320-
table.add_row([method, identifier]);
321-
}
322-
})
336+
print_table(
337+
headers,
338+
|table| {
339+
for (method, identifier) in method_identifiers {
340+
table.add_row([method, identifier]);
341+
}
342+
},
343+
should_wrap,
344+
)
323345
}
324346

325-
fn print_errors_events(map: &Map<String, Value>, is_err: bool) -> Result<()> {
347+
fn print_errors_events(map: &Map<String, Value>, is_err: bool, should_wrap: bool) -> Result<()> {
326348
if shell::is_json() {
327349
return print_json(map);
328350
}
@@ -332,17 +354,28 @@ fn print_errors_events(map: &Map<String, Value>, is_err: bool) -> Result<()> {
332354
} else {
333355
vec![Cell::new("Event"), Cell::new("Topic")]
334356
};
335-
print_table(headers, |table| {
336-
for (method, selector) in map {
337-
table.add_row([method, selector.as_str().unwrap()]);
338-
}
339-
})
357+
print_table(
358+
headers,
359+
|table| {
360+
for (method, selector) in map {
361+
table.add_row([method, selector.as_str().unwrap()]);
362+
}
363+
},
364+
should_wrap,
365+
)
340366
}
341367

342-
fn print_table(headers: Vec<Cell>, add_rows: impl FnOnce(&mut Table)) -> Result<()> {
368+
fn print_table(
369+
headers: Vec<Cell>,
370+
add_rows: impl FnOnce(&mut Table),
371+
should_wrap: bool,
372+
) -> Result<()> {
343373
let mut table = Table::new();
344374
table.apply_modifier(UTF8_ROUND_CORNERS);
345375
table.set_header(headers);
376+
if should_wrap {
377+
table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
378+
}
346379
add_rows(&mut table);
347380
sh_println!("\n{table}\n")?;
348381
Ok(())

crates/forge/tests/cli/cmd.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3690,6 +3690,42 @@ forgetest!(inspect_custom_counter_method_identifiers, |prj, cmd| {
36903690
╰----------------------------+------------╯
36913691
36923692
3693+
"#]]);
3694+
});
3695+
3696+
const CUSTOM_COUNTER_HUGE_METHOD_IDENTIFIERS: &str = r#"
3697+
contract Counter {
3698+
struct BigStruct {
3699+
uint256 a;
3700+
uint256 b;
3701+
uint256 c;
3702+
uint256 d;
3703+
uint256 e;
3704+
uint256 f;
3705+
}
3706+
3707+
struct NestedBigStruct {
3708+
BigStruct a;
3709+
BigStruct b;
3710+
BigStruct c;
3711+
}
3712+
3713+
function hugeIdentifier(NestedBigStruct[] calldata _bigStructs, NestedBigStruct calldata _bigStruct) external {}
3714+
}
3715+
"#;
3716+
3717+
forgetest!(inspect_custom_counter_very_huge_method_identifiers_unwrapped, |prj, cmd| {
3718+
prj.add_source("Counter.sol", CUSTOM_COUNTER_HUGE_METHOD_IDENTIFIERS).unwrap();
3719+
3720+
cmd.args(["inspect", "Counter", "method-identifiers"]).assert_success().stdout_eq(str![[r#"
3721+
3722+
╭-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------╮
3723+
| Method | Identifier |
3724+
+================================================================================================================================================================================================================================================================================================================================================+
3725+
| hugeIdentifier(((uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256))[],((uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256))) | f38dafbb |
3726+
╰-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------╯
3727+
3728+
36933729
"#]]);
36943730
});
36953731

0 commit comments

Comments
 (0)