diff --git a/src/app/macros.rs b/src/app/macros.rs index fd98084529f..1a1049d9f57 100644 --- a/src/app/macros.rs +++ b/src/app/macros.rs @@ -1,13 +1,21 @@ macro_rules! remove_overriden { - (@remove $_self:ident, $v:ident, $a:ident.$ov:ident) => { - if let Some(ref ora) = $a.$ov() { - vec_remove_all!($_self.$v, ora); + (@remove_requires $rem_from:expr, $a:ident.$ov:ident) => { + if let Some(ora) = $a.$ov() { + for i in (0 .. $rem_from.len()).rev() { + let should_remove = ora.iter().any(|&(_, ref name)| name == &$rem_from[i]); + if should_remove { $rem_from.swap_remove(i); } + } + } + }; + (@remove $rem_from:expr, $a:ident.$ov:ident) => { + if let Some(ora) = $a.$ov() { + vec_remove_all!($rem_from, ora.iter()); } }; (@arg $_self:ident, $arg:ident) => { - remove_overriden!(@remove $_self, required, $arg.requires); - remove_overriden!(@remove $_self, blacklist, $arg.blacklist); - remove_overriden!(@remove $_self, overrides, $arg.overrides); + remove_overriden!(@remove_requires $_self.required, $arg.requires); + remove_overriden!(@remove $_self.blacklist, $arg.blacklist); + remove_overriden!(@remove $_self.overrides, $arg.overrides); }; ($_self:ident, $name:expr) => { debugln!("macro=remove_overriden!;"); @@ -41,7 +49,7 @@ macro_rules! arg_post_processing { $matcher.remove_all(or); for pa in or { remove_overriden!($me, pa); } $me.overrides.extend(or); - vec_remove_all!($me.required, or); + vec_remove_all!($me.required, or.iter()); } else { sdebugln!("No"); } // Handle conflicts @@ -62,15 +70,15 @@ macro_rules! arg_post_processing { } $me.blacklist.extend(bl); - vec_remove_all!($me.overrides, bl); - vec_remove_all!($me.required, bl); + vec_remove_all!($me.overrides, bl.iter()); + vec_remove_all!($me.required, bl.iter()); } else { sdebugln!("No"); } // Add all required args which aren't already found in matcher to the master // list debug!("Does '{}' have requirements...", $arg.to_string()); if let Some(reqs) = $arg.requires() { - for n in reqs { + for n in reqs.iter().filter(|&&(val, _)| val.is_none()).map(|&(_, name)| name) { if $matcher.contains(&n) { sdebugln!("\tYes '{}' but it's already met", n); continue; @@ -103,7 +111,10 @@ macro_rules! _handle_group_reqs{ }; debugln!("iter;grp={};found={:?}", grp.name, found); if found { - vec_remove_all!($me.required, &grp.args); + for i in (0 .. $me.required.len()).rev() { + let should_remove = grp.args.contains(&grp.args[i]); + if should_remove { $me.required.swap_remove(i); } + } debugln!("Adding args from group to blacklist...{:?}", grp.args); if !grp.multiple { $me.blacklist.extend(&grp.args); diff --git a/src/app/mod.rs b/src/app/mod.rs index 1502739f774..2d61fa0ecef 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1512,7 +1512,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> { fn id(&self) -> usize { self.p.id } fn kind(&self) -> ArgKind { ArgKind::Subcmd } fn overrides(&self) -> Option<&[&'e str]> { None } - fn requires(&self) -> Option<&[&'e str]> { None } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { None } fn blacklist(&self) -> Option<&[&'e str]> { None } fn required_unless(&self) -> Option<&[&'e str]> { None } fn val_names(&self) -> Option<&VecMap<&'e str>> { None } diff --git a/src/app/parser.rs b/src/app/parser.rs index eaa0d140a22..bcb5b33e8f4 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -37,7 +37,7 @@ use suggestions; pub struct Parser<'a, 'b> where 'a: 'b { - required: Vec<&'b str>, + required: Vec<&'a str>, pub short_list: Vec, pub long_list: Vec<&'b str>, blacklist: Vec<&'b str>, @@ -326,13 +326,13 @@ impl<'a, 'b> Parser<'a, 'b> for a in &$v1 { if let Some(a) = self.$t1.$i1().filter(|arg| &arg.b.name == a).next() { if let Some(ref rl) = a.b.requires { - for r in rl { - if !reqs.contains(r) { - if $_self.$t1.$i1().any(|t| &t.b.name == r) { - $tmp.push(*r); - } else if $_self.$t2.$i2().any(|t| &t.b.name == r) { + for &(_, r) in rl.iter() { + if !reqs.contains(&r) { + if $_self.$t1.$i1().any(|t| &t.b.name == &r) { + $tmp.push(r); + } else if $_self.$t2.$i2().any(|t| &t.b.name == &r) { $v2.push(r); - } else if $_self.$t3.$i3().any(|t| &t.b.name == r) { + } else if $_self.$t3.$i3().any(|t| &t.b.name == &r) { $v3.push(r); } else if $_self.groups.contains_key(r) { $gv.push(r); @@ -918,6 +918,7 @@ impl<'a, 'b> Parser<'a, 'b> .expect(INTERNAL_ERROR_MSG); try!(self.parse_subcommand(sc_name, matcher, it)); }; + try!(self.parse_subcommand(sc_name, matcher, it)); } else if self.is_set(AppSettings::SubcommandRequired) { let bn = self.meta.bin_name.as_ref().unwrap_or(&self.meta.name); return Err(Error::missing_subcommand(bn, @@ -1603,23 +1604,28 @@ impl<'a, 'b> Parser<'a, 'b> Ok(()) } - fn validate_num_args(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { - debugln!("fn=validate_num_args;"); + fn validate_matched_args(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { + debugln!("fn=validate_matched_args;"); for (name, ma) in matcher.iter() { debugln!("iter;name={}", name); if let Some(opt) = find_by_name!(self, name, opts, iter) { - try!(self._validate_num_vals(opt, ma, matcher)); + try!(self.validate_arg_num_vals(opt, ma, matcher)); + try!(self.validate_arg_requires(opt, ma, matcher)); + } else if let Some(flag) = find_by_name!(self, name, flags, iter) { + // Only requires + try!(self.validate_arg_requires(flag, ma, matcher)); } else if let Some(pos) = find_by_name!(self, name, positionals, values) { - try!(self._validate_num_vals(pos, ma, matcher)); + try!(self.validate_arg_num_vals(pos, ma, matcher)); + try!(self.validate_arg_requires(pos, ma, matcher)); } } Ok(()) } - fn _validate_num_vals(&self, a: &A, ma: &MatchedArg, matcher: &ArgMatcher) -> ClapResult<()> + fn validate_arg_num_vals(&self, a: &A, ma: &MatchedArg, matcher: &ArgMatcher) -> ClapResult<()> where A: AnyArg<'a, 'b> + Display { - debugln!("fn=_validate_num_vals;"); + debugln!("fn=validate_arg_num_vals;"); if let Some(num) = a.num_vals() { debugln!("num_vals set: {}", num); let should_err = if a.is_set(ArgSettings::Multiple) { @@ -1679,6 +1685,32 @@ impl<'a, 'b> Parser<'a, 'b> Ok(()) } + fn validate_arg_requires(&self, a: &A, ma: &MatchedArg, matcher: &ArgMatcher) -> ClapResult<()> + where A: AnyArg<'a, 'b> + Display + { + debugln!("fn=validate_arg_requires;"); + if let Some(a_reqs) = a.requires() { + for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) { + if ma.vals.values().any(|v| v == val.expect(INTERNAL_ERROR_MSG)) { + if matcher.contains(name) { continue; } + + let mut reqs = self.required.iter().map(|&r| &*r).collect::>(); + reqs.retain(|n| !matcher.contains(n)); + reqs.dedup(); + return Err(Error::missing_required_argument( + &*self.get_required_from(&self.required[..], Some(matcher)) + .iter() + .fold(String::new(), + |acc, s| acc + &format!("\n {}", Format::Error(s))[..]), + &*self.create_current_usage(matcher), + self.color()) + ); + } + } + } + Ok(()) + } + fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> { debugln!("fn=validate_required;required={:?};", self.required); let c = Colorizer { @@ -1948,7 +1980,7 @@ impl<'a, 'b> Parser<'a, 'b> fn add_defaults(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { macro_rules! add_val { - ($_self:ident, $a:ident, $m:ident) => { + (@default $_self:ident, $a:ident, $m:ident) => { if let Some(ref val) = $a.v.default_val { if $m.get($a.b.name).is_none() { try!($_self.add_val_to_arg($a, OsStr::new(val), $m)); @@ -1983,9 +2015,10 @@ impl<'a, 'b> Parser<'a, 'b> } if done { - continue; + continue; // outer loop (outside macro) } } + add_val!(@default $_self, $a, $m) }; } for o in &self.opts { diff --git a/src/args/any_arg.rs b/src/args/any_arg.rs index 6c2d5983346..c4cb9c776f6 100644 --- a/src/args/any_arg.rs +++ b/src/args/any_arg.rs @@ -15,7 +15,7 @@ pub trait AnyArg<'n, 'e>: std_fmt::Display { fn id(&self) -> usize; fn overrides(&self) -> Option<&[&'e str]>; fn aliases(&self) -> Option>; - fn requires(&self) -> Option<&[&'e str]>; + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]>; fn blacklist(&self) -> Option<&[&'e str]>; fn required_unless(&self) -> Option<&[&'e str]>; fn is_set(&self, ArgSettings) -> bool; diff --git a/src/args/arg.rs b/src/args/arg.rs index 48279eb1d41..6846950c3e1 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -52,7 +52,7 @@ pub struct Arg<'a, 'b> #[doc(hidden)] pub possible_vals: Option>, #[doc(hidden)] - pub requires: Option>, + pub requires: Option, &'a str)>>, #[doc(hidden)] pub groups: Option>, #[doc(hidden)] @@ -1135,9 +1135,139 @@ impl<'a, 'b> Arg<'a, 'b> { /// [override]: ./struct.Arg.html#method.overrides_with pub fn requires(mut self, name: &'a str) -> Self { if let Some(ref mut vec) = self.requires { - vec.push(name); + vec.push((None, name)); + } else { + let mut vec = vec![]; + vec.push((None, name)); + self.requires = Some(vec); + } + self + } + + /// Allows a conditional requirement. The requirement will only become valid if this arg's value + /// equals `val`. + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .requires_if("val", "arg") + /// # ; + /// ``` + /// + /// Setting [`Arg::requires(val, arg)`] requires that the `arg` be used at runtime if the + /// defining argument's value is equal to `val`. If the defining argument is anything other than + /// `val`, the other argument isn't required. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("reqtest") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .requires_if("my.cfg", "other") + /// .long("config")) + /// .arg(Arg::with_name("other")) + /// .get_matches_from_safe(vec![ + /// "reqtest", "--config", "some.cfg" + /// ]); + /// + /// assert!(res.is_ok()); // We didn't use --config=my.cfg, so other wasn't required + /// ``` + /// + /// Setting [`Arg::requires_if(val, arg)`] and setting the value to `val` but *not* supplying + /// `arg` is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("reqtest") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .requires_if("my.cfg", "input") + /// .long("config")) + /// .arg(Arg::with_name("input")) + /// .get_matches_from_safe(vec![ + /// "reqtest", "--config", "my.cfg" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [`Arg::requires(name)`]: ./struct.Arg.html#method.requires + /// [Conflicting]: ./struct.Arg.html#method.conflicts_with + /// [override]: ./struct.Arg.html#method.overrides_with + pub fn requires_if(mut self, val: &'b str, arg: &'a str) -> Self { + if let Some(ref mut vec) = self.requires { + vec.push((Some(val), arg)); + } else { + self.requires = Some(vec![(Some(val), arg)]); + } + self + } + + /// Allows a conditional requirement. The requirement will only become valid if this arg's value + /// equals `val`. + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .requires_if("val", "arg") + /// # ; + /// ``` + /// + /// Setting [`Arg::requires(val, arg)`] requires that the `arg` be used at runtime if the + /// defining argument's value is equal to `val`. If the defining argument is anything other than + /// `val`, the other argument isn't required. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("reqtest") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .requires_if("my.cfg", "other") + /// .long("config")) + /// .arg(Arg::with_name("other")) + /// .get_matches_from_safe(vec![ + /// "reqtest", "--config", "some.cfg" + /// ]); + /// + /// assert!(res.is_ok()); // We didn't use --config=my.cfg, so other wasn't required + /// ``` + /// + /// Setting [`Arg::requires_if(val, arg)`] and setting the value to `val` but *not* supplying + /// `arg` is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("reqtest") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .requires_if("my.cfg", "input") + /// .long("config")) + /// .arg(Arg::with_name("input")) + /// .get_matches_from_safe(vec![ + /// "reqtest", "--config", "my.cfg" + /// ]); + /// + /// assert!(res.is_err()); + /// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + /// ``` + /// [`Arg::requires(name)`]: ./struct.Arg.html#method.requires + /// [Conflicting]: ./struct.Arg.html#method.conflicts_with + /// [override]: ./struct.Arg.html#method.overrides_with + pub fn requires_ifs(mut self, ifs: &[(&'b str, &'a str)]) -> Self { + if let Some(ref mut vec) = self.requires { + for &(val, arg) in ifs { + vec.push((Some(val), arg)); + } } else { - self.requires = Some(vec![name]); + let mut vec = vec![]; + for &(val, arg) in ifs { + vec.push((Some(val), arg)); + } + self.requires = Some(vec); } self } @@ -1207,10 +1337,14 @@ impl<'a, 'b> Arg<'a, 'b> { pub fn requires_all(mut self, names: &[&'a str]) -> Self { if let Some(ref mut vec) = self.requires { for s in names { - vec.push(s); + vec.push((None, s)); } } else { - self.requires = Some(names.into_iter().map(|s| *s).collect::>()); + let mut vec = vec![]; + for s in names { + vec.push((None, *s)); + } + self.requires = Some(vec); } self } @@ -1780,7 +1914,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`ArgGroup`]: ./struct.ArgGroup.html pub fn group(mut self, name: &'a str) -> Self { - if let Some(ref mut vec) = self.requires { + if let Some(ref mut vec) = self.groups { vec.push(name); } else { self.groups = Some(vec![name]); @@ -2422,7 +2556,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// `None`, `arg` only needs to be present. If `val` is set to `"some-val"` then `arg` must be /// present at runtime **and** have the value `val`. /// - /// **NOTE:** This setting is perfectly compatible with [`Arg::default_value`] but slightly + /// **NOTE:** This setting is perfectly compatible with [`Arg::default_value`] but slightly /// different. `Arg::default_value` *only* takes affect when the user has not provided this arg /// at runtime. This setting however only takes affect when the user has not provided a value at /// runtime **and** these other conditions are met as well. If you have set `Arg::default_value` @@ -2506,7 +2640,11 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value /// [`Arg::default_value`]: ./struct.Arg.html#method.default_value - pub fn default_value_if(mut self, arg: &'a str, val: Option<&'b str>, default: &'b str) -> Self { + pub fn default_value_if(mut self, + arg: &'a str, + val: Option<&'b str>, + default: &'b str) + -> Self { self.setb(ArgSettings::TakesValue); if let Some(ref mut vm) = self.default_vals_ifs { let l = vm.len(); diff --git a/src/args/arg_builder/base.rs b/src/args/arg_builder/base.rs index 3c6a8a92f7f..8e5682441c6 100644 --- a/src/args/arg_builder/base.rs +++ b/src/args/arg_builder/base.rs @@ -13,7 +13,7 @@ pub struct Base<'a, 'b> pub r_unless: Option>, pub overrides: Option>, pub groups: Option>, - pub requires: Option>, + pub requires: Option, &'a str)>>, } impl<'n, 'e> Default for Base<'n, 'e> { diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index 8f8ac6bdd10..a56e2ecb5d2 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -52,7 +52,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> { fn id(&self) -> usize { self.b.id } fn kind(&self) -> ArgKind { ArgKind::Flag } fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } - fn requires(&self) -> Option<&[&'e str]> { self.b.requires.as_ref().map(|o| &o[..]) } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { self.b.requires.as_ref().map(|o| &o[..]) } fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } fn required_unless(&self) -> Option<&[&'e str]> { None } fn is_set(&self, s: ArgSettings) -> bool { self.b.settings.is_set(s) } diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 787620dc560..c4c7e963611 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -23,7 +23,7 @@ pub struct OptBuilder<'n, 'e> impl<'n, 'e> OptBuilder<'n, 'e> { pub fn new(name: &'n str) -> Self { OptBuilder { b: Base::new(name), ..Default::default() } } - pub fn from_arg(a: &Arg<'n, 'e>, reqs: &mut Vec<&'e str>) -> Self { + pub fn from_arg(a: &Arg<'n, 'e>, reqs: &mut Vec<&'n str>) -> Self { // No need to check for .index() as that is handled above let ob = OptBuilder { b: Base::from(a), @@ -33,7 +33,9 @@ impl<'n, 'e> OptBuilder<'n, 'e> { // If the arg is required, add all it's requirements to master required list if a.is_set(ArgSettings::Required) { if let Some(ref areqs) = a.requires { - reqs.extend_from_slice(areqs); + for r in areqs.iter().filter(|r| r.0.is_none()) { + reqs.push(r.1); + } } } ob @@ -94,7 +96,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { fn id(&self) -> usize { self.b.id } fn kind(&self) -> ArgKind { ArgKind::Opt } fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } - fn requires(&self) -> Option<&[&'e str]> { self.b.requires.as_ref().map(|o| &o[..]) } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { self.b.requires.as_ref().map(|o| &o[..]) } fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } fn required_unless(&self) -> Option<&[&'e str]> { self.b.r_unless.as_ref().map(|o| &o[..]) } fn val_names(&self) -> Option<&VecMap<&'e str>> { self.v.val_names.as_ref() } diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index 9ee65ec996d..b41604e5639 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -31,7 +31,7 @@ impl<'n, 'e> PosBuilder<'n, 'e> { } } - pub fn from_arg(a: &Arg<'n, 'e>, idx: u64, reqs: &mut Vec<&'e str>) -> Self { + pub fn from_arg(a: &Arg<'n, 'e>, idx: u64, reqs: &mut Vec<&'n str>) -> Self { // Create the Positional Argument Builder with each HashSet = None to only // allocate // those that require it @@ -47,7 +47,9 @@ impl<'n, 'e> PosBuilder<'n, 'e> { // If the arg is required, add all it's requirements to master required list if a.is_set(ArgSettings::Required) { if let Some(ref areqs) = a.requires { - reqs.extend_from_slice(areqs); + for name in areqs.iter().filter(|&&(val,_)|val.is_none()).map(|&(_, name)| name) { + reqs.push(name); + } } } pb @@ -98,7 +100,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> { fn id(&self) -> usize { self.b.id } fn kind(&self) -> ArgKind { ArgKind::Pos } fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } - fn requires(&self) -> Option<&[&'e str]> { self.b.requires.as_ref().map(|o| &o[..]) } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { self.b.requires.as_ref().map(|o| &o[..]) } fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } fn required_unless(&self) -> Option<&[&'e str]> { self.b.r_unless.as_ref().map(|o| &o[..]) } fn val_names(&self) -> Option<&VecMap<&'e str>> { self.v.val_names.as_ref() } diff --git a/src/macros.rs b/src/macros.rs index d734bade05a..adfda44d1fb 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -699,7 +699,7 @@ macro_rules! vec_remove_all { ($vec:expr, $to_rem:expr) => { debugln!("macro=vec_remove_all!;to_rem={:?}", $to_rem); for i in (0 .. $vec.len()).rev() { - let should_remove = $to_rem.contains(&$vec[i]); + let should_remove = $to_rem.any(|name| name == &$vec[i]); if should_remove { $vec.swap_remove(i); } } };