feat(Opts and Flags): adds support for custom ordering in help messages

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.

```rust
use clap::{App, Arg};
let m = App::new("cust-ord")
    .arg(Arg::with_name("a") // typically args are grouped by alphabetically by name
                             // Args without a display_order have a value of 999 and are
                             // displayed alphabetically with all other 999 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)   // Let's 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 would be displayed
                            // alphabetically with other 1 args. Then 2, then 3, etc.
        .help("I should be first!"))
    .get_matches_from(vec![
        "cust-ord", "--help"
    ]);
```

The above example displays the following help message

```
cust-ord

USAGE:
    cust-ord [FLAGS] [OPTIONS]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
    -O, --other-option <b>    I should be first!
    -o, --long-option <a>     Some help and text
```
This commit is contained in:
Kevin K 2016-03-09 19:24:04 -05:00
parent e2c104c8c5
commit 9803b51e79
5 changed files with 102 additions and 18 deletions

View file

@ -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::<BTreeMap<_, _>>()
.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::<BTreeMap<_, _>>()
.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));
}
}
}
}

View file

@ -68,6 +68,8 @@ pub struct Arg<'a, 'b> where 'a: 'b {
pub val_delim: Option<char>,
#[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 <b> I should be first!
/// -o, --long-option <a> 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,
}
}
}

View file

@ -20,6 +20,7 @@ pub struct FlagBuilder<'n, 'e> {
pub short: Option<char>,
pub overrides: Option<Vec<&'e str>>,
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,
}
}
}

View file

@ -27,6 +27,7 @@ pub struct OptBuilder<'n, 'e> {
pub settings: ArgFlags,
pub val_delim: Option<char>,
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 {

View file

@ -27,6 +27,7 @@ pub struct PosBuilder<'n, 'e> {
pub settings: ArgFlags,
pub val_delim: Option<char>,
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()