mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 22:32:33 +00:00
feat: Implement Arg::required_if_eq_all
This commit is contained in:
parent
c9e875e036
commit
701a4610b3
5 changed files with 221 additions and 2 deletions
|
@ -144,6 +144,15 @@ pub(crate) fn assert_app(app: &App) {
|
|||
);
|
||||
}
|
||||
|
||||
for req in &arg.r_ifs_all {
|
||||
assert!(
|
||||
app.id_exists(&req.0),
|
||||
"Argument or group '{:?}' specified in 'required_if_eq_all' for '{}' does not exist",
|
||||
req.0,
|
||||
arg.name
|
||||
);
|
||||
}
|
||||
|
||||
for req in &arg.r_unless {
|
||||
assert!(
|
||||
app.id_exists(req),
|
||||
|
|
|
@ -91,6 +91,7 @@ pub struct Arg<'help> {
|
|||
pub(crate) groups: Vec<Id>,
|
||||
pub(crate) requires: Vec<(Option<&'help str>, Id)>,
|
||||
pub(crate) r_ifs: Vec<(Id, &'help str)>,
|
||||
pub(crate) r_ifs_all: Vec<(Id, &'help str)>,
|
||||
pub(crate) r_unless: Vec<Id>,
|
||||
pub(crate) short: Option<char>,
|
||||
pub(crate) long: Option<&'help str>,
|
||||
|
@ -1436,7 +1437,7 @@ impl<'help> Arg<'help> {
|
|||
|
||||
/// Allows specifying that this 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`.
|
||||
/// if one of the specified `arg`'s value equals its corresponding `val`.
|
||||
///
|
||||
/// **NOTE:** If using YAML the values should be laid out as follows
|
||||
///
|
||||
|
@ -1520,6 +1521,90 @@ impl<'help> Arg<'help> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Allows specifying that this 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 every one of the specified `arg`'s value equals its corresponding `val`.
|
||||
///
|
||||
/// **NOTE:** If using YAML the values should be laid out as follows
|
||||
///
|
||||
/// ```yaml
|
||||
/// required_if_eq_all:
|
||||
/// - [arg, val]
|
||||
/// - [arg2, val2]
|
||||
/// ```
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::Arg;
|
||||
/// Arg::new("config")
|
||||
/// .required_if_eq_all(&[
|
||||
/// ("extra", "val"),
|
||||
/// ("option", "spec")
|
||||
/// ])
|
||||
/// # ;
|
||||
/// ```
|
||||
///
|
||||
/// Setting `Arg::required_if_eq_all(&[(arg, val)])` makes this arg required if all of the `arg`s
|
||||
/// are used at runtime and every value is equal to its corresponding `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("prog")
|
||||
/// .arg(Arg::new("cfg")
|
||||
/// .required_if_eq_all(&[
|
||||
/// ("extra", "val"),
|
||||
/// ("option", "spec")
|
||||
/// ])
|
||||
/// .takes_value(true)
|
||||
/// .long("config"))
|
||||
/// .arg(Arg::new("extra")
|
||||
/// .takes_value(true)
|
||||
/// .long("extra"))
|
||||
/// .arg(Arg::new("option")
|
||||
/// .takes_value(true)
|
||||
/// .long("option"))
|
||||
/// .try_get_matches_from(vec![
|
||||
/// "prog", "--option", "spec"
|
||||
/// ]);
|
||||
///
|
||||
/// assert!(res.is_ok()); // We didn't use --option=spec --extra=val so "cfg" isn't required
|
||||
/// ```
|
||||
///
|
||||
/// Setting `Arg::required_if_eq_all(&[(arg, val)])` and having all of the `arg`s used with its
|
||||
/// value of `val` but *not* using this arg is an error.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg, ErrorKind};
|
||||
/// let res = App::new("prog")
|
||||
/// .arg(Arg::new("cfg")
|
||||
/// .required_if_eq_all(&[
|
||||
/// ("extra", "val"),
|
||||
/// ("option", "spec")
|
||||
/// ])
|
||||
/// .takes_value(true)
|
||||
/// .long("config"))
|
||||
/// .arg(Arg::new("extra")
|
||||
/// .takes_value(true)
|
||||
/// .long("extra"))
|
||||
/// .arg(Arg::new("option")
|
||||
/// .takes_value(true)
|
||||
/// .long("option"))
|
||||
/// .try_get_matches_from(vec![
|
||||
/// "prog", "--extra", "val", "--option", "spec"
|
||||
/// ]);
|
||||
///
|
||||
/// assert!(res.is_err());
|
||||
/// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument);
|
||||
/// ```
|
||||
/// [required]: ./struct.Arg.html#method.required
|
||||
pub fn required_if_eq_all<T: Key>(mut self, ifs: &[(T, &'help str)]) -> Self {
|
||||
self.r_ifs_all
|
||||
.extend(ifs.iter().map(|(id, val)| (Id::from_ref(id), *val)));
|
||||
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.
|
||||
///
|
||||
|
@ -4536,7 +4621,8 @@ impl<'help> From<&'help Yaml> for Arg<'help> {
|
|||
"long_help" => yaml_to_str!(a, v, long_about),
|
||||
"required" => yaml_to_bool!(a, v, required),
|
||||
"required_if_eq" => yaml_tuple2!(a, v, required_if_eq),
|
||||
"required_if_eq_any" => yaml_tuple2!(a, v, required_if_eq),
|
||||
"required_if_eq_any" => yaml_array_tuple2!(a, v, required_if_eq_any),
|
||||
"required_if_eq_all" => yaml_array_tuple2!(a, v, required_if_eq_all),
|
||||
"takes_value" => yaml_to_bool!(a, v, takes_value),
|
||||
"index" => yaml_to_usize!(a, v, index),
|
||||
"global" => yaml_to_bool!(a, v, global),
|
||||
|
|
|
@ -17,6 +17,25 @@ macro_rules! yaml_tuple2 {
|
|||
}};
|
||||
}
|
||||
|
||||
#[cfg(feature = "yaml")]
|
||||
macro_rules! yaml_array_tuple2 {
|
||||
($a:ident, $v:ident, $c:ident) => {{
|
||||
if let Some(vec) = $v.as_vec() {
|
||||
for ys in vec {
|
||||
if let Some(tup) = ys.as_vec() {
|
||||
debug_assert_eq!(2, tup.len());
|
||||
$a = $a.$c(&[(yaml_str!(tup[0]), yaml_str!(tup[1]))]);
|
||||
} else {
|
||||
panic!("Failed to convert YAML value to vec");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Failed to convert YAML value to vec");
|
||||
}
|
||||
$a
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(feature = "yaml")]
|
||||
macro_rules! yaml_tuple3 {
|
||||
($a:ident, $v:ident, $c:ident) => {{
|
||||
|
|
|
@ -568,6 +568,23 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut match_all = true;
|
||||
for (other, val) in &a.r_ifs_all {
|
||||
if let Some(ma) = matcher.get(other) {
|
||||
if !ma.contains_val(val) {
|
||||
match_all = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
match_all = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if match_all && !a.r_ifs_all.is_empty() && !matcher.contains(&a.id) {
|
||||
return self.missing_required_error(matcher, vec![a.id.clone()]);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -533,6 +533,94 @@ fn required_if_val_present_fail() {
|
|||
assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_if_all_values_present_pass() {
|
||||
let res = App::new("ri")
|
||||
.arg(
|
||||
Arg::new("cfg")
|
||||
.required_if_eq_all(&[("extra", "val"), ("option", "spec")])
|
||||
.takes_value(true)
|
||||
.long("config"),
|
||||
)
|
||||
.arg(Arg::new("extra").takes_value(true).long("extra"))
|
||||
.arg(Arg::new("option").takes_value(true).long("option"))
|
||||
.try_get_matches_from(vec![
|
||||
"ri", "--extra", "val", "--option", "spec", "--config", "my.cfg",
|
||||
]);
|
||||
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_if_some_values_present_pass() {
|
||||
let res = App::new("ri")
|
||||
.arg(
|
||||
Arg::new("cfg")
|
||||
.required_if_eq_all(&[("extra", "val"), ("option", "spec")])
|
||||
.takes_value(true)
|
||||
.long("config"),
|
||||
)
|
||||
.arg(Arg::new("extra").takes_value(true).long("extra"))
|
||||
.arg(Arg::new("option").takes_value(true).long("option"))
|
||||
.try_get_matches_from(vec!["ri", "--extra", "val"]);
|
||||
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_if_all_values_present_fail() {
|
||||
let res = App::new("ri")
|
||||
.arg(
|
||||
Arg::new("cfg")
|
||||
.required_if_eq_all(&[("extra", "val"), ("option", "spec")])
|
||||
.takes_value(true)
|
||||
.long("config"),
|
||||
)
|
||||
.arg(Arg::new("extra").takes_value(true).long("extra"))
|
||||
.arg(Arg::new("option").takes_value(true).long("option"))
|
||||
.try_get_matches_from(vec!["ri", "--extra", "val", "--option", "spec"]);
|
||||
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_if_any_all_values_present_pass() {
|
||||
let res = App::new("ri")
|
||||
.arg(
|
||||
Arg::new("cfg")
|
||||
.required_if_eq_all(&[("extra", "val"), ("option", "spec")])
|
||||
.required_if_eq_any(&[("extra", "val2"), ("option", "spec2")])
|
||||
.takes_value(true)
|
||||
.long("config"),
|
||||
)
|
||||
.arg(Arg::new("extra").takes_value(true).long("extra"))
|
||||
.arg(Arg::new("option").takes_value(true).long("option"))
|
||||
.try_get_matches_from(vec![
|
||||
"ri", "--extra", "val", "--option", "spec", "--config", "my.cfg",
|
||||
]);
|
||||
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_if_any_all_values_present_fail() {
|
||||
let res = App::new("ri")
|
||||
.arg(
|
||||
Arg::new("cfg")
|
||||
.required_if_eq_all(&[("extra", "val"), ("option", "spec")])
|
||||
.required_if_eq_any(&[("extra", "val2"), ("option", "spec2")])
|
||||
.takes_value(true)
|
||||
.long("config"),
|
||||
)
|
||||
.arg(Arg::new("extra").takes_value(true).long("extra"))
|
||||
.arg(Arg::new("option").takes_value(true).long("option"))
|
||||
.try_get_matches_from(vec!["ri", "--extra", "val", "--option", "spec"]);
|
||||
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_correct_required_args() {
|
||||
let app = App::new("Test app")
|
||||
|
|
Loading…
Reference in a new issue