setting(PropagateGlobalValuesDown): adds a setting to allow automatically propagating global args values down through *used* subcommands

Closes #694
This commit is contained in:
Kevin K 2017-01-02 23:02:34 -05:00
parent 20842ed8c2
commit 985536c8eb
No known key found for this signature in database
GPG key ID: 17218E4B3692F01A
4 changed files with 126 additions and 35 deletions

View file

@ -1004,6 +1004,12 @@ impl<'a, 'b> App<'a, 'b> {
/// [`io::stdout()`]: https://doc.rust-lang.org/std/io/fn.stdout.html
/// [`BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html
pub fn print_help(&mut self) -> ClapResult<()> {
// If there are global arguments, or settings we need to propgate them down to subcommands
// before parsing incase we run into a subcommand
self.p.propogate_globals();
self.p.propogate_settings();
self.p.derive_display_order();
self.p.create_help_and_version();
let out = io::stdout();
let mut buf_w = BufWriter::new(out.lock());
@ -1022,7 +1028,13 @@ impl<'a, 'b> App<'a, 'b> {
/// app.write_help(&mut out).expect("failed to write to stdout");
/// ```
/// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
pub fn write_help<W: Write>(&self, w: &mut W) -> ClapResult<()> {
pub fn write_help<W: Write>(&mut self, w: &mut W) -> ClapResult<()> {
// If there are global arguments, or settings we need to propgate them down to subcommands
// before parsing incase we run into a subcommand
self.p.propogate_globals();
self.p.propogate_settings();
self.p.derive_display_order();
self.p.create_help_and_version();
Help::write_app_help(w, self)
}
@ -1352,6 +1364,12 @@ impl<'a, 'b> App<'a, 'b> {
return Err(e);
}
if self.p.is_set(AppSettings::PropagateGlobalValuesDown) {
for a in &self.p.global_args {
matcher.propagate(a.name);
}
}
Ok(matcher.into())
}

View file

@ -52,7 +52,7 @@ pub struct Parser<'a, 'b>
#[doc(hidden)]
pub subcommands: Vec<App<'a, 'b>>,
groups: HashMap<&'a str, ArgGroup<'a>>,
global_args: Vec<Arg<'a, 'b>>,
pub global_args: Vec<Arg<'a, 'b>>,
overrides: Vec<&'b str>,
help_short: Option<char>,
version_short: Option<char>,

View file

@ -4,39 +4,40 @@ use std::str::FromStr;
use std::ops::BitOr;
bitflags! {
flags Flags: u32 {
const SC_NEGATE_REQS = 0b00000000000000000000000000000001,
const SC_REQUIRED = 0b00000000000000000000000000000010,
const A_REQUIRED_ELSE_HELP = 0b00000000000000000000000000000100,
const GLOBAL_VERSION = 0b00000000000000000000000000001000,
const VERSIONLESS_SC = 0b00000000000000000000000000010000,
const UNIFIED_HELP = 0b00000000000000000000000000100000,
const WAIT_ON_ERROR = 0b00000000000000000000000001000000,
const SC_REQUIRED_ELSE_HELP= 0b00000000000000000000000010000000,
const NEEDS_LONG_HELP = 0b00000000000000000000000100000000,
const NEEDS_LONG_VERSION = 0b00000000000000000000001000000000,
const NEEDS_SC_HELP = 0b00000000000000000000010000000000,
const DISABLE_VERSION = 0b00000000000000000000100000000000,
const HIDDEN = 0b00000000000000000001000000000000,
const TRAILING_VARARG = 0b00000000000000000010000000000000,
const NO_BIN_NAME = 0b00000000000000000100000000000000,
const ALLOW_UNK_SC = 0b00000000000000001000000000000000,
const UTF8_STRICT = 0b00000000000000010000000000000000,
const UTF8_NONE = 0b00000000000000100000000000000000,
const LEADING_HYPHEN = 0b00000000000001000000000000000000,
const NO_POS_VALUES = 0b00000000000010000000000000000000,
const NEXT_LINE_HELP = 0b00000000000100000000000000000000,
const DERIVE_DISP_ORDER = 0b00000000001000000000000000000000,
const COLORED_HELP = 0b00000000010000000000000000000000,
const COLOR_ALWAYS = 0b00000000100000000000000000000000,
const COLOR_AUTO = 0b00000001000000000000000000000000,
const COLOR_NEVER = 0b00000010000000000000000000000000,
const DONT_DELIM_TRAIL = 0b00000100000000000000000000000000,
const ALLOW_NEG_NUMS = 0b00001000000000000000000000000000,
const LOW_INDEX_MUL_POS = 0b00010000000000000000000000000000,
const DISABLE_HELP_SC = 0b00100000000000000000000000000000,
const DONT_COLLAPSE_ARGS = 0b01000000000000000000000000000000,
const ARGS_NEGATE_SCS = 0b10000000000000000000000000000000,
flags Flags: u64 {
const SC_NEGATE_REQS = 0b000000000000000000000000000000001,
const SC_REQUIRED = 0b000000000000000000000000000000010,
const A_REQUIRED_ELSE_HELP = 0b000000000000000000000000000000100,
const GLOBAL_VERSION = 0b000000000000000000000000000001000,
const VERSIONLESS_SC = 0b000000000000000000000000000010000,
const UNIFIED_HELP = 0b000000000000000000000000000100000,
const WAIT_ON_ERROR = 0b000000000000000000000000001000000,
const SC_REQUIRED_ELSE_HELP= 0b000000000000000000000000010000000,
const NEEDS_LONG_HELP = 0b000000000000000000000000100000000,
const NEEDS_LONG_VERSION = 0b000000000000000000000001000000000,
const NEEDS_SC_HELP = 0b000000000000000000000010000000000,
const DISABLE_VERSION = 0b000000000000000000000100000000000,
const HIDDEN = 0b000000000000000000001000000000000,
const TRAILING_VARARG = 0b000000000000000000010000000000000,
const NO_BIN_NAME = 0b000000000000000000100000000000000,
const ALLOW_UNK_SC = 0b000000000000000001000000000000000,
const UTF8_STRICT = 0b000000000000000010000000000000000,
const UTF8_NONE = 0b000000000000000100000000000000000,
const LEADING_HYPHEN = 0b000000000000001000000000000000000,
const NO_POS_VALUES = 0b000000000000010000000000000000000,
const NEXT_LINE_HELP = 0b000000000000100000000000000000000,
const DERIVE_DISP_ORDER = 0b000000000001000000000000000000000,
const COLORED_HELP = 0b000000000010000000000000000000000,
const COLOR_ALWAYS = 0b000000000100000000000000000000000,
const COLOR_AUTO = 0b000000001000000000000000000000000,
const COLOR_NEVER = 0b000000010000000000000000000000000,
const DONT_DELIM_TRAIL = 0b000000100000000000000000000000000,
const ALLOW_NEG_NUMS = 0b000001000000000000000000000000000,
const LOW_INDEX_MUL_POS = 0b000010000000000000000000000000000,
const DISABLE_HELP_SC = 0b000100000000000000000000000000000,
const DONT_COLLAPSE_ARGS = 0b001000000000000000000000000000000,
const ARGS_NEGATE_SCS = 0b010000000000000000000000000000000,
const PROPAGATE_VALS_DOWN = 0b100000000000000000000000000000000,
}
}
@ -88,6 +89,7 @@ impl AppFlags {
NeedsLongVersion => NEEDS_LONG_VERSION,
NeedsSubcommandHelp => NEEDS_SC_HELP,
NoBinaryName => NO_BIN_NAME,
PropagateGlobalValuesDown=> PROPAGATE_VALS_DOWN,
StrictUtf8 => UTF8_STRICT,
SubcommandsNegateReqs => SC_NEGATE_REQS,
SubcommandRequired => SC_REQUIRED,
@ -512,6 +514,44 @@ pub enum AppSettings {
/// ```
NextLineHelp,
/// Specifies that the parser should propagate global arg's values down through any *used* child
/// subcommands. Meaning, if a subcommand wasn't used, the values won't be propagated down to
/// said subcommand.
///
/// **NOTE:** Values are only propagated *down* through futher child commands, not up
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, AppSettings, SubCommand};
/// let m = App::new("myprog")
/// .setting(AppSettings::PropagateGlobalValuesDown)
/// .arg(Arg::from_usage("[cmd] 'command to run'")
/// .global(true))
/// .subcommand(SubCommand::with_name("foo"))
/// .get_matches_from(vec!["myprog", "set", "foo"]);
///
/// assert_eq!(m.value_of("cmd"), Some("set"));
///
/// let sub_m = m.subcommand_matches("foo").unwrap();
/// assert_eq!(sub_m.value_of("cmd"), Some("set"));
/// ```
/// Now doing the same thing, but *not* using any subcommands will result in the value not being
/// propagated down.
/// ```rust
/// # use clap::{App, Arg, AppSettings};
/// let m = App::new("myprog")
/// .setting(AppSettings::PropagateGlobalValuesDown)
/// .global_arg(Arg::from_usage("<cmd> 'command to run'"))
/// .subcommand(SubCommand::with_name("foo"))
/// .get_matches_from(vec!["myprog", "set"]);
///
/// assert_eq!(m.value_of("cmd"), Some("set"));
///
/// assert!(m.subcommand_matches("foo").is_none());
/// ```
PropagateGlobalValuesDown,
/// Allows [`SubCommand`]s to override all requirements of the parent command.
/// For example if you had a subcommand or top level application with a required argument
/// that is only required as long as there is no subcommand present,

View file

@ -2,6 +2,7 @@
use std::collections::hash_map::{Entry, Iter};
use std::ffi::OsStr;
use std::ops::Deref;
use std::mem;
// Third Party
use vec_map::VecMap;
@ -22,6 +23,38 @@ impl<'a> Default for ArgMatcher<'a> {
impl<'a> ArgMatcher<'a> {
pub fn new() -> Self { ArgMatcher::default() }
pub fn propagate(&mut self, arg: &'a str) {
debugln!("ArgMatcher::propagate: arg={}", arg);
let vals: VecMap<_> = if let Some(ma) = self.get(arg) {
ma.vals.clone()
} else {
debugln!("ArgMatcher::propagate: arg wasn't used");
return;
};
if let Some(ref mut sc) = self.0.subcommand {
{
let sma = (*sc).matches.args.entry(arg).or_insert_with(|| {
let mut gma = MatchedArg::new();
gma.occurs += 1;
for (i, v) in &vals {
gma.vals.insert(i, v.clone());
}
gma
});
if sma.vals.is_empty() {
for (i, v) in &vals {
sma.vals.insert(i, v.clone());
}
}
}
let mut am = ArgMatcher(mem::replace(&mut sc.matches, ArgMatches::new()));
am.propagate(arg);
mem::swap(&mut am.0, &mut sc.matches);
} else {
debugln!("ArgMatcher::propagate: Subcommand wasn't used");
}
}
pub fn get_mut(&mut self, arg: &str) -> Option<&mut MatchedArg> { self.0.args.get_mut(arg) }
pub fn get(&self, arg: &str) -> Option<&MatchedArg> { self.0.args.get(arg) }