diff --git a/clap_derive/src/attrs.rs b/clap_derive/src/attrs.rs index 4235b335839..133b2e68872 100644 --- a/clap_derive/src/attrs.rs +++ b/clap_derive/src/attrs.rs @@ -46,6 +46,7 @@ pub struct Attrs { author: Option, version: Option, verbatim_doc_comment: Option, + next_help_heading: Option, help_heading: Option, is_enum: bool, has_custom_parser: bool, @@ -379,6 +380,7 @@ impl Attrs { author: None, version: None, verbatim_doc_comment: None, + next_help_heading: None, help_heading: None, is_enum: false, has_custom_parser: false, @@ -535,6 +537,9 @@ impl Attrs { HelpHeading(ident, expr) => { self.help_heading = Some(Method::new(ident, quote!(#expr))); } + NextHelpHeading(ident, expr) => { + self.next_help_heading = Some(Method::new(ident, quote!(#expr))); + } About(ident, about) => { if let Some(method) = @@ -623,8 +628,9 @@ impl Attrs { /// generate methods from attributes on top of struct or enum pub fn initial_top_level_methods(&self) -> TokenStream { + let next_help_heading = self.next_help_heading.as_ref().into_iter(); let help_heading = self.help_heading.as_ref().into_iter(); - quote!( #(#help_heading)* ) + quote!( #(#next_help_heading)* #(#help_heading)* ) } pub fn final_top_level_methods(&self) -> TokenStream { @@ -655,9 +661,10 @@ impl Attrs { } } - pub fn help_heading(&self) -> TokenStream { + pub fn next_help_heading(&self) -> TokenStream { + let next_help_heading = self.next_help_heading.as_ref().into_iter(); let help_heading = self.help_heading.as_ref().into_iter(); - quote!( #(#help_heading)* ) + quote!( #(#next_help_heading)* #(#help_heading)* ) } pub fn cased_name(&self) -> TokenStream { diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs index 27eb21d54b3..b2010d56e3d 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -213,20 +213,20 @@ pub fn gen_augment( Kind::Flatten => { let ty = &field.ty; let old_heading_var = format_ident!("__clap_old_heading"); - let help_heading = attrs.help_heading(); + let next_help_heading = attrs.next_help_heading(); if override_required { Some(quote_spanned! { kind.span()=> - let #old_heading_var = #app_var.get_help_heading(); - let #app_var = #app_var #help_heading; + let #old_heading_var = #app_var.get_next_help_heading(); + let #app_var = #app_var #next_help_heading; let #app_var = <#ty as clap::Args>::augment_args_for_update(#app_var); - let #app_var = #app_var.help_heading(#old_heading_var); + let #app_var = #app_var.next_help_heading(#old_heading_var); }) } else { Some(quote_spanned! { kind.span()=> - let #old_heading_var = #app_var.get_help_heading(); - let #app_var = #app_var #help_heading; + let #old_heading_var = #app_var.get_next_help_heading(); + let #app_var = #app_var #next_help_heading; let #app_var = <#ty as clap::Args>::augment_args(#app_var); - let #app_var = #app_var.help_heading(#old_heading_var); + let #app_var = #app_var.next_help_heading(#old_heading_var); }) } } diff --git a/clap_derive/src/derives/subcommand.rs b/clap_derive/src/derives/subcommand.rs index 735f94f9d5e..6dcc30d066e 100644 --- a/clap_derive/src/derives/subcommand.rs +++ b/clap_derive/src/derives/subcommand.rs @@ -187,20 +187,20 @@ fn gen_augment( Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0]; let old_heading_var = format_ident!("__clap_old_heading"); - let help_heading = attrs.help_heading(); + let next_help_heading = attrs.next_help_heading(); let subcommand = if override_required { quote! { - let #old_heading_var = #app_var.get_help_heading(); - let #app_var = #app_var #help_heading; + let #old_heading_var = #app_var.get_next_help_heading(); + let #app_var = #app_var #next_help_heading; let #app_var = <#ty as clap::Subcommand>::augment_subcommands_for_update(#app_var); - let #app_var = #app_var.help_heading(#old_heading_var); + let #app_var = #app_var.next_help_heading(#old_heading_var); } } else { quote! { - let #old_heading_var = #app_var.get_help_heading(); - let #app_var = #app_var #help_heading; + let #old_heading_var = #app_var.get_next_help_heading(); + let #app_var = #app_var #next_help_heading; let #app_var = <#ty as clap::Subcommand>::augment_subcommands(#app_var); - let #app_var = #app_var.help_heading(#old_heading_var); + let #app_var = #app_var.next_help_heading(#old_heading_var); } }; Some(subcommand) diff --git a/clap_derive/src/parse.rs b/clap_derive/src/parse.rs index 76444d28ffc..b9960ad5c02 100644 --- a/clap_derive/src/parse.rs +++ b/clap_derive/src/parse.rs @@ -54,6 +54,7 @@ pub enum ClapAttr { NameExpr(Ident, Expr), DefaultValueT(Ident, Option), DefaultValueOsT(Ident, Option), + NextHelpHeading(Ident, Expr), HelpHeading(Ident, Expr), // ident(arbitrary_expr,*) @@ -114,6 +115,14 @@ impl Parse for ClapAttr { Ok(Skip(name, Some(expr))) } + "next_help_heading" => { + let expr = ExprLit { + attrs: vec![], + lit: Lit::Str(lit), + }; + let expr = Expr::Lit(expr); + Ok(NextHelpHeading(name, expr)) + } "help_heading" => { let expr = ExprLit { attrs: vec![], @@ -131,6 +140,7 @@ impl Parse for ClapAttr { "skip" => Ok(Skip(name, Some(expr))), "default_value_t" => Ok(DefaultValueT(name, Some(expr))), "default_value_os_t" => Ok(DefaultValueOsT(name, Some(expr))), + "next_help_heading" => Ok(NextHelpHeading(name, expr)), "help_heading" => Ok(HelpHeading(name, expr)), _ => Ok(NameExpr(name, expr)), }, diff --git a/examples/derive_ref/README.md b/examples/derive_ref/README.md index d76c8e693d6..6718843770a 100644 --- a/examples/derive_ref/README.md +++ b/examples/derive_ref/README.md @@ -130,7 +130,7 @@ In addition to the raw attributes, the following magic attributes are supported: - `long_about = `: `clap::App::long_about` - When not present: [Doc comment](#doc-comments) if there is a blank line, else nothing - `verbatim_doc_comment`: Minimizes pre-processing when converting doc comments to `about` / `long_about` -- `help_heading`: `clap::App::help_heading` +- `next_help_heading`: `clap::App::next_help_heading` - When `flatten`ing `Args`, this is scoped to just the args in this struct and any struct `flatten`ed into it - `rename_all = `: Override default field / variant name case conversion for `App::name` / `Arg::name` - When not present: `kebab-case` diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index 3c6928dff66..beda3f3346e 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -1333,7 +1333,28 @@ impl<'help> App<'help> { /// [`Arg::help_heading`]: crate::Arg::help_heading() #[inline] #[must_use] - pub fn help_heading(mut self, heading: O) -> Self + // TODO: Deprecate + pub fn help_heading(self, heading: O) -> Self + where + O: Into>, + { + self.next_help_heading(heading) + } + + /// Set the default section heading for future args. + /// + /// This will be used for any arg that hasn't had [`Arg::help_heading`] called. + /// + /// This is useful if the default `OPTIONS` or `ARGS` headings are + /// not specific enough for one's use case. + /// + /// For subcommands, see [`App::subcommand_help_heading`] + /// + /// [`App::arg`]: App::arg() + /// [`Arg::help_heading`]: crate::Arg::help_heading() + #[inline] + #[must_use] + pub fn next_help_heading(mut self, heading: O) -> Self where O: Into>, { @@ -2165,7 +2186,16 @@ impl<'help> App<'help> { /// /// [`App::help_heading`]: App::help_heading() #[inline] + // TODO: Deprecate pub fn get_help_heading(&self) -> Option<&'help str> { + self.get_next_help_heading() + } + + /// Get the custom section heading specified via [`App::help_heading`]. + /// + /// [`App::help_heading`]: App::help_heading() + #[inline] + pub fn get_next_help_heading(&self) -> Option<&'help str> { self.current_help_heading } diff --git a/tests/builder/help.rs b/tests/builder/help.rs index a2678d8d7ea..b5c1863172c 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -1785,7 +1785,7 @@ fn custom_headers_headers() { .require_delimiter(true) .value_delimiter(':'), ) - .help_heading(Some("NETWORKING")) + .next_help_heading(Some("NETWORKING")) .arg( Arg::new("no-proxy") .short('n') @@ -1842,14 +1842,14 @@ fn multiple_custom_help_headers() { .require_delimiter(true) .value_delimiter(':'), ) - .help_heading(Some("NETWORKING")) + .next_help_heading(Some("NETWORKING")) .arg( Arg::new("no-proxy") .short('n') .long("no-proxy") .help("Do not use system proxy settings"), ) - .help_heading(Some("SPECIAL")) + .next_help_heading(Some("SPECIAL")) .arg( arg!(-b --"birthday-song" "Change which song is played for birthdays") .help_heading(Some("OVERRIDE SPECIAL")), @@ -1862,7 +1862,7 @@ fn multiple_custom_help_headers() { .arg(arg!( -v --"birthday-song-volume" "Change the volume of the birthday song" )) - .help_heading(None) + .next_help_heading(None) .arg( Arg::new("server-addr") .short('a') @@ -1912,7 +1912,7 @@ fn custom_help_headers_hide_args() { .author("Will M.") .about("does stuff") .version("1.4") - .help_heading(Some("NETWORKING")) + .next_help_heading(Some("NETWORKING")) .arg( Arg::new("no-proxy") .short('n') @@ -1920,7 +1920,7 @@ fn custom_help_headers_hide_args() { .help("Do not use system proxy settings") .hide_short_help(true), ) - .help_heading(Some("SPECIAL")) + .next_help_heading(Some("SPECIAL")) .arg( arg!(-b --song "Change which song is played for birthdays") .help_heading(Some("OVERRIDE SPECIAL")), @@ -1928,7 +1928,7 @@ fn custom_help_headers_hide_args() { .arg(arg!( -v --"song-volume" "Change the volume of the birthday song" )) - .help_heading(None) + .next_help_heading(None) .arg( Arg::new("server-addr") .short('a') @@ -2392,7 +2392,7 @@ fn custom_heading_pos() { let app = App::new("test") .version("1.4") .arg(Arg::new("gear").help("Which gear")) - .help_heading(Some("NETWORKING")) + .next_help_heading(Some("NETWORKING")) .arg(Arg::new("speed").help("How fast")); assert!(utils::compare_output( @@ -2418,7 +2418,7 @@ fn only_custom_heading_opts_no_args() { .version("1.4") .setting(AppSettings::DisableVersionFlag) .mut_arg("help", |a| a.hide(true)) - .help_heading(Some("NETWORKING")) + .next_help_heading(Some("NETWORKING")) .arg(arg!(-s --speed "How fast").required(false)); assert!(utils::compare_output( @@ -2444,7 +2444,7 @@ fn only_custom_heading_pos_no_args() { .version("1.4") .setting(AppSettings::DisableVersionFlag) .mut_arg("help", |a| a.hide(true)) - .help_heading(Some("NETWORKING")) + .next_help_heading(Some("NETWORKING")) .arg(Arg::new("speed").help("How fast")); assert!(utils::compare_output( diff --git a/tests/builder/opts.rs b/tests/builder/opts.rs index 4ecb34fc5db..95eab5e9707 100644 --- a/tests/builder/opts.rs +++ b/tests/builder/opts.rs @@ -561,7 +561,7 @@ fn long_eq_val_starts_with_eq() { #[test] fn issue_2022_get_flags_misuse() { let app = App::new("test") - .help_heading(Some("test")) + .next_help_heading(Some("test")) .arg(Arg::new("a").long("a").default_value("32")); let matches = app.try_get_matches_from(&[""]).unwrap(); assert!(matches.value_of("a").is_some()) @@ -571,14 +571,14 @@ fn issue_2022_get_flags_misuse() { fn issue_2279() { let before_help_heading = App::new("app") .arg(Arg::new("foo").short('f').default_value("bar")) - .help_heading(Some("This causes default_value to be ignored")) + .next_help_heading(Some("This causes default_value to be ignored")) .try_get_matches_from(&[""]) .unwrap(); assert_eq!(before_help_heading.value_of("foo"), Some("bar")); let after_help_heading = App::new("app") - .help_heading(Some("This causes default_value to be ignored")) + .next_help_heading(Some("This causes default_value to be ignored")) .arg(Arg::new("foo").short('f').default_value("bar")) .try_get_matches_from(&[""]) .unwrap(); diff --git a/tests/derive/help.rs b/tests/derive/help.rs index c86c5d782b4..742e3de268b 100644 --- a/tests/derive/help.rs +++ b/tests/derive/help.rs @@ -30,7 +30,7 @@ fn arg_help_heading_applied() { #[test] fn app_help_heading_applied() { #[derive(Debug, Clone, Parser)] - #[clap(help_heading = "DEFAULT")] + #[clap(next_help_heading = "DEFAULT")] struct CliOptions { #[clap(long)] #[clap(help_heading = Some("HEADING A"))] @@ -79,14 +79,14 @@ fn app_help_heading_flattened() { } #[derive(Debug, Clone, Args)] - #[clap(help_heading = "HEADING A")] + #[clap(next_help_heading = "HEADING A")] struct OptionsA { #[clap(long)] should_be_in_section_a: u32, } #[derive(Debug, Clone, Args)] - #[clap(help_heading = "HEADING B")] + #[clap(next_help_heading = "HEADING B")] struct OptionsB { #[clap(long)] should_be_in_section_b: u32, @@ -99,7 +99,7 @@ fn app_help_heading_flattened() { #[clap(subcommand)] SubC(SubC), SubAOne, - #[clap(help_heading = "SUB A")] + #[clap(next_help_heading = "SUB A")] SubATwo { should_be_in_sub_a: u32, }, @@ -107,13 +107,13 @@ fn app_help_heading_flattened() { #[derive(Debug, Clone, Subcommand)] enum SubB { - #[clap(help_heading = "SUB B")] + #[clap(next_help_heading = "SUB B")] SubBOne { should_be_in_sub_b: u32 }, } #[derive(Debug, Clone, Subcommand)] enum SubC { - #[clap(help_heading = "SUB C")] + #[clap(next_help_heading = "SUB C")] SubCOne { should_be_in_sub_c: u32 }, } @@ -168,7 +168,7 @@ fn flatten_field_with_help_heading() { #[derive(Debug, Clone, Parser)] struct CliOptions { #[clap(flatten)] - #[clap(help_heading = "HEADING A")] + #[clap(next_help_heading = "HEADING A")] options_a: OptionsA, }