Skip to content

Commit a29da4f

Browse files
committed
feat(forge-inspect): add option to wrap tables to terminal width
1 parent 6983a93 commit a29da4f

File tree

1 file changed

+115
-83
lines changed

1 file changed

+115
-83
lines changed

crates/forge/src/cmd/inspect.rs

Lines changed: 115 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ 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+
#[arg(long, short, help_heading = "Display options")]
45+
pub wrap: bool,
4346
}
4447

4548
impl InspectArgs {
4649
pub fn run(self) -> Result<()> {
47-
let Self { contract, field, build, strip_yul_comments } = self;
50+
let Self { contract, field, build, strip_yul_comments, wrap } = self;
4851

4952
trace!(target: "forge", ?field, ?contract, "running forge inspect");
5053

@@ -86,7 +89,7 @@ impl InspectArgs {
8689
.abi
8790
.as_ref()
8891
.ok_or_else(|| eyre::eyre!("Failed to fetch lossless ABI"))?;
89-
print_abi(abi)?;
92+
print_abi(abi, wrap)?;
9093
}
9194
ContractArtifactField::Bytecode => {
9295
print_json_str(&artifact.bytecode, Some("object"))?;
@@ -101,13 +104,13 @@ impl InspectArgs {
101104
print_json_str(&artifact.legacy_assembly, None)?;
102105
}
103106
ContractArtifactField::MethodIdentifiers => {
104-
print_method_identifiers(&artifact.method_identifiers)?;
107+
print_method_identifiers(&artifact.method_identifiers, wrap)?;
105108
}
106109
ContractArtifactField::GasEstimates => {
107110
print_json(&artifact.gas_estimates)?;
108111
}
109112
ContractArtifactField::StorageLayout => {
110-
print_storage_layout(artifact.storage_layout.as_ref())?;
113+
print_storage_layout(artifact.storage_layout.as_ref(), wrap)?;
111114
}
112115
ContractArtifactField::DevDoc => {
113116
print_json(&artifact.devdoc)?;
@@ -129,11 +132,11 @@ impl InspectArgs {
129132
}
130133
ContractArtifactField::Errors => {
131134
let out = artifact.abi.as_ref().map_or(Map::new(), parse_errors);
132-
print_errors_events(&out, true)?;
135+
print_errors_events(&out, true, wrap)?;
133136
}
134137
ContractArtifactField::Events => {
135138
let out = artifact.abi.as_ref().map_or(Map::new(), parse_events);
136-
print_errors_events(&out, false)?;
139+
print_errors_events(&out, false, wrap)?;
137140
}
138141
ContractArtifactField::StandardJson => {
139142
let standard_json = if let Some(version) = solc_version {
@@ -187,66 +190,70 @@ fn parse_event_params(ev_params: &[EventParam]) -> String {
187190
.join(",")
188191
}
189192

190-
fn print_abi(abi: &JsonAbi) -> Result<()> {
193+
fn print_abi(abi: &JsonAbi, should_wrap: bool) -> Result<()> {
191194
if shell::is_json() {
192195
return print_json(abi)
193196
}
194197

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

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

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

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

240-
if let Some(fallback) = &abi.fallback {
241-
let state_mut = fallback.state_mutability.as_json_str();
242-
table.add_row(["fallback", &format!("fallback() {state_mut}"), ""]);
243-
}
245+
if let Some(fallback) = &abi.fallback {
246+
let state_mut = fallback.state_mutability.as_json_str();
247+
table.add_row(["fallback", &format!("fallback() {state_mut}"), ""]);
248+
}
244249

245-
if let Some(receive) = &abi.receive {
246-
let state_mut = receive.state_mutability.as_json_str();
247-
table.add_row(["receive", &format!("receive() {state_mut}"), ""]);
248-
}
249-
})
250+
if let Some(receive) = &abi.receive {
251+
let state_mut = receive.state_mutability.as_json_str();
252+
table.add_row(["receive", &format!("receive() {state_mut}"), ""]);
253+
}
254+
},
255+
should_wrap,
256+
)
250257
}
251258

252259
fn get_ty_sig(inputs: &[Param]) -> String {
@@ -274,7 +281,10 @@ fn internal_ty(ty: &InternalType) -> String {
274281
}
275282
}
276283

277-
pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<()> {
284+
pub fn print_storage_layout(
285+
storage_layout: Option<&StorageLayout>,
286+
should_wrap: bool,
287+
) -> Result<()> {
278288
let Some(storage_layout) = storage_layout else {
279289
eyre::bail!("Could not get storage layout");
280290
};
@@ -292,22 +302,29 @@ pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<()
292302
Cell::new("Contract"),
293303
];
294304

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

310-
fn print_method_identifiers(method_identifiers: &Option<BTreeMap<String, String>>) -> Result<()> {
324+
fn print_method_identifiers(
325+
method_identifiers: &Option<BTreeMap<String, String>>,
326+
should_wrap: bool,
327+
) -> Result<()> {
311328
let Some(method_identifiers) = method_identifiers else {
312329
eyre::bail!("Could not get method identifiers");
313330
};
@@ -318,14 +335,18 @@ fn print_method_identifiers(method_identifiers: &Option<BTreeMap<String, String>
318335

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

321-
print_table(headers, |table| {
322-
for (method, identifier) in method_identifiers {
323-
table.add_row([method, identifier]);
324-
}
325-
})
338+
print_table(
339+
headers,
340+
|table| {
341+
for (method, identifier) in method_identifiers {
342+
table.add_row([method, identifier]);
343+
}
344+
},
345+
should_wrap,
346+
)
326347
}
327348

328-
fn print_errors_events(map: &Map<String, Value>, is_err: bool) -> Result<()> {
349+
fn print_errors_events(map: &Map<String, Value>, is_err: bool, should_wrap: bool) -> Result<()> {
329350
if shell::is_json() {
330351
return print_json(map);
331352
}
@@ -335,17 +356,28 @@ fn print_errors_events(map: &Map<String, Value>, is_err: bool) -> Result<()> {
335356
} else {
336357
vec![Cell::new("Event"), Cell::new("Topic")]
337358
};
338-
print_table(headers, |table| {
339-
for (method, selector) in map {
340-
table.add_row([method, selector.as_str().unwrap()]);
341-
}
342-
})
359+
print_table(
360+
headers,
361+
|table| {
362+
for (method, selector) in map {
363+
table.add_row([method, selector.as_str().unwrap()]);
364+
}
365+
},
366+
should_wrap,
367+
)
343368
}
344369

345-
fn print_table(headers: Vec<Cell>, add_rows: impl FnOnce(&mut Table)) -> Result<()> {
370+
fn print_table(
371+
headers: Vec<Cell>,
372+
add_rows: impl FnOnce(&mut Table),
373+
should_wrap: bool,
374+
) -> Result<()> {
346375
let mut table = Table::new();
347376
table.apply_modifier(UTF8_ROUND_CORNERS);
348377
table.set_header(headers);
378+
if should_wrap {
379+
table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
380+
}
349381
add_rows(&mut table);
350382
sh_println!("\n{table}\n")?;
351383
Ok(())

0 commit comments

Comments
 (0)