From ee9cfddf345a6b5ae2af42ba72aa5c89e2ca7f59 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Wed, 28 Dec 2016 20:32:47 -0500 Subject: [PATCH] feat(Conditionally Required): adds the ability for an arg to be conditionally required An arg can now be conditionally required (i.e. it's only required if arg A is used with a value of V). For example: ```rust let res = App::new("ri") .arg(Arg::with_name("cfg") .required_if("extra", "val") .takes_value(true) .long("config")) .arg(Arg::with_name("extra") .takes_value(true) .long("extra")) .get_matches_from_safe(vec![ "ri", "--extra", "val" ]); assert!(res.is_err()); assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); ``` Relates to #764 --- src/app/parser.rs | 55 ++++++++++------ src/args/arg.rs | 163 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 196 insertions(+), 22 deletions(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index bcb5b33e8f4..bd494806081 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -38,6 +38,7 @@ pub struct Parser<'a, 'b> where 'a: 'b { required: Vec<&'a str>, + r_ifs: Vec<(&'a str, &'b str, &'a str)>, pub short_list: Vec, pub long_list: Vec<&'b str>, blacklist: Vec<&'b str>, @@ -73,6 +74,7 @@ impl<'a, 'b> Default for Parser<'a, 'b> { help_short: None, version_short: None, required: vec![], + r_ifs: vec![], short_list: vec![], long_list: vec![], blacklist: vec![], @@ -141,6 +143,11 @@ impl<'a, 'b> Parser<'a, 'b> self.opts.iter().any(|o| o.b.name == a.name) || self.positionals.values().any(|p| p.b.name == a.name)), format!("Non-unique argument name: {} is already in use", a.name)); + if let Some(ref r_ifs) = a.r_ifs { + for &(arg, val) in r_ifs { + self.r_ifs.push((arg, val, a.name)); + } + } if let Some(ref grps) = a.groups { for g in grps { let ag = self.groups.entry(g).or_insert_with(|| ArgGroup::with_name(g)); @@ -1622,7 +1629,11 @@ impl<'a, 'b> Parser<'a, 'b> Ok(()) } - fn validate_arg_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_arg_num_vals;"); @@ -1685,14 +1696,20 @@ impl<'a, 'b> Parser<'a, 'b> Ok(()) } - fn validate_arg_requires(&self, a: &A, ma: &MatchedArg, matcher: &ArgMatcher) -> ClapResult<()> + 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; } + if matcher.contains(name) { + continue; + } let mut reqs = self.required.iter().map(|&r| &*r).collect::>(); reqs.retain(|n| !matcher.contains(n)); @@ -1745,22 +1762,20 @@ impl<'a, 'b> Parser<'a, 'b> continue 'outer; } } - let err = - if self.settings.is_set(AppSettings::ArgRequiredElseHelp) && matcher.is_empty() { - self._help().unwrap_err() - } else { - let mut reqs = self.required.iter().map(|&r| &*r).collect::>(); - reqs.retain(|n| !matcher.contains(n)); - reqs.dedup(); - Error::missing_required_argument( - &*self.get_required_from(&*reqs, Some(matcher)) - .iter() - .fold(String::new(), - |acc, s| acc + &format!("\n {}", c.error(s))[..]), - &*self.create_current_usage(matcher), - self.color()) - }; - return Err(err); + return Err(err()); + } + + // Validate the conditionally required args + for &(a, v, r) in &self.r_ifs { + if let Some(ref ma) = matcher.get(a) { + for val in ma.vals.values() { + if v == val { + if matcher.get(r).is_none() { + return Err(err()); + } + } + } + } } Ok(()) } @@ -2021,6 +2036,7 @@ impl<'a, 'b> Parser<'a, 'b> add_val!(@default $_self, $a, $m) }; } + for o in &self.opts { add_vals_ifs!(self, o, matcher); add_val!(self, o, matcher); @@ -2119,6 +2135,7 @@ impl<'a, 'b> Clone for Parser<'a, 'b> trailing_vals: self.trailing_vals, id: self.id, valid_neg_num: self.valid_neg_num, + r_ifs: self.r_ifs.clone(), } } } diff --git a/src/args/arg.rs b/src/args/arg.rs index 1bdc40b1c8f..4d91798170b 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -79,6 +79,8 @@ pub struct Arg<'a, 'b> pub disp_ord: usize, #[doc(hidden)] pub r_unless: Option>, + #[doc(hidden)] + pub r_ifs: Option>, } impl<'a, 'b> Default for Arg<'a, 'b> { @@ -106,6 +108,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> { default_vals_ifs: None, disp_ord: 999, r_unless: None, + r_ifs: None, } } } @@ -1156,7 +1159,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// # ; /// ``` /// - /// Setting [`Arg::requires(val, arg)`] requires that the `arg` be used at runtime if the + /// Setting [`Arg::requires_if(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. /// @@ -1221,7 +1224,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// /// Setting [`Arg::requires_ifs(&["val", "arg"])`] requires that the `arg` be used at runtime if the - /// defining argument's value is equal to `val`. If the defining argument's value is anything other + /// defining argument's value is equal to `val`. If the defining argument's value is anything other /// than `val`, `arg` isn't required. /// /// ```rust @@ -1263,6 +1266,158 @@ impl<'a, 'b> Arg<'a, 'b> { self } + /// Allows specifying that an argument is [required] conditionally. The requirement will only + /// become valid if the specified `arg`'s value equals `val`. + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .required_if("other_arg", "value") + /// # ; + /// ``` + /// + /// Setting [`Arg::required_if(arg, val)`] makes this arg required if the `arg` is used at + /// runtime and it's value is equal to `val`. If the `arg`'s value is anything other than `val`, + /// this argument isn't required. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("reqtest") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .required_if("other", "special") + /// .long("config")) + /// .arg(Arg::with_name("other") + /// .long("other") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "reqtest", "--other", "not-special" + /// ]); + /// + /// assert!(res.is_ok()); // We didn't use --other=special, so "cfg" wasn't required + /// ``` + /// + /// Setting [`Arg::required_if(arg, val)`] and having `arg` used with a vaue of `val` but *not* + /// using this arg is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("reqtest") + /// .arg(Arg::with_name("cfg") + /// .takes_value(true) + /// .required_if("other", "special") + /// .long("config")) + /// .arg(Arg::with_name("other") + /// .long("other") + /// .takes_value(true)) + /// .get_matches_from_safe(vec![ + /// "reqtest", "--other", "special" + /// ]); + /// + /// 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 + /// [required]: ./struct.Arg.html#method.required + pub fn required_if(mut self, arg: &'a str, val: &'b str) -> Self { + if let Some(ref mut vec) = self.r_ifs { + vec.push((arg, val)); + } else { + self.r_ifs = Some(vec![(arg, val)]); + } + self + } + + /// Allows specifying that an argument is [required] based on multiple conditions. The + /// conditions are set up in a `(arg, val)` style tuple. The requirement will only become valid + /// if one of the specified `arg`'s value equals it's corresponding `val`. + /// + /// # Examples + /// + /// ```rust + /// # use clap::Arg; + /// Arg::with_name("config") + /// .required_ifs(&[ + /// ("extra", "val"), + /// ("option", "spec") + /// ]) + /// # ; + /// ``` + /// + /// Setting [`Arg::required_ifs(&[(arg, val)])`] makes this arg required if any of the `arg`s + /// are used at runtime and it's corresponding value is equal to `val`. If the `arg`'s value is + /// anything other than `val`, this argument isn't required. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let res = App::new("ri") + /// .arg(Arg::with_name("cfg") + /// .required_ifs(&[ + /// ("extra", "val"), + /// ("option", "spec") + /// ]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("extra") + /// .takes_value(true) + /// .long("extra")) + /// .arg(Arg::with_name("option") + /// .takes_value(true) + /// .long("option")) + /// .get_matches_from_safe(vec![ + /// "ri", "--option", "other" + /// ]); + /// + /// assert!(res.is_ok()); // We didn't use --option=spec, or --extra=val so "cfg" isn't required + /// ``` + /// + /// Setting [`Arg::required_ifs(&[(arg, val)])`] and having any of the `arg`s used with it's + /// vaue of `val` but *not* using this arg is an error. + /// + /// ```rust + /// # use clap::{App, Arg, ErrorKind}; + /// let res = App::new("ri") + /// .arg(Arg::with_name("cfg") + /// .required_ifs(&[ + /// ("extra", "val"), + /// ("option", "spec") + /// ]) + /// .takes_value(true) + /// .long("config")) + /// .arg(Arg::with_name("extra") + /// .takes_value(true) + /// .long("extra")) + /// .arg(Arg::with_name("option") + /// .takes_value(true) + /// .long("option")) + /// .get_matches_from_safe(vec![ + /// "ri", "--option", "spec" + /// ]); + /// + /// 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 + /// [required]: ./struct.Arg.html#method.required + pub fn required_ifs(mut self, ifs: &[(&'a str, &'b str)]) -> Self { + if let Some(ref mut vec) = self.r_ifs { + for r_if in ifs { + vec.push((r_if.0, r_if.1)); + } + } else { + let mut vec = vec![]; + for r_if in ifs { + vec.push((r_if.0, r_if.1)); + } + self.r_ifs = Some(vec); + } + self + } + /// Sets multiple arguments by names that are required when this one is present I.e. when /// using this argument, the following arguments *must* be present. /// @@ -1331,7 +1486,7 @@ impl<'a, 'b> Arg<'a, 'b> { vec.push((None, s)); } } else { - let mut vec = vec![]; + let mut vec = vec![]; for s in names { vec.push((None, *s)); } @@ -2908,6 +3063,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> { default_vals_ifs: a.default_vals_ifs.clone(), disp_ord: a.disp_ord, r_unless: a.r_unless.clone(), + r_ifs: a.r_ifs.clone(), } } } @@ -2937,6 +3093,7 @@ impl<'a, 'b> Clone for Arg<'a, 'b> { default_vals_ifs: self.default_vals_ifs.clone(), disp_ord: self.disp_ord, r_unless: self.r_unless.clone(), + r_ifs: self.r_ifs.clone(), } } }