Skip to content

Commit

Permalink
setting(PropagateGlobalValuesDown): adds a setting to allow automatic…
Browse files Browse the repository at this point in the history
…ally propagating global args values down through *used* subcommands

Closes #694
  • Loading branch information
kbknapp committed Jan 3, 2017
1 parent 20842ed commit 985536c
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 35 deletions.
20 changes: 19 additions & 1 deletion src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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)
}
Expand Down Expand Up @@ -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())
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
106 changes: 73 additions & 33 deletions src/app/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
33 changes: 33 additions & 0 deletions src/args/arg_matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) }
Expand Down

2 comments on commit 985536c

@rumpelsepp
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be that this caused the shell completion generator to panic?

thread 'main' panicked at 'Non-unique argument name: THREADS is already in use', /home/stefan/.cargo/registry/src/github.aaakk.us.kg-1ecc6299db9ec823/clap-2.20.3/src/app/parser.rs:147
note: Run with `RUST_BACKTRACE=1` for a backtrace.

I have this build script:

#[macro_use]
extern crate clap;

use std::env;
use std::str::FromStr;
use clap::Shell;

include!("src/cli.rs");

fn main() {
    let outdir = match env::var_os("OUT_DIR") {
        Some(outdir) => outdir,
        None => return,
    };

    let mut app = build();

    for shell in Shell::variants().into_iter() {
        let shell = Shell::from_str(shell).expect("BUG in clap?");
        let outdir = outdir.clone();
        app.gen_completions(crate_name!(), shell, outdir);
    }
}

@kbknapp
Copy link
Member Author

@kbknapp kbknapp commented on 985536c Feb 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.