Skip to content

Commit

Permalink
feat(Conditionally Required): adds the ability for an arg to be condi…
Browse files Browse the repository at this point in the history
…tionally 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
  • Loading branch information
kbknapp committed Dec 29, 2016
1 parent 4ef0910 commit ee9cfdd
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 22 deletions.
55 changes: 36 additions & 19 deletions src/app/parser.rs
Expand Up @@ -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<char>,
pub long_list: Vec<&'b str>,
blacklist: Vec<&'b str>,
Expand Down Expand Up @@ -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![],
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -1622,7 +1629,11 @@ impl<'a, 'b> Parser<'a, 'b>
Ok(())
}

fn validate_arg_num_vals<A>(&self, a: &A, ma: &MatchedArg, matcher: &ArgMatcher) -> ClapResult<()>
fn validate_arg_num_vals<A>(&self,
a: &A,
ma: &MatchedArg,
matcher: &ArgMatcher)
-> ClapResult<()>
where A: AnyArg<'a, 'b> + Display
{
debugln!("fn=validate_arg_num_vals;");
Expand Down Expand Up @@ -1685,14 +1696,20 @@ impl<'a, 'b> Parser<'a, 'b>
Ok(())
}

fn validate_arg_requires<A>(&self, a: &A, ma: &MatchedArg, matcher: &ArgMatcher) -> ClapResult<()>
fn validate_arg_requires<A>(&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::<Vec<_>>();
reqs.retain(|n| !matcher.contains(n));
Expand Down Expand Up @@ -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::<Vec<_>>();
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(())
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(),
}
}
}
163 changes: 160 additions & 3 deletions src/args/arg.rs
Expand Up @@ -79,6 +79,8 @@ pub struct Arg<'a, 'b>
pub disp_ord: usize,
#[doc(hidden)]
pub r_unless: Option<Vec<&'a str>>,
#[doc(hidden)]
pub r_ifs: Option<Vec<(&'a str, &'b str)>>,
}

impl<'a, 'b> Default for Arg<'a, 'b> {
Expand Down Expand Up @@ -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,
}
}
}
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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(),
}
}
}
Expand Down Expand Up @@ -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(),
}
}
}

0 comments on commit ee9cfdd

Please sign in to comment.