Skip to content

Commit 7594d78

Browse files
fbenksteinjpgrayson
authored andcommitted
feat: organize subcommands by group in help
The list of subcommands is getting rather long. Combined with aliases they create a wall of text in the help message that is hard to scan. We can break up the long list into multiple smallers by grouping commands by category. This creates a more meaningful help message.
1 parent 2d6a2d7 commit 7594d78

File tree

3 files changed

+67
-2
lines changed

3 files changed

+67
-2
lines changed

src/cmd/completion/list.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ fn list_commands(output: &mut Box<dyn std::io::Write>, style: OutputStyle) -> Re
181181
~~~~~~~~~~~~~~\n"
182182
)?;
183183
}
184+
CommandCategory::Alias | CommandCategory::Help => {
185+
unreachable!();
186+
}
184187
}
185188
last_category = Some(category);
186189
}

src/cmd/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ pub(crate) enum CommandCategory {
5959
StackInspection,
6060
StackManipulation,
6161
Administration,
62+
Alias,
63+
Help,
6264
}
6365

6466
/// Entry point for a StGit subcommand.

src/main.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ mod stupid;
2121
mod templates;
2222
mod wrap;
2323

24-
use std::{ffi::OsString, io::Write, path::PathBuf};
24+
use std::{ffi::OsString, fmt::Write as _, io::Write as _, path::PathBuf};
2525

2626
use anyhow::{anyhow, Context, Result};
2727
use bstr::ByteSlice;
@@ -312,10 +312,70 @@ fn full_app_help(
312312
}
313313
};
314314

315+
// By default clap renders subcommands as a single list which is hard to read given the number
316+
// of existing subcommands. We group the commands by their categories and create several
317+
// shorter lists which are easier to read.
318+
let command = {
319+
let command = get_full_command(&aliases, color_choice);
320+
let heading_style = command.get_styles().get_header().render();
321+
let heading_style_reset = command.get_styles().get_header().render_reset();
322+
let mut subcommands_by_category = String::new();
323+
324+
use cmd::CommandCategory::*;
325+
const GROUPS: [(cmd::CommandCategory, &str); 7] = [
326+
(PatchInspection, "Patch inspection commands:"),
327+
(PatchManipulation, "Patch manipulation commands:"),
328+
(StackInspection, "Stack inspection commands:"),
329+
(StackManipulation, "Stack manipulation commands:"),
330+
(Administration, "Administration commands:"),
331+
(Alias, "Aliases:"),
332+
(Help, "Help:"),
333+
];
334+
335+
// Render each subcommand list individually with a custom template and by hiding other
336+
// subcommands.
337+
for (group_category, group_heading) in GROUPS {
338+
let mut command = command.clone().help_template(format!(
339+
"{heading_style}{group_heading}{heading_style_reset}\n{{subcommands}}"
340+
));
341+
342+
for stgit_command in cmd::STGIT_COMMANDS {
343+
command = command.mut_subcommand(stgit_command.name, |subcommand| {
344+
subcommand.hide(stgit_command.category != group_category)
345+
});
346+
}
347+
348+
for alias_name in aliases.keys() {
349+
command = command.mut_subcommand(alias_name, |subcommand| {
350+
subcommand.hide(group_category != Alias)
351+
});
352+
}
353+
354+
command = command.disable_help_subcommand(group_category != Help);
355+
356+
write!(
357+
subcommands_by_category,
358+
"\n{}",
359+
command.render_help().ansi()
360+
)
361+
.expect("failed to render help");
362+
}
363+
364+
// Render the full help by injecting the subcommand groups into the template.
365+
command.help_template(format!(
366+
"\
367+
{{before-help}}{{about-with-newline}}
368+
{{usage-heading}} {{usage}}
369+
{subcommands_by_category}
370+
{heading_style}Options:{heading_style_reset}
371+
{{options}}{{after-help}}"
372+
))
373+
};
374+
315375
// full_app_help should only be called once it has been determined that the command
316376
// line does not have a viable subcommand or alias. Thus this get_matches_from()
317377
// call should print an appropriate help message and terminate the process.
318-
let err = get_full_command(&aliases, color_choice)
378+
let err = command
319379
.try_get_matches_from(argv)
320380
.expect_err("command line should not have viable matches");
321381
err.print().expect("failed to print clap error");

0 commit comments

Comments
 (0)