diff --git a/src/app/parser.rs b/src/app/parser.rs index 5450bb0b84d..c6e0404ac16 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -759,6 +759,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { requires: None, overrides: None, settings: ArgFlags::new(), + disp_ord: 999, }; self.long_list.push("help".into()); self.flags.push(arg); @@ -778,6 +779,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { requires: None, overrides: None, settings: ArgFlags::new(), + disp_ord: 999, }; self.long_list.push("version".into()); self.flags.push(arg); @@ -1443,40 +1445,50 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { let nlh = self.settings.is_set(AppSettings::NextLineHelp); if unified_help && (flags || opts) { try!(write!(w, "\nOPTIONS:\n")); - let mut combined = BTreeMap::new(); + let mut ord_m = VecMap::new(); for f in self.flags.iter().filter(|f| !f.settings.is_set(ArgSettings::Hidden)) { + let btm = ord_m.entry(f.disp_ord).or_insert(BTreeMap::new()); let mut v = vec![]; try!(f.write_help(&mut v, tab, longest, nlh)); - combined.insert(f.name, v); + btm.insert(f.name, v); } for o in self.opts.iter().filter(|o| !o.settings.is_set(ArgSettings::Hidden)) { + let btm = ord_m.entry(o.disp_ord).or_insert(BTreeMap::new()); let mut v = vec![]; try!(o.write_help(&mut v, tab, longest, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh)); - combined.insert(o.name, v); + btm.insert(o.name, v); } - for (_, a) in combined { - // Only valid UTF-8 is supported, so panicing on invalid UTF-8 is ok - try!(write!(w, "{}", unsafe { String::from_utf8_unchecked(a) })); + for (_, btm) in ord_m.into_iter() { + for (_, a) in btm.into_iter() { + // Only valid UTF-8 is supported, so panicing on invalid UTF-8 is ok + try!(write!(w, "{}", unsafe { String::from_utf8_unchecked(a) })); + } } } else { if flags { + let mut ord_m = VecMap::new(); try!(write!(w, "\nFLAGS:\n")); - for f in self.flags.iter() - .filter(|f| !f.settings.is_set(ArgSettings::Hidden)) - .map(|f| (f.name, f)) - .collect::>() - .values() { - try!(f.write_help(w, tab, longest, nlh)); + for f in self.flags.iter().filter(|f| !f.settings.is_set(ArgSettings::Hidden)) { + let btm = ord_m.entry(f.disp_ord).or_insert(BTreeMap::new()); + btm.insert(f.name, f); + } + for (_, btm) in ord_m.into_iter() { + for (_, f) in btm.into_iter() { + try!(f.write_help(w, tab, longest, nlh)); + } } } if opts { + let mut ord_m = VecMap::new(); try!(write!(w, "\nOPTIONS:\n")); - for o in self.opts.iter() - .filter(|o| !o.settings.is_set(ArgSettings::Hidden)) - .map(|o| (o.name, o)) - .collect::>() - .values() { - try!(o.write_help(w, tab, longest_opt, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh)); + for o in self.opts.iter().filter(|o| !o.settings.is_set(ArgSettings::Hidden)) { + let btm = ord_m.entry(o.disp_ord).or_insert(BTreeMap::new()); + btm.insert(o.name, o); + } + for (_, btm) in ord_m.into_iter() { + for (_, o) in btm.into_iter() { + try!(o.write_help(w, tab, longest_opt, self.is_set(AppSettings::HidePossibleValuesInHelp), nlh)); + } } } } diff --git a/src/args/arg.rs b/src/args/arg.rs index b30c146af89..9427f030282 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -68,6 +68,8 @@ pub struct Arg<'a, 'b> where 'a: 'b { pub val_delim: Option, #[doc(hidden)] pub default_val: Option<&'a str>, + #[doc(hidden)] + pub disp_ord: usize, } impl<'a, 'b> Default for Arg<'a, 'b> { @@ -91,6 +93,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> { settings: ArgFlags::new(), val_delim: Some(','), default_val: None, + disp_ord: 999, } } } @@ -154,6 +157,7 @@ impl<'a, 'b> Arg<'a, 'b> { "value_name" => a.value_name(v.as_str().unwrap()), "use_delimiter" => a.use_delimiter(v.as_bool().unwrap()), "value_delimiter" => a.value_delimiter(v.as_str().unwrap()), + "display_order" => a.display_order(v.as_u32().unwrap()), "value_names" => { for ys in v.as_vec().unwrap() { if let Some(s) = ys.as_str() { @@ -1795,6 +1799,64 @@ impl<'a, 'b> Arg<'a, 'b> { self } + /// Allows custom ordering of args within the help message. Args with a lower value will be + /// displayed first in the help message. This is helpful when one would like to emphasise + /// frequently used args, or prioritize those towards the top of the list. Duplicate values + /// **are** allowed. Args with duplicate display orders will be displayed in alphabetical + /// order. + /// + /// **NOTE:** The default is 999 for all arguments. + /// + /// **NOTE:** This setting is ignored for positional arguments which are always displayed in + /// index order. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let m = App::new("cust-ord") + /// .arg(Arg::with_name("a") // Typically args are grouped alphabetically by name. + /// // Args without a display_order have a value of 999 and are + /// // displayed alphabetically with all other 999 valued args. + /// .long("long-option") + /// .short("o") + /// .takes_value(true) + /// .help("Some help and text")) + /// .arg(Arg::with_name("b") + /// .long("other-option") + /// .short("O") + /// .takes_value(true) + /// .display_order(1) // In order to force this arg to appear *first* + /// // all we have to do is give it a value lower than 999. + /// // Any other args with a value of 1 will be displayed + /// // alphabetically with this one...then 2 values, then 3, etc. + /// .help("I should be first!")) + /// .get_matches_from(vec![ + /// "cust-ord", "--help" + /// ]); + /// ``` + /// + /// The above example displays the following help message + /// + /// ```ignore + /// cust-ord + /// + /// USAGE: + /// cust-ord [FLAGS] [OPTIONS] + /// + /// FLAGS: + /// -h, --help Prints help information + /// -V, --version Prints version information + /// + /// OPTIONS: + /// -O, --other-option I should be first! + /// -o, --long-option Some help and text + /// ``` + pub fn display_order(mut self, ord: usize) -> Self { + self.disp_ord = ord; + self + } + /// Checks if one of the `ArgSettings` settings is set for the argument pub fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) @@ -1845,6 +1907,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> settings: a.settings, val_delim: a.val_delim, default_val: a.default_val, + disp_ord: a.disp_ord, } } } diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index faef97804f1..7f17cddaa7e 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -20,6 +20,7 @@ pub struct FlagBuilder<'n, 'e> { pub short: Option, pub overrides: Option>, pub settings: ArgFlags, + pub disp_ord: usize, } impl<'n, 'e> Default for FlagBuilder<'n, 'e> { @@ -33,6 +34,7 @@ impl<'n, 'e> Default for FlagBuilder<'n, 'e> { short: None, overrides: None, settings: ArgFlags::new(), + disp_ord: 999, } } } @@ -74,6 +76,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> { overrides: a.overrides.clone(), requires: a.requires.clone(), settings: a.settings, + disp_ord: a.disp_ord, } } } diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 58ca132e64d..b60bbaeb968 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -27,6 +27,7 @@ pub struct OptBuilder<'n, 'e> { pub settings: ArgFlags, pub val_delim: Option, pub default_val: Option<&'n str>, + pub disp_ord: usize, } impl<'n, 'e> Default for OptBuilder<'n, 'e> { @@ -48,6 +49,7 @@ impl<'n, 'e> Default for OptBuilder<'n, 'e> { settings: ArgFlags::new(), val_delim: Some(','), default_val: None, + disp_ord: 999, } } } @@ -82,6 +84,7 @@ impl<'n, 'e> OptBuilder<'n, 'e> { possible_vals: a.possible_vals.clone(), settings: a.settings, default_val: a.default_val, + disp_ord: a.disp_ord, ..Default::default() }; if let Some(ref vec) = ob.val_names { diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index 893dd125ff2..b2ae2c1eee9 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -27,6 +27,7 @@ pub struct PosBuilder<'n, 'e> { pub settings: ArgFlags, pub val_delim: Option, pub default_val: Option<&'n str>, + pub disp_ord: usize, } impl<'n, 'e> Default for PosBuilder<'n, 'e> { @@ -47,6 +48,7 @@ impl<'n, 'e> Default for PosBuilder<'n, 'e> { settings: ArgFlags::new(), val_delim: Some(','), default_val: None, + disp_ord: 999, } } } @@ -83,6 +85,7 @@ impl<'n, 'e> PosBuilder<'n, 'e> { val_delim: a.val_delim, settings: a.settings, default_val: a.default_val, + disp_ord: a.disp_ord, ..Default::default() }; if a.max_vals.is_some()