diff --git a/clap_derive/src/attrs.rs b/clap_derive/src/attrs.rs index 4235b335839..5c9d2b1b120 100644 --- a/clap_derive/src/attrs.rs +++ b/clap_derive/src/attrs.rs @@ -46,6 +46,8 @@ pub struct Attrs { author: Option, version: Option, verbatim_doc_comment: Option, + next_display_order: Option, + next_help_heading: Option, help_heading: Option, is_enum: bool, has_custom_parser: bool, @@ -379,6 +381,8 @@ impl Attrs { author: None, version: None, verbatim_doc_comment: None, + next_display_order: None, + next_help_heading: None, help_heading: None, is_enum: false, has_custom_parser: false, @@ -532,9 +536,16 @@ impl Attrs { self.methods.push(Method::new(raw_ident, val)); } + NextDisplayOrder(ident, expr) => { + self.next_display_order = Some(Method::new(ident, quote!(#expr))); + } + 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 +634,14 @@ impl Attrs { /// generate methods from attributes on top of struct or enum pub fn initial_top_level_methods(&self) -> TokenStream { + let next_display_order = self.next_display_order.as_ref().into_iter(); + 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_display_order)* + #(#next_help_heading)* + #(#help_heading)* + ) } pub fn final_top_level_methods(&self) -> TokenStream { @@ -655,9 +672,15 @@ impl Attrs { } } - pub fn help_heading(&self) -> TokenStream { + pub fn next_display_order(&self) -> TokenStream { + let next_display_order = self.next_display_order.as_ref().into_iter(); + quote!( #(#next_display_order)* ) + } + + 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..291b6287d24 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -213,20 +213,21 @@ 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(); + let next_display_order = attrs.next_display_order(); 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 #next_display_order; 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 #next_display_order; 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..8f1b4510ce3 100644 --- a/clap_derive/src/derives/subcommand.rs +++ b/clap_derive/src/derives/subcommand.rs @@ -187,20 +187,21 @@ 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 next_display_order = attrs.next_display_order(); 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 #next_display_order; 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 #next_display_order; 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..e4bd18a4ebb 100644 --- a/clap_derive/src/parse.rs +++ b/clap_derive/src/parse.rs @@ -54,6 +54,8 @@ pub enum ClapAttr { NameExpr(Ident, Expr), DefaultValueT(Ident, Option), DefaultValueOsT(Ident, Option), + NextDisplayOrder(Ident, Expr), + NextHelpHeading(Ident, Expr), HelpHeading(Ident, Expr), // ident(arbitrary_expr,*) @@ -114,6 +116,23 @@ impl Parse for ClapAttr { Ok(Skip(name, Some(expr))) } + "next_display_order" => { + let expr = ExprLit { + attrs: vec![], + lit: Lit::Str(lit), + }; + let expr = Expr::Lit(expr); + Ok(NextDisplayOrder(name, 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 +150,8 @@ 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_display_order" => Ok(NextDisplayOrder(name, 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..993b9cf841e 100644 --- a/examples/derive_ref/README.md +++ b/examples/derive_ref/README.md @@ -130,7 +130,8 @@ 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_display_order`: `clap::App::next_display_order` +- `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..c6bb2ca6181 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -62,7 +62,7 @@ use crate::{Error, INTERNAL_ERROR_MSG}; /// // Your program logic starts here... /// ``` /// [`App::get_matches`]: App::get_matches() -#[derive(Default, Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct App<'help> { pub(crate) id: Id, pub(crate) name: String, @@ -95,6 +95,7 @@ pub struct App<'help> { pub(crate) replacers: HashMap<&'help str, &'help [&'help str]>, pub(crate) groups: Vec>, pub(crate) current_help_heading: Option<&'help str>, + pub(crate) current_disp_ord: Option, pub(crate) subcommand_value_name: Option<&'help str>, pub(crate) subcommand_heading: Option<&'help str>, } @@ -169,6 +170,14 @@ impl<'help> App<'help> { #[must_use] pub fn arg>>(mut self, a: A) -> Self { let mut arg = a.into(); + if let Some(current_disp_ord) = self.current_disp_ord.as_mut() { + if !arg.is_positional() && arg.provider != ArgProvider::Generated { + let current = *current_disp_ord; + arg.disp_ord.set_implicit(current); + *current_disp_ord = current + 1; + } + } + arg.help_heading.get_or_insert(self.current_help_heading); self.args.push(arg); self @@ -1333,7 +1342,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>, { @@ -1341,6 +1371,16 @@ impl<'help> App<'help> { self } + /// Change the starting value for assigning future display orders for ags. + /// + /// This will be used for any arg that hasn't had [`Arg::display_order`] called. + #[inline] + #[must_use] + pub fn next_display_order(mut self, disp_ord: impl Into>) -> Self { + self.current_disp_ord = disp_ord.into(); + self + } + /// Sets the terminal width at which to wrap help messages. /// /// Using `0` will ignore terminal widths and use source formatting. @@ -2165,7 +2205,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 } @@ -2993,14 +3042,13 @@ impl<'help> App<'help> { debug!("App::_derive_display_order:{}", self.name); if self.settings.is_set(AppSettings::DeriveDisplayOrder) { - for (i, a) in self + for a in self .args .args_mut() .filter(|a| !a.is_positional()) .filter(|a| a.provider != ArgProvider::Generated) - .enumerate() { - a.disp_ord.get_or_insert(i); + a.disp_ord.make_explicit(); } for (i, sc) in &mut self.subcommands.iter_mut().enumerate() { sc.disp_ord.get_or_insert(i); @@ -3309,6 +3357,47 @@ impl<'help> App<'help> { } } +impl<'help> Default for App<'help> { + fn default() -> Self { + Self { + id: Default::default(), + name: Default::default(), + long_flag: Default::default(), + short_flag: Default::default(), + bin_name: Default::default(), + author: Default::default(), + version: Default::default(), + long_version: Default::default(), + about: Default::default(), + long_about: Default::default(), + before_help: Default::default(), + before_long_help: Default::default(), + after_help: Default::default(), + after_long_help: Default::default(), + aliases: Default::default(), + short_flag_aliases: Default::default(), + long_flag_aliases: Default::default(), + usage_str: Default::default(), + usage: Default::default(), + help_str: Default::default(), + disp_ord: Default::default(), + term_w: Default::default(), + max_w: Default::default(), + template: Default::default(), + settings: Default::default(), + g_settings: Default::default(), + args: Default::default(), + subcommands: Default::default(), + replacers: Default::default(), + groups: Default::default(), + current_help_heading: Default::default(), + current_disp_ord: Some(0), + subcommand_value_name: Default::default(), + subcommand_heading: Default::default(), + } + } +} + impl<'help> Index<&'_ Id> for App<'help> { type Output = Arg<'help>; diff --git a/src/build/arg/mod.rs b/src/build/arg/mod.rs index 4c9cdb5dd6a..c7b9c42a2d0 100644 --- a/src/build/arg/mod.rs +++ b/src/build/arg/mod.rs @@ -83,7 +83,7 @@ pub struct Arg<'help> { pub(crate) long: Option<&'help str>, pub(crate) aliases: Vec<(&'help str, bool)>, // (name, visible) pub(crate) short_aliases: Vec<(char, bool)>, // (name, visible) - pub(crate) disp_ord: Option, + pub(crate) disp_ord: DisplayOrder, pub(crate) possible_vals: Vec>, pub(crate) val_names: Vec<&'help str>, pub(crate) num_vals: Option, @@ -2892,7 +2892,7 @@ impl<'help> Arg<'help> { #[inline] #[must_use] pub fn display_order(mut self, ord: usize) -> Self { - self.disp_ord = Some(ord); + self.disp_ord.set_explicit(ord); self } @@ -4982,7 +4982,7 @@ impl<'help> Arg<'help> { } pub(crate) fn get_display_order(&self) -> usize { - self.disp_ord.unwrap_or(999) + self.disp_ord.get_explicit() } } @@ -5186,6 +5186,43 @@ where Ok(()) } +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum DisplayOrder { + None, + Implicit(usize), + Explicit(usize), +} + +impl DisplayOrder { + pub(crate) fn set_explicit(&mut self, explicit: usize) { + *self = Self::Explicit(explicit) + } + + pub(crate) fn set_implicit(&mut self, implicit: usize) { + *self = (*self).max(Self::Implicit(implicit)) + } + + pub(crate) fn make_explicit(&mut self) { + match *self { + Self::None | Self::Explicit(_) => {} + Self::Implicit(disp) => self.set_explicit(disp), + } + } + + pub(crate) fn get_explicit(self) -> usize { + match self { + Self::None | Self::Implicit(_) => 999, + Self::Explicit(disp) => disp, + } + } +} + +impl Default for DisplayOrder { + fn default() -> Self { + Self::None + } +} + // Flags #[cfg(test)] mod test { diff --git a/src/error/mod.rs b/src/error/mod.rs index 82884c3aa9c..fd8396a6476 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -40,9 +40,11 @@ pub type Result = StdResult; pub struct Error { inner: Box, /// The type of error + // TODO: Deprecate pub kind: ErrorKind, /// Additional information depending on the error kind, like values and argument names. /// Useful when you want to render an error of your own. + // TODO: Deprecate pub info: Vec, } diff --git a/tests/builder/derive_order.rs b/tests/builder/derive_order.rs index c1a232c2b76..f61bbc2cb90 100644 --- a/tests/builder/derive_order.rs +++ b/tests/builder/derive_order.rs @@ -133,6 +133,83 @@ fn derive_order() { )); } +#[test] +fn derive_order_next_order() { + static HELP: &str = "test 1.2 + +USAGE: + test [OPTIONS] + +OPTIONS: + --flag_b first flag + --option_b first option + -h, --help Print help information + -V, --version Print version information + --flag_a second flag + --option_a second option +"; + + let app = App::new("test") + .setting(AppSettings::DeriveDisplayOrder) + .version("1.2") + .next_display_order(10000) + .arg(Arg::new("flag_a").long("flag_a").help("second flag")) + .arg( + Arg::new("option_a") + .long("option_a") + .takes_value(true) + .help("second option"), + ) + .next_display_order(10) + .arg(Arg::new("flag_b").long("flag_b").help("first flag")) + .arg( + Arg::new("option_b") + .long("option_b") + .takes_value(true) + .help("first option"), + ); + + assert!(utils::compare_output(app, "test --help", HELP, false)); +} + +#[test] +fn derive_order_no_next_order() { + static HELP: &str = "test 1.2 + +USAGE: + test [OPTIONS] + +OPTIONS: + --flag_a first flag + --flag_b second flag + -h, --help Print help information + --option_a first option + --option_b second option + -V, --version Print version information +"; + + let app = App::new("test") + .setting(AppSettings::DeriveDisplayOrder) + .version("1.2") + .next_display_order(None) + .arg(Arg::new("flag_a").long("flag_a").help("first flag")) + .arg( + Arg::new("option_a") + .long("option_a") + .takes_value(true) + .help("first option"), + ) + .arg(Arg::new("flag_b").long("flag_b").help("second flag")) + .arg( + Arg::new("option_b") + .long("option_b") + .takes_value(true) + .help("second option"), + ); + + assert!(utils::compare_output(app, "test --help", HELP, false)); +} + #[test] fn derive_order_subcommand_propagate() { let app = App::new("test") 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..c471a0eb329 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, } @@ -228,3 +228,173 @@ For more information try --help "#; assert_eq!(result.unwrap_err().to_string(), expected); } + +#[test] +fn derive_order_next_order() { + static HELP: &str = "test 1.2 + +USAGE: + test [OPTIONS] + +OPTIONS: + --flag-b first flag + --option-b first option + -h, --help Print help information + -V, --version Print version information + --flag-a second flag + --option-a second option +"; + + #[derive(Parser, Debug)] + #[clap(name = "test", version = "1.2")] + #[clap(setting = AppSettings::DeriveDisplayOrder)] + struct Args { + #[clap(flatten)] + a: A, + #[clap(flatten)] + b: B, + } + + #[derive(Args, Debug)] + #[clap(next_display_order = 10000)] + struct A { + /// second flag + #[clap(long)] + flag_a: bool, + /// second option + #[clap(long)] + option_a: Option, + } + + #[derive(Args, Debug)] + #[clap(next_display_order = 10)] + struct B { + /// first flag + #[clap(long)] + flag_b: bool, + /// first option + #[clap(long)] + option_b: Option, + } + + use clap::IntoApp; + let mut app = Args::into_app(); + + let mut buffer: Vec = Default::default(); + app.write_help(&mut buffer).unwrap(); + let help = String::from_utf8(buffer).unwrap(); + assert_eq!(help, HELP); +} + +#[test] +fn derive_order_next_order_flatten() { + static HELP: &str = "test 1.2 + +USAGE: + test [OPTIONS] + +OPTIONS: + --flag-b first flag + --option-b first option + -h, --help Print help information + -V, --version Print version information + --flag-a second flag + --option-a second option +"; + + #[derive(Parser, Debug)] + #[clap(setting = AppSettings::DeriveDisplayOrder)] + #[clap(name = "test", version = "1.2")] + struct Args { + #[clap(flatten)] + #[clap(next_display_order = 10000)] + a: A, + #[clap(flatten)] + #[clap(next_display_order = 10)] + b: B, + } + + #[derive(Args, Debug)] + struct A { + /// second flag + #[clap(long)] + flag_a: bool, + /// second option + #[clap(long)] + option_a: Option, + } + + #[derive(Args, Debug)] + struct B { + /// first flag + #[clap(long)] + flag_b: bool, + /// first option + #[clap(long)] + option_b: Option, + } + + use clap::IntoApp; + let mut app = Args::into_app(); + + let mut buffer: Vec = Default::default(); + app.write_help(&mut buffer).unwrap(); + let help = String::from_utf8(buffer).unwrap(); + assert_eq!(help, HELP); +} + +#[test] +fn derive_order_no_next_order() { + static HELP: &str = "test 1.2 + +USAGE: + test [OPTIONS] + +OPTIONS: + --flag-a first flag + --flag-b second flag + -h, --help Print help information + --option-a first option + --option-b second option + -V, --version Print version information +"; + + #[derive(Parser, Debug)] + #[clap(name = "test", version = "1.2")] + #[clap(setting = AppSettings::DeriveDisplayOrder)] + #[clap(next_display_order = None)] + struct Args { + #[clap(flatten)] + a: A, + #[clap(flatten)] + b: B, + } + + #[derive(Args, Debug)] + struct A { + /// first flag + #[clap(long)] + flag_a: bool, + /// first option + #[clap(long)] + option_a: Option, + } + + #[derive(Args, Debug)] + struct B { + /// second flag + #[clap(long)] + flag_b: bool, + /// second option + #[clap(long)] + option_b: Option, + } + + use clap::IntoApp; + let mut app = Args::into_app(); + + let mut buffer: Vec = Default::default(); + app.write_help(&mut buffer).unwrap(); + let help = String::from_utf8(buffer).unwrap(); + assert_eq!(help, HELP); +}