diff --git a/.travis.yml b/.travis.yml index 398862e28dc..792e3eeaa7e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,10 @@ rust: - nightly - beta - stable +# Only while clippy is failing +matrix: + allow_failures: + - rust: nightly before_script: - | pip install 'travis-cargo<0.2' --user && diff --git a/src/app/meta.rs b/src/app/meta.rs index 973a6a3d12e..335d77f592d 100644 --- a/src/app/meta.rs +++ b/src/app/meta.rs @@ -10,6 +10,7 @@ pub struct AppMeta<'b> { pub usage_str: Option<&'b str>, pub usage: Option, pub help_str: Option<&'b str>, + pub disp_ord: usize, } impl<'b> Default for AppMeta<'b> { @@ -24,6 +25,7 @@ impl<'b> Default for AppMeta<'b> { usage: None, bin_name: None, help_str: None, + disp_ord: 999, } } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 46497ef0bad..e915e256e76 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -545,6 +545,56 @@ impl<'a, 'b> App<'a, 'b> { self } + /// Allows custom ordering of subcommands within the help message. Subcommands with a lower + /// value will be displayed first in the help message. This is helpful when one would like to + /// emphasise frequently used subcommands, or prioritize those towards the top of the list. + /// Duplicate values **are** allowed. Subcommands with duplicate display orders will be + /// displayed in alphabetical order. + /// + /// **NOTE:** The default is 999 for all subcommands. + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, SubCommand}; + /// let m = App::new("cust-ord") + /// .subcommand(SubCommand::with_name("alpha") // typically subcommands are grouped + /// // alphabetically by name. Subcommands + /// // without a display_order have a value of + /// // 999 and are displayed alphabetically with + /// // all other 999 subcommands + /// .about("Some help and text")) + /// .subcommand(SubCommand::with_name("beta") + /// .display_order(1) // In order to force this subcommand to appear *first* + /// // all we have to do is give it a value lower than 999. + /// // Any other subcommands with a value of 1 will be displayed + /// // alphabetically with this one...then 2 values, then 3, etc. + /// .about("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 + /// + /// SUBCOMMANDS: + /// beta I should be first! + /// alpha Some help and text + /// ``` + pub fn display_order(mut self, ord: usize) -> Self { + self.p.meta.disp_ord = ord; + self + } /// Prints the full help message to `io::stdout()` using a `BufWriter` /// @@ -783,6 +833,9 @@ impl<'a> From<&'a Yaml> for App<'a, 'a> { if let Some(v) = yaml["after_help"].as_str() { a = a.after_help(v); } + if let Some(v) = yaml["display_order"].as_i64() { + a = a.display_order(v as usize); + } if let Some(v) = yaml["usage"].as_str() { a = a.usage(v); } diff --git a/src/app/parser.rs b/src/app/parser.rs index 5450bb0b84d..701293c5521 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -12,7 +12,7 @@ use app::App; use args::{Arg, FlagBuilder, OptBuilder, ArgGroup, PosBuilder}; use app::settings::{AppSettings, AppFlags}; use args::{AnyArg, ArgMatcher}; -use args::settings::{ArgSettings, ArgFlags}; +use args::settings::ArgSettings; use errors::{ErrorKind, Error}; use errors::Result as ClapResult; use INVALID_UTF8; @@ -747,45 +747,42 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { debugln!("fn=create_help_and_version;"); // name is "hclap_help" because flags are sorted by name if !self.flags.iter().any(|a| a.long.is_some() && a.long.unwrap() == "help") { + debugln!("Building --help"); if self.help_short.is_none() && !self.short_list.contains(&'h') { self.help_short = Some('h'); } let arg = FlagBuilder { - name: "hclap_help".into(), + name: "hclap_help", short: self.help_short, - long: Some("help".into()), - help: Some("Prints help information".into()), - blacklist: None, - requires: None, - overrides: None, - settings: ArgFlags::new(), + long: Some("help"), + help: Some("Prints help information"), + ..Default::default() }; - self.long_list.push("help".into()); + self.long_list.push("help"); self.flags.push(arg); } if !self.settings.is_set(AppSettings::DisableVersion) && !self.flags.iter().any(|a| a.long.is_some() && a.long.unwrap() == "version") { + debugln!("Building --version"); if self.version_short.is_none() && !self.short_list.contains(&'V') { self.version_short = Some('V'); } // name is "vclap_version" because flags are sorted by name let arg = FlagBuilder { - name: "vclap_version".into(), + name: "vclap_version", short: self.version_short, - long: Some("version".into()), - help: Some("Prints version information".into()), - blacklist: None, - requires: None, - overrides: None, - settings: ArgFlags::new(), + long: Some("version"), + help: Some("Prints version information"), + ..Default::default() }; - self.long_list.push("version".into()); + self.long_list.push("version"); self.flags.push(arg); } if !self.subcommands.is_empty() && !self.subcommands .iter() .any(|s| &s.p.meta.name[..] == "help") { + debugln!("Building help"); self.subcommands.push(App::new("help").about("Prints this message")); } } @@ -973,7 +970,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { sdebugln!("Found Empty - Error"); return Err(Error::empty_value(opt, &*self.create_current_usage(matcher))); } - sdebugln!("Found - {:?}, len: {}", v, v.len()); + sdebugln!("Found - {:?}, len: {}", v, v.len_()); try!(self.add_val_to_arg(opt, v, matcher)); } else { sdebugln!("None"); } @@ -1443,40 +1440,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)); + } } } } @@ -1488,26 +1495,30 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b { } } if subcmds { + let mut ord_m = VecMap::new(); try!(write!(w, "\nSUBCOMMANDS:\n")); - for (name, sc) in self.subcommands.iter() - .filter(|s| !s.p.is_set(AppSettings::Hidden)) - .map(|s| (&s.p.meta.name[..], s)) - .collect::>() { - try!(write!(w, "{}{}", tab, name)); - write_spaces!((longest_sc + 4) - (name.len()), w); - if let Some(a) = sc.p.meta.about { - if a.contains("{n}") { - let mut ab = a.split("{n}"); - while let Some(part) = ab.next() { - try!(write!(w, "{}\n", part)); - write_spaces!(longest_sc + 8, w); - try!(write!(w, "{}", ab.next().unwrap_or(""))); + for sc in self.subcommands.iter().filter(|s| !s.p.is_set(AppSettings::Hidden)) { + let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new()); + btm.insert(sc.p.meta.name.clone(), sc); + } + for (_, btm) in ord_m.into_iter() { + for (name, sc) in btm.into_iter() { + try!(write!(w, "{}{}", tab, name)); + write_spaces!((longest_sc + 4) - (name.len()), w); + if let Some(a) = sc.p.meta.about { + if a.contains("{n}") { + let mut ab = a.split("{n}"); + while let Some(part) = ab.next() { + try!(write!(w, "{}\n", part)); + write_spaces!(longest_sc + 8, w); + try!(write!(w, "{}", ab.next().unwrap_or(""))); + } + } else { + try!(write!(w, "{}", a)); } - } else { - try!(write!(w, "{}", a)); } + try!(write!(w, "\n")); } - try!(write!(w, "\n")); } } diff --git a/src/app/settings.rs b/src/app/settings.rs index f88b30ac8d2..8bdc429d2fb 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -31,9 +31,15 @@ bitflags! { #[derive(Debug)] pub struct AppFlags(Flags); +impl Default for AppFlags { + fn default() -> Self { + AppFlags(NEEDS_LONG_VERSION | NEEDS_LONG_HELP | NEEDS_SC_HELP | UTF8_NONE) + } +} + impl AppFlags { pub fn new() -> Self { - AppFlags(NEEDS_LONG_VERSION | NEEDS_LONG_HELP | NEEDS_SC_HELP | UTF8_NONE) + AppFlags::default() } impl_settings! { AppSettings, diff --git a/src/args/arg.rs b/src/args/arg.rs index b30c146af89..17bd8ec358a 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_i64().unwrap() as usize), "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() diff --git a/src/args/group.rs b/src/args/group.rs index a345baee083..726ace6b9a8 100644 --- a/src/args/group.rs +++ b/src/args/group.rs @@ -143,6 +143,8 @@ impl<'a> ArgGroup<'a> { /// that one, and only one argument from this group *must* be present at runtime (unless /// conflicting with another argument). /// + /// **NOTE:** This setting only applies to the current `App` / `SubCommand`, and not globally. + /// /// # Examples /// /// ```rust diff --git a/src/args/settings.rs b/src/args/settings.rs index aa57e408779..a7aa5dee82b 100644 --- a/src/args/settings.rs +++ b/src/args/settings.rs @@ -20,7 +20,7 @@ pub struct ArgFlags(Flags); impl ArgFlags { pub fn new() -> Self { - ArgFlags(EMPTY_VALS | USE_DELIM) + ArgFlags::default() } impl_settings!{ArgSettings, @@ -37,7 +37,7 @@ impl ArgFlags { impl Default for ArgFlags { fn default() -> Self { - ArgFlags::new() + ArgFlags(EMPTY_VALS | USE_DELIM) } }