Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
2 changes: 2 additions & 0 deletions src/app/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub struct AppMeta<'b> {
pub usage_str: Option<&'b str>,
pub usage: Option<String>,
pub help_str: Option<&'b str>,
pub disp_ord: usize,
}

impl<'b> Default for AppMeta<'b> {
Expand All @@ -24,6 +25,7 @@ impl<'b> Default for AppMeta<'b> {
usage: None,
bin_name: None,
help_str: None,
disp_ord: 999,
}
}
}
Expand Down
53 changes: 53 additions & 0 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
///
Expand Down Expand Up @@ -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);
}
Expand Down
115 changes: 63 additions & 52 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"));
}
}
Expand Down Expand Up @@ -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"); }

Expand Down Expand Up @@ -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::<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 All @@ -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::<BTreeMap<_, _>>() {
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"));
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/app/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading