From a1fd922bdacad5d0e01cae1965fc1c91d72f23db Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 9 Nov 2023 13:31:11 -0600 Subject: [PATCH 1/7] feat(help): Allow controlling flattening --- clap_builder/src/builder/app_settings.rs | 1 + clap_builder/src/builder/command.rs | 21 ++ examples/git-derive.rs | 1 + examples/git.rs | 1 + tests/builder/help.rs | 379 +++++++++++++++++++++++ 5 files changed, 403 insertions(+) diff --git a/clap_builder/src/builder/app_settings.rs b/clap_builder/src/builder/app_settings.rs index 4fce4b4a2ee..f9a87dadcd4 100644 --- a/clap_builder/src/builder/app_settings.rs +++ b/clap_builder/src/builder/app_settings.rs @@ -57,6 +57,7 @@ pub(crate) enum AppSettings { SubcommandsNegateReqs, ArgsNegateSubcommands, SubcommandPrecedenceOverArg, + FlattenHelp, ArgRequiredElseHelp, NextLineHelp, DisableColoredHelp, diff --git a/clap_builder/src/builder/command.rs b/clap_builder/src/builder/command.rs index 264649c7b1c..e82652e805a 100644 --- a/clap_builder/src/builder/command.rs +++ b/clap_builder/src/builder/command.rs @@ -2049,6 +2049,21 @@ impl Command { self } + /// Flatten subcommand help into the current command's help + /// + /// This shows a summary of subcommands within the usage and help for the current command, similar to + /// `git stash --help` showing information on `push`, `pop`, etc. + /// To see more information, a user can still pass `--help` to the individual subcommands. + #[inline] + #[must_use] + pub fn flatten_help(self, yes: bool) -> Self { + if yes { + self.setting(AppSettings::FlattenHelp) + } else { + self.unset_setting(AppSettings::FlattenHelp) + } + } + /// Set the default section heading for future args. /// /// This will be used for any arg that hasn't had [`Arg::help_heading`] called. @@ -3430,6 +3445,12 @@ impl Command { self.long_about.as_ref() } + /// Get the custom section heading specified via [`Command::flatten_help`]. + #[inline] + pub fn is_flatten_help_set(&self) -> bool { + self.is_set(AppSettings::FlattenHelp) + } + /// Get the custom section heading specified via [`Command::next_help_heading`]. /// /// [`Command::help_heading`]: Command::help_heading() diff --git a/examples/git-derive.rs b/examples/git-derive.rs index ad82e0cea78..9d171373d9f 100644 --- a/examples/git-derive.rs +++ b/examples/git-derive.rs @@ -76,6 +76,7 @@ impl std::fmt::Display for ColorWhen { #[derive(Debug, Args)] #[command(args_conflicts_with_subcommands = true)] +#[command(flatten_help = true)] struct StashArgs { #[command(subcommand)] command: Option, diff --git a/examples/git.rs b/examples/git.rs index 02173675f9c..fc8fd01f79e 100644 --- a/examples/git.rs +++ b/examples/git.rs @@ -45,6 +45,7 @@ fn cli() -> Command { .subcommand( Command::new("stash") .args_conflicts_with_subcommands(true) + .flatten_help(true) .args(push_args()) .subcommand(Command::new("push").args(push_args())) .subcommand(Command::new("pop").arg(arg!([STASH]))) diff --git a/tests/builder/help.rs b/tests/builder/help.rs index 0d6509bdc9b..eefc188ffeb 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -2948,3 +2948,382 @@ fn display_name_subcommand_explicit() { Some("child.display") ); } + +#[test] +fn flatten_basic() { + static EXPECTED: &str = "\ +Usage: parent [OPTIONS] [COMMAND] + +Commands: + test some + help Print this message or the help of the given subcommand(s) + +Options: + --parent + -h, --help Print help +"; + let cmd = Command::new("parent") + .flatten_help(true) + .arg(Arg::new("parent").long("parent")) + .subcommand( + Command::new("test") + .about("some") + .arg(Arg::new("child").long("child")), + ); + utils::assert_output(cmd, "parent -h", EXPECTED, false); +} + +#[test] +fn flatten_short_help() { + static EXPECTED: &str = "\ +Usage: parent [OPTIONS] [COMMAND] + +Commands: + test some + help Print this message or the help of the given subcommand(s) + +Options: + --parent foo + -h, --help Print help (see more with '--help') +"; + let cmd = Command::new("parent") + .flatten_help(true) + .arg( + Arg::new("parent") + .long("parent") + .help("foo") + .long_help("bar"), + ) + .subcommand( + Command::new("test") + .about("some") + .long_about("long some") + .arg(Arg::new("child").long("child").help("foo").long_help("bar")), + ); + utils::assert_output(cmd, "parent -h", EXPECTED, false); +} + +#[test] +fn flatten_long_help() { + static EXPECTED: &str = "\ +Usage: parent [OPTIONS] [COMMAND] + +Commands: + test some + help Print this message or the help of the given subcommand(s) + +Options: + --parent + bar + + -h, --help + Print help (see a summary with '-h') +"; + let cmd = Command::new("parent") + .flatten_help(true) + .arg( + Arg::new("parent") + .long("parent") + .help("foo") + .long_help("bar"), + ) + .subcommand( + Command::new("test") + .about("some") + .long_about("long some") + .arg(Arg::new("child").long("child").help("foo").long_help("bar")), + ); + utils::assert_output(cmd, "parent --help", EXPECTED, false); +} + +#[test] +fn flatten_help_cmd() { + static EXPECTED: &str = "\ +Usage: parent [OPTIONS] [COMMAND] + +Commands: + test some + help Print this message or the help of the given subcommand(s) + +Options: + --parent + bar + + -h, --help + Print help (see a summary with '-h') +"; + let cmd = Command::new("parent") + .flatten_help(true) + .arg( + Arg::new("parent") + .long("parent") + .help("foo") + .long_help("bar"), + ) + .subcommand( + Command::new("test") + .about("some") + .long_about("long some") + .arg(Arg::new("child").long("child").help("foo").long_help("bar")), + ); + utils::assert_output(cmd, "parent help", EXPECTED, false); +} + +#[test] +fn flatten_with_global() { + static EXPECTED: &str = "\ +Usage: parent [OPTIONS] [COMMAND] + +Commands: + test some + help Print this message or the help of the given subcommand(s) + +Options: + --parent + -h, --help Print help +"; + let cmd = Command::new("parent") + .flatten_help(true) + .arg(Arg::new("parent").long("parent").global(true)) + .subcommand( + Command::new("test") + .about("some") + .arg(Arg::new("child").long("child")), + ); + utils::assert_output(cmd, "parent -h", EXPECTED, false); +} + +#[test] +fn flatten_arg_required() { + static EXPECTED: &str = "\ +Usage: parent --parent [COMMAND] + +Commands: + test some + help Print this message or the help of the given subcommand(s) + +Options: + --parent + -h, --help Print help +"; + let cmd = Command::new("parent") + .flatten_help(true) + .arg(Arg::new("parent").long("parent").required(true)) + .subcommand( + Command::new("test") + .about("some") + .arg(Arg::new("child").long("child").required(true)), + ); + utils::assert_output(cmd, "parent -h", EXPECTED, false); +} + +#[test] +fn flatten_with_external_subcommand() { + static EXPECTED: &str = "\ +Usage: parent [OPTIONS] [COMMAND] + +Commands: + test some + help Print this message or the help of the given subcommand(s) + +Options: + --parent + -h, --help Print help +"; + let cmd = Command::new("parent") + .flatten_help(true) + .allow_external_subcommands(true) + .arg(Arg::new("parent").long("parent")) + .subcommand( + Command::new("test") + .about("some") + .arg(Arg::new("child").long("child")), + ); + utils::assert_output(cmd, "parent -h", EXPECTED, false); +} + +#[test] +fn flatten_without_subcommands() { + static EXPECTED: &str = "\ +Usage: parent [OPTIONS] + +Options: + --parent + -h, --help Print help +"; + let cmd = Command::new("parent") + .flatten_help(true) + .arg(Arg::new("parent").long("parent")); + utils::assert_output(cmd, "parent -h", EXPECTED, false); +} + +#[test] +fn flatten_with_subcommand_required() { + static EXPECTED: &str = "\ +Usage: parent [OPTIONS] + +Commands: + test some + help Print this message or the help of the given subcommand(s) + +Options: + --parent + -h, --help Print help +"; + let cmd = Command::new("parent") + .flatten_help(true) + .subcommand_required(true) + .arg(Arg::new("parent").long("parent")) + .subcommand( + Command::new("test") + .about("some") + .arg(Arg::new("child").long("child")), + ); + utils::assert_output(cmd, "parent -h", EXPECTED, false); +} + +#[test] +fn flatten_with_args_conflicts_with_subcommands() { + static EXPECTED: &str = "\ +Usage: parent [OPTIONS] + parent + +Commands: + test some + help Print this message or the help of the given subcommand(s) + +Options: + --parent + -h, --help Print help +"; + let cmd = Command::new("parent") + .flatten_help(true) + .subcommand_required(true) + .args_conflicts_with_subcommands(true) + .arg(Arg::new("parent").long("parent")) + .subcommand( + Command::new("test") + .about("some") + .arg(Arg::new("child").long("child")), + ); + utils::assert_output(cmd, "parent -h", EXPECTED, false); +} + +#[test] +fn flatten_recursive() { + static EXPECTED: &str = "\ +Usage: parent [OPTIONS] [COMMAND] + +Commands: + child1 some 1 + child2 some 2 + child3 some 3 + help Print this message or the help of the given subcommand(s) + +Options: + --parent + -h, --help Print help +"; + let cmd = Command::new("parent") + .flatten_help(true) + .arg(Arg::new("parent").long("parent")) + .subcommand( + Command::new("child1") + .flatten_help(true) + .about("some 1") + .arg(Arg::new("child").long("child1")) + .subcommand( + Command::new("grandchild1") + .flatten_help(true) + .about("some 1") + .arg(Arg::new("grandchild").long("grandchild1")) + .subcommand( + Command::new("greatgrandchild1") + .about("some 1") + .arg(Arg::new("greatgrandchild").long("greatgrandchild1")), + ) + .subcommand( + Command::new("greatgrandchild2") + .about("some 2") + .arg(Arg::new("greatgrandchild").long("greatgrandchild2")), + ) + .subcommand( + Command::new("greatgrandchild3") + .about("some 3") + .arg(Arg::new("greatgrandchild").long("greatgrandchild3")), + ), + ) + .subcommand( + Command::new("grandchild2") + .about("some 2") + .arg(Arg::new("grandchild").long("grandchild2")), + ) + .subcommand( + Command::new("grandchild3") + .about("some 3") + .arg(Arg::new("grandchild").long("grandchild3")), + ), + ) + .subcommand( + Command::new("child2") + .about("some 2") + .arg(Arg::new("child").long("child2")), + ) + .subcommand( + Command::new("child3") + .about("some 3") + .arg(Arg::new("child").long("child3")), + ); + utils::assert_output(cmd, "parent -h", EXPECTED, false); +} + +#[test] +fn flatten_not_recursive() { + static EXPECTED: &str = "\ +Usage: parent [OPTIONS] [COMMAND] + +Commands: + child1 some 1 + child2 some 2 + child3 some 3 + help Print this message or the help of the given subcommand(s) + +Options: + --parent + -h, --help Print help +"; + let cmd = Command::new("parent") + .flatten_help(true) + .arg(Arg::new("parent").long("parent")) + .subcommand( + Command::new("child1") + .about("some 1") + .arg(Arg::new("child").long("child1")) + .subcommand( + Command::new("grandchild1") + .about("some 1") + .arg(Arg::new("grandchild").long("grandchild1")), + ) + .subcommand( + Command::new("grandchild2") + .about("some 2") + .arg(Arg::new("grandchild").long("grandchild2")), + ) + .subcommand( + Command::new("grandchild3") + .about("some 3") + .arg(Arg::new("grandchild").long("grandchild3")), + ), + ) + .subcommand( + Command::new("child2") + .about("some 2") + .arg(Arg::new("child").long("child2")), + ) + .subcommand( + Command::new("child3") + .about("some 3") + .arg(Arg::new("child").long("child3")), + ); + utils::assert_output(cmd, "parent -h", EXPECTED, false); +} From caf5cdcfa04d92bdea0dcd924296990425212b91 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 9 Nov 2023 12:46:16 -0600 Subject: [PATCH 2/7] feat(help): Allow flattening usage --- clap_builder/src/output/usage.rs | 24 ++++++++++++-- examples/git-derive.md | 5 ++- examples/git.md | 5 ++- tests/builder/help.rs | 54 +++++++++++++++++++++++++------- 4 files changed, 73 insertions(+), 15 deletions(-) diff --git a/clap_builder/src/output/usage.rs b/clap_builder/src/output/usage.rs index d4e932ce7f6..0b2ff3b93c3 100644 --- a/clap_builder/src/output/usage.rs +++ b/clap_builder/src/output/usage.rs @@ -101,9 +101,29 @@ impl<'cmd> Usage<'cmd> { // Creates a usage string for display in help messages (i.e. not for errors) fn write_help_usage(&self, styled: &mut StyledStr) { debug!("Usage::write_help_usage"); + use std::fmt::Write; - self.write_arg_usage(styled, &[], true); - self.write_subcommand_usage(styled); + if self.cmd.is_flatten_help_set() { + if !self.cmd.is_subcommand_required_set() + || self.cmd.is_args_conflicts_with_subcommands_set() + { + self.write_arg_usage(styled, &[], true); + styled.trim_end(); + let _ = write!(styled, "{}", USAGE_SEP); + } + let mut cmd = self.cmd.clone(); + cmd.build(); + for (i, sub) in cmd.get_subcommands().enumerate() { + if i != 0 { + styled.trim_end(); + let _ = write!(styled, "{}", USAGE_SEP); + } + Usage::new(sub).write_usage_no_title(styled, &[]); + } + } else { + self.write_arg_usage(styled, &[], true); + self.write_subcommand_usage(styled); + } } // Creates a context aware usage string, or "smart usage" from currently used diff --git a/examples/git-derive.md b/examples/git-derive.md index 57edb612e9e..afc4d0c8feb 100644 --- a/examples/git-derive.md +++ b/examples/git-derive.md @@ -73,7 +73,10 @@ Default subcommand: ```console $ git-derive stash -h Usage: git-derive[EXE] stash [OPTIONS] - git-derive[EXE] stash + git-derive[EXE] stash push [OPTIONS] + git-derive[EXE] stash pop [STASH] + git-derive[EXE] stash apply [STASH] + git-derive[EXE] stash help [COMMAND]... Commands: push diff --git a/examples/git.md b/examples/git.md index d90b2e8e053..66e8baae83b 100644 --- a/examples/git.md +++ b/examples/git.md @@ -71,7 +71,10 @@ Default subcommand: ```console $ git stash -h Usage: git[EXE] stash [OPTIONS] - git[EXE] stash + git[EXE] stash push [OPTIONS] + git[EXE] stash pop [STASH] + git[EXE] stash apply [STASH] + git[EXE] stash help [COMMAND]... Commands: push diff --git a/tests/builder/help.rs b/tests/builder/help.rs index eefc188ffeb..22cbaab583c 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -2952,7 +2952,9 @@ fn display_name_subcommand_explicit() { #[test] fn flatten_basic() { static EXPECTED: &str = "\ -Usage: parent [OPTIONS] [COMMAND] +Usage: parent [OPTIONS] + parent test [OPTIONS] + parent help [COMMAND]... Commands: test some @@ -2976,7 +2978,9 @@ Options: #[test] fn flatten_short_help() { static EXPECTED: &str = "\ -Usage: parent [OPTIONS] [COMMAND] +Usage: parent [OPTIONS] + parent test [OPTIONS] + parent help [COMMAND]... Commands: test some @@ -3006,7 +3010,9 @@ Options: #[test] fn flatten_long_help() { static EXPECTED: &str = "\ -Usage: parent [OPTIONS] [COMMAND] +Usage: parent [OPTIONS] + parent test [OPTIONS] + parent help [COMMAND]... Commands: test some @@ -3039,7 +3045,9 @@ Options: #[test] fn flatten_help_cmd() { static EXPECTED: &str = "\ -Usage: parent [OPTIONS] [COMMAND] +Usage: parent [OPTIONS] + parent test [OPTIONS] + parent help [COMMAND]... Commands: test some @@ -3072,7 +3080,9 @@ Options: #[test] fn flatten_with_global() { static EXPECTED: &str = "\ -Usage: parent [OPTIONS] [COMMAND] +Usage: parent [OPTIONS] + parent test [OPTIONS] + parent help [COMMAND]... Commands: test some @@ -3096,7 +3106,9 @@ Options: #[test] fn flatten_arg_required() { static EXPECTED: &str = "\ -Usage: parent --parent [COMMAND] +Usage: parent --parent + parent --parent test --child + parent --parent help [COMMAND]... Commands: test some @@ -3120,7 +3132,9 @@ Options: #[test] fn flatten_with_external_subcommand() { static EXPECTED: &str = "\ -Usage: parent [OPTIONS] [COMMAND] +Usage: parent [OPTIONS] + parent test [OPTIONS] + parent help [COMMAND]... Commands: test some @@ -3160,7 +3174,8 @@ Options: #[test] fn flatten_with_subcommand_required() { static EXPECTED: &str = "\ -Usage: parent [OPTIONS] +Usage: parent test [OPTIONS] + parent help [COMMAND]... Commands: test some @@ -3186,7 +3201,8 @@ Options: fn flatten_with_args_conflicts_with_subcommands() { static EXPECTED: &str = "\ Usage: parent [OPTIONS] - parent + parent test [OPTIONS] + parent help [COMMAND]... Commands: test some @@ -3212,7 +3228,19 @@ Options: #[test] fn flatten_recursive() { static EXPECTED: &str = "\ -Usage: parent [OPTIONS] [COMMAND] +Usage: parent [OPTIONS] + parent child1 [OPTIONS] + parent child1 grandchild1 [OPTIONS] + parent child1 grandchild1 greatgrandchild1 [OPTIONS] + parent child1 grandchild1 greatgrandchild2 [OPTIONS] + parent child1 grandchild1 greatgrandchild3 [OPTIONS] + parent child1 grandchild1 help [COMMAND] + parent child1 grandchild2 [OPTIONS] + parent child1 grandchild3 [OPTIONS] + parent child1 help [COMMAND] + parent child2 [OPTIONS] + parent child3 [OPTIONS] + parent help [COMMAND]... Commands: child1 some 1 @@ -3280,7 +3308,11 @@ Options: #[test] fn flatten_not_recursive() { static EXPECTED: &str = "\ -Usage: parent [OPTIONS] [COMMAND] +Usage: parent [OPTIONS] + parent child1 [OPTIONS] [COMMAND] + parent child2 [OPTIONS] + parent child3 [OPTIONS] + parent help [COMMAND]... Commands: child1 some 1 From 66d2bcbdd4067e83eff6241fad9c6e55e771d024 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 9 Nov 2023 13:22:08 -0600 Subject: [PATCH 3/7] feat(help): Allow flattening help --- clap_builder/src/output/help_template.rs | 58 ++++++++- examples/git-derive.md | 21 +++- examples/git.md | 21 +++- tests/builder/help.rs | 142 +++++++++++++++-------- 4 files changed, 181 insertions(+), 61 deletions(-) diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index da2e7541328..f8e07adaccc 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -393,9 +393,11 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { .filter_map(|arg| arg.get_help_heading()) .collect::>(); + let flatten = self.cmd.is_flatten_help_set(); + let mut first = true; - if subcmds { + if subcmds && !flatten { if !first { self.writer.push_str("\n\n"); } @@ -474,6 +476,60 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { } } } + if flatten { + let mut cmd = self.cmd.clone(); + cmd.build(); + + let mut ord_v = Vec::new(); + for subcommand in cmd + .get_subcommands() + .filter(|subcommand| should_show_subcommand(subcommand)) + { + ord_v.push(( + subcommand.get_display_order(), + subcommand.get_name(), + subcommand, + )); + } + ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1))); + for (_, _, subcommand) in ord_v { + if !first { + self.writer.push_str("\n\n"); + } + first = false; + + let heading = subcommand.get_usage_name_fallback(); + let about = cmd + .get_about() + .or_else(|| cmd.get_long_about()) + .unwrap_or_default(); + + let _ = write!( + self.writer, + "{}{heading}:{}\n", + header.render(), + header.render_reset() + ); + if !about.is_empty() { + let _ = write!(self.writer, "{about}\n",); + } + + let mut sub_help = HelpTemplate { + writer: self.writer, + cmd: subcommand, + styles: self.styles, + usage: self.usage, + next_line_help: false, + term_w: self.term_w, + use_long: false, + }; + let args = subcommand + .get_arguments() + .filter(|arg| should_show_arg(self.use_long, arg)) + .collect::>(); + sub_help.write_args(&args, heading, option_sort_key); + } + } } /// Sorts arguments by length and display order and write their help to the wrapped stream. diff --git a/examples/git-derive.md b/examples/git-derive.md index afc4d0c8feb..442498dc603 100644 --- a/examples/git-derive.md +++ b/examples/git-derive.md @@ -78,16 +78,25 @@ Usage: git-derive[EXE] stash [OPTIONS] git-derive[EXE] stash apply [STASH] git-derive[EXE] stash help [COMMAND]... -Commands: - push - pop - apply - help Print this message or the help of the given subcommand(s) - Options: -m, --message -h, --help Print help +git-derive[EXE] stash push: + -m, --message + -h, --help Print help + +git-derive[EXE] stash pop: + -h, --help Print help + [STASH] + +git-derive[EXE] stash apply: + -h, --help Print help + [STASH] + +git-derive[EXE] stash help: + [COMMAND]... Print help for the subcommand(s) + $ git-derive stash push -h Usage: git-derive[EXE] stash push [OPTIONS] diff --git a/examples/git.md b/examples/git.md index 66e8baae83b..55acc69c246 100644 --- a/examples/git.md +++ b/examples/git.md @@ -76,16 +76,25 @@ Usage: git[EXE] stash [OPTIONS] git[EXE] stash apply [STASH] git[EXE] stash help [COMMAND]... -Commands: - push - pop - apply - help Print this message or the help of the given subcommand(s) - Options: -m, --message -h, --help Print help +git[EXE] stash push: + -m, --message + -h, --help Print help + +git[EXE] stash pop: + -h, --help Print help + [STASH] + +git[EXE] stash apply: + -h, --help Print help + [STASH] + +git[EXE] stash help: + [COMMAND]... Print help for the subcommand(s) + $ git stash push -h Usage: git[EXE] stash push [OPTIONS] diff --git a/tests/builder/help.rs b/tests/builder/help.rs index 22cbaab583c..83bed4b519c 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -2956,13 +2956,16 @@ Usage: parent [OPTIONS] parent test [OPTIONS] parent help [COMMAND]... -Commands: - test some - help Print this message or the help of the given subcommand(s) - Options: --parent -h, --help Print help + +parent test: + --child + -h, --help Print help + +parent help: + [COMMAND]... Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) @@ -2982,13 +2985,16 @@ Usage: parent [OPTIONS] parent test [OPTIONS] parent help [COMMAND]... -Commands: - test some - help Print this message or the help of the given subcommand(s) - Options: --parent foo -h, --help Print help (see more with '--help') + +parent test: + --child foo + -h, --help Print help (see more with '--help') + +parent help: + [COMMAND]... Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) @@ -3014,16 +3020,19 @@ Usage: parent [OPTIONS] parent test [OPTIONS] parent help [COMMAND]... -Commands: - test some - help Print this message or the help of the given subcommand(s) - Options: --parent bar -h, --help Print help (see a summary with '-h') + +parent test: + --child foo + -h, --help Print help (see more with '--help') + +parent help: + [COMMAND]... Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) @@ -3049,16 +3058,19 @@ Usage: parent [OPTIONS] parent test [OPTIONS] parent help [COMMAND]... -Commands: - test some - help Print this message or the help of the given subcommand(s) - Options: --parent bar -h, --help Print help (see a summary with '-h') + +parent test: + --child foo + -h, --help Print help (see more with '--help') + +parent help: + [COMMAND]... Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) @@ -3084,13 +3096,17 @@ Usage: parent [OPTIONS] parent test [OPTIONS] parent help [COMMAND]... -Commands: - test some - help Print this message or the help of the given subcommand(s) - Options: --parent -h, --help Print help + +parent test: + --child + --parent + -h, --help Print help + +parent help: + [COMMAND]... Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) @@ -3110,13 +3126,16 @@ Usage: parent --parent parent --parent test --child parent --parent help [COMMAND]... -Commands: - test some - help Print this message or the help of the given subcommand(s) - Options: --parent -h, --help Print help + +parent --parent test: + --child + -h, --help Print help + +parent --parent help: + [COMMAND]... Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) @@ -3136,13 +3155,16 @@ Usage: parent [OPTIONS] parent test [OPTIONS] parent help [COMMAND]... -Commands: - test some - help Print this message or the help of the given subcommand(s) - Options: --parent -h, --help Print help + +parent test: + --child + -h, --help Print help + +parent help: + [COMMAND]... Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) @@ -3177,13 +3199,16 @@ fn flatten_with_subcommand_required() { Usage: parent test [OPTIONS] parent help [COMMAND]... -Commands: - test some - help Print this message or the help of the given subcommand(s) - Options: --parent -h, --help Print help + +parent test: + --child + -h, --help Print help + +parent help: + [COMMAND]... Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) @@ -3204,13 +3229,16 @@ Usage: parent [OPTIONS] parent test [OPTIONS] parent help [COMMAND]... -Commands: - test some - help Print this message or the help of the given subcommand(s) - Options: --parent -h, --help Print help + +parent test: + --child + -h, --help Print help + +parent help: + [COMMAND]... Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) @@ -3242,15 +3270,24 @@ Usage: parent [OPTIONS] parent child3 [OPTIONS] parent help [COMMAND]... -Commands: - child1 some 1 - child2 some 2 - child3 some 3 - help Print this message or the help of the given subcommand(s) - Options: --parent -h, --help Print help + +parent child1: + --child1 + -h, --help Print help + +parent child2: + --child2 + -h, --help Print help + +parent child3: + --child3 + -h, --help Print help + +parent help: + [COMMAND]... Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) @@ -3314,15 +3351,24 @@ Usage: parent [OPTIONS] parent child3 [OPTIONS] parent help [COMMAND]... -Commands: - child1 some 1 - child2 some 2 - child3 some 3 - help Print this message or the help of the given subcommand(s) - Options: --parent -h, --help Print help + +parent child1: + --child1 + -h, --help Print help + +parent child2: + --child2 + -h, --help Print help + +parent child3: + --child3 + -h, --help Print help + +parent help: + [COMMAND]... Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) From 9e5f93d43fa5aa60a0523fca342a694adca990dc Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 9 Nov 2023 14:31:52 -0600 Subject: [PATCH 4/7] fix(help): Be consistent in long/short help --- clap_builder/src/output/help_template.rs | 4 ++-- tests/builder/help.rs | 20 ++++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index f8e07adaccc..d1c304b5861 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -519,9 +519,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { cmd: subcommand, styles: self.styles, usage: self.usage, - next_line_help: false, + next_line_help: self.next_line_help, term_w: self.term_w, - use_long: false, + use_long: self.use_long, }; let args = subcommand .get_arguments() diff --git a/tests/builder/help.rs b/tests/builder/help.rs index 83bed4b519c..9ee71be6057 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -3028,11 +3028,15 @@ Options: Print help (see a summary with '-h') parent test: - --child foo - -h, --help Print help (see more with '--help') + --child + bar + + -h, --help + Print help (see a summary with '-h') parent help: - [COMMAND]... Print help for the subcommand(s) + [COMMAND]... + Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) @@ -3066,11 +3070,15 @@ Options: Print help (see a summary with '-h') parent test: - --child foo - -h, --help Print help (see more with '--help') + --child + bar + + -h, --help + Print help (see a summary with '-h') parent help: - [COMMAND]... Print help for the subcommand(s) + [COMMAND]... + Print help for the subcommand(s) "; let cmd = Command::new("parent") .flatten_help(true) From c9a7ef06e15ac806f23e0baf671fa68fbe73cedb Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 9 Nov 2023 14:37:09 -0600 Subject: [PATCH 5/7] fix(help): Gloss over globals with flatten When using globals, people tend to make all of the top-level arguments global and cascading them through would just bloat the output. --- clap_builder/src/output/help_template.rs | 2 +- tests/builder/help.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index d1c304b5861..ac7b221f7de 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -525,7 +525,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { }; let args = subcommand .get_arguments() - .filter(|arg| should_show_arg(self.use_long, arg)) + .filter(|arg| should_show_arg(self.use_long, arg) && !arg.is_global_set()) .collect::>(); sub_help.write_args(&args, heading, option_sort_key); } diff --git a/tests/builder/help.rs b/tests/builder/help.rs index 9ee71be6057..3695056feca 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -3109,9 +3109,8 @@ Options: -h, --help Print help parent test: - --child - --parent - -h, --help Print help + --child + -h, --help Print help parent help: [COMMAND]... Print help for the subcommand(s) From 4bef91ca3c7526ec0723f736b9899e614741d14b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 9 Nov 2023 14:44:52 -0600 Subject: [PATCH 6/7] refactor(help): Pull out flat subcommands --- clap_builder/src/output/help_template.rs | 111 +++++++++++++---------- 1 file changed, 61 insertions(+), 50 deletions(-) diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index ac7b221f7de..f2faea07b47 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -479,56 +479,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { if flatten { let mut cmd = self.cmd.clone(); cmd.build(); - - let mut ord_v = Vec::new(); - for subcommand in cmd - .get_subcommands() - .filter(|subcommand| should_show_subcommand(subcommand)) - { - ord_v.push(( - subcommand.get_display_order(), - subcommand.get_name(), - subcommand, - )); - } - ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1))); - for (_, _, subcommand) in ord_v { - if !first { - self.writer.push_str("\n\n"); - } - first = false; - - let heading = subcommand.get_usage_name_fallback(); - let about = cmd - .get_about() - .or_else(|| cmd.get_long_about()) - .unwrap_or_default(); - - let _ = write!( - self.writer, - "{}{heading}:{}\n", - header.render(), - header.render_reset() - ); - if !about.is_empty() { - let _ = write!(self.writer, "{about}\n",); - } - - let mut sub_help = HelpTemplate { - writer: self.writer, - cmd: subcommand, - styles: self.styles, - usage: self.usage, - next_line_help: self.next_line_help, - term_w: self.term_w, - use_long: self.use_long, - }; - let args = subcommand - .get_arguments() - .filter(|arg| should_show_arg(self.use_long, arg) && !arg.is_global_set()) - .collect::>(); - sub_help.write_args(&args, heading, option_sort_key); - } + self.write_flat_subcommands(&cmd, first); } } @@ -929,6 +880,66 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { /// Subcommand handling impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { + /// Writes help for subcommands of a Parser Object to the wrapped stream. + fn write_flat_subcommands(&mut self, cmd: &Command, mut first: bool) { + debug!( + "HelpTemplate::write_flat_subcommands, cmd={}, first={first}", + cmd.get_name() + ); + use std::fmt::Write as _; + let header = &self.styles.get_header(); + + let mut ord_v = Vec::new(); + for subcommand in cmd + .get_subcommands() + .filter(|subcommand| should_show_subcommand(subcommand)) + { + ord_v.push(( + subcommand.get_display_order(), + subcommand.get_name(), + subcommand, + )); + } + ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1))); + for (_, _, subcommand) in ord_v { + if !first { + self.writer.push_str("\n\n"); + } + first = false; + + let heading = subcommand.get_usage_name_fallback(); + let about = cmd + .get_about() + .or_else(|| cmd.get_long_about()) + .unwrap_or_default(); + + let _ = write!( + self.writer, + "{}{heading}:{}\n", + header.render(), + header.render_reset() + ); + if !about.is_empty() { + let _ = write!(self.writer, "{about}\n",); + } + + let mut sub_help = HelpTemplate { + writer: self.writer, + cmd: subcommand, + styles: self.styles, + usage: self.usage, + next_line_help: self.next_line_help, + term_w: self.term_w, + use_long: self.use_long, + }; + let args = subcommand + .get_arguments() + .filter(|arg| should_show_arg(self.use_long, arg) && !arg.is_global_set()) + .collect::>(); + sub_help.write_args(&args, heading, option_sort_key); + } + } + /// Writes help for subcommands of a Parser Object to the wrapped stream. fn write_subcommands(&mut self, cmd: &Command) { debug!("HelpTemplate::write_subcommands"); From 9c0f7a7253e914fea33078f28351c5447ee8ef15 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 9 Nov 2023 14:50:22 -0600 Subject: [PATCH 7/7] fix(help): Recurse help flattening --- clap_builder/src/output/help_template.rs | 16 ++++++---- tests/builder/help.rs | 38 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index f2faea07b47..92a039dfe1c 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -479,7 +479,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { if flatten { let mut cmd = self.cmd.clone(); cmd.build(); - self.write_flat_subcommands(&cmd, first); + self.write_flat_subcommands(&cmd, &mut first); } } @@ -881,10 +881,11 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { /// Subcommand handling impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { /// Writes help for subcommands of a Parser Object to the wrapped stream. - fn write_flat_subcommands(&mut self, cmd: &Command, mut first: bool) { + fn write_flat_subcommands(&mut self, cmd: &Command, first: &mut bool) { debug!( - "HelpTemplate::write_flat_subcommands, cmd={}, first={first}", - cmd.get_name() + "HelpTemplate::write_flat_subcommands, cmd={}, first={}", + cmd.get_name(), + *first ); use std::fmt::Write as _; let header = &self.styles.get_header(); @@ -902,10 +903,10 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { } ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1))); for (_, _, subcommand) in ord_v { - if !first { + if !*first { self.writer.push_str("\n\n"); } - first = false; + *first = false; let heading = subcommand.get_usage_name_fallback(); let about = cmd @@ -937,6 +938,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { .filter(|arg| should_show_arg(self.use_long, arg) && !arg.is_global_set()) .collect::>(); sub_help.write_args(&args, heading, option_sort_key); + if subcommand.is_flatten_help_set() { + sub_help.write_flat_subcommands(subcommand, first); + } } } diff --git a/tests/builder/help.rs b/tests/builder/help.rs index 3695056feca..e8fd22cc696 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -3285,6 +3285,44 @@ parent child1: --child1 -h, --help Print help +parent child1 grandchild1: +some 1 + --grandchild1 + -h, --help Print help + +parent child1 grandchild1 greatgrandchild1: +some 1 + --greatgrandchild1 + -h, --help Print help + +parent child1 grandchild1 greatgrandchild2: +some 1 + --greatgrandchild2 + -h, --help Print help + +parent child1 grandchild1 greatgrandchild3: +some 1 + --greatgrandchild3 + -h, --help Print help + +parent child1 grandchild1 help: +some 1 + + +parent child1 grandchild2: +some 1 + --grandchild2 + -h, --help Print help + +parent child1 grandchild3: +some 1 + --grandchild3 + -h, --help Print help + +parent child1 help: +some 1 + + parent child2: --child2 -h, --help Print help