Skip to content

Commit

Permalink
feat(Opts and Flags): adds support for custom ordering in help messages
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
kbknapp committed Mar 10, 2016
1 parent e2c104c commit 9803b51
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 18 deletions.
48 changes: 30 additions & 18 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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));
}
}
}
}
Expand Down
63 changes: 63 additions & 0 deletions src/args/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -91,6 +93,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> {
settings: ArgFlags::new(),
val_delim: Some(','),
default_val: None,
disp_ord: 999,
}
}
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
}
}
}
3 changes: 3 additions & 0 deletions src/args/arg_builder/flag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -33,6 +34,7 @@ impl<'n, 'e> Default for FlagBuilder<'n, 'e> {
short: None,
overrides: None,
settings: ArgFlags::new(),
disp_ord: 999,
}
}
}
Expand Down Expand Up @@ -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,
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/args/arg_builder/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -48,6 +49,7 @@ impl<'n, 'e> Default for OptBuilder<'n, 'e> {
settings: ArgFlags::new(),
val_delim: Some(','),
default_val: None,
disp_ord: 999,
}
}
}
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions src/args/arg_builder/positional.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -47,6 +48,7 @@ impl<'n, 'e> Default for PosBuilder<'n, 'e> {
settings: ArgFlags::new(),
val_delim: Some(','),
default_val: None,
disp_ord: 999,
}
}
}
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 9803b51

Please sign in to comment.