mirror of
https://github.com/clap-rs/clap
synced 2025-03-04 23:37:32 +00:00
feat(Required): adds allowing args that are required unless certain args are present
Adds three new methods of `Arg` which allow for specifying three new types of rules. * `Arg::required_unless` Allows saying a particular arg is only required if a specific other arg *isn't* present. * `Arg::required_unless_all` Allows saying a particular arg is only required so long as *all* the following args aren't present * `Arg::required_unless_one` Allows saying a particular arg is required unless at least one of the following args is present.
This commit is contained in:
parent
cb708093a7
commit
6987f37e71
8 changed files with 169 additions and 14 deletions
|
@ -936,6 +936,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> {
|
|||
fn overrides(&self) -> Option<&[&'e str]> { None }
|
||||
fn requires(&self) -> Option<&[&'e 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 }
|
||||
fn is_set(&self, _: ArgSettings) -> bool { false }
|
||||
fn set(&mut self, _: ArgSettings) {
|
||||
|
|
|
@ -1245,11 +1245,11 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
continue 'outer;
|
||||
}
|
||||
if let Some(a) = self.flags.iter().filter(|f| &f.name == name).next() {
|
||||
if self._validate_blacklist_required(a, matcher) { continue 'outer; }
|
||||
if self.is_missing_required_ok(a, matcher) { continue 'outer; }
|
||||
} else if let Some(a) = self.opts.iter().filter(|o| &o.name == name).next() {
|
||||
if self._validate_blacklist_required(a, matcher) { continue 'outer; }
|
||||
if self.is_missing_required_ok(a, matcher) { continue 'outer; }
|
||||
} else if let Some(a) = self.positionals.values().filter(|p| &p.name == name).next() {
|
||||
if self._validate_blacklist_required(a, matcher) { continue 'outer; }
|
||||
if self.is_missing_required_ok(a, matcher) { continue 'outer; }
|
||||
}
|
||||
let err = if self.settings.is_set(AppSettings::ArgRequiredElseHelp) && matcher.is_empty() {
|
||||
self._help().unwrap_err()
|
||||
|
@ -1266,7 +1266,7 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn _validate_blacklist_required<A>(&self, a: &A, matcher: &ArgMatcher) -> bool where A: AnyArg<'a, 'b> {
|
||||
fn is_missing_required_ok<A>(&self, a: &A, matcher: &ArgMatcher) -> bool where A: AnyArg<'a, 'b> {
|
||||
if let Some(bl) = a.blacklist() {
|
||||
for n in bl.iter() {
|
||||
if matcher.contains(n)
|
||||
|
@ -1276,6 +1276,20 @@ impl<'a, 'b> Parser<'a, 'b> where 'a: 'b {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
} else if let Some(ru) = a.required_unless() {
|
||||
for n in ru.iter() {
|
||||
if matcher.contains(n)
|
||||
|| self.groups
|
||||
.get(n)
|
||||
.map_or(false, |g| g.args.iter().any(|an| matcher.contains(an))) {
|
||||
if !a.is_set(ArgSettings::RequiredUnlessAll) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ pub trait AnyArg<'n, 'e> {
|
|||
fn overrides(&self) -> Option<&[&'e str]>;
|
||||
fn requires(&self) -> Option<&[&'e str]>;
|
||||
fn blacklist(&self) -> Option<&[&'e str]>;
|
||||
fn required_unless(&self) -> Option<&[&'e str]>;
|
||||
fn is_set(&self, ArgSettings) -> bool;
|
||||
fn set(&mut self, ArgSettings);
|
||||
fn has_switch(&self) -> bool;
|
||||
|
|
118
src/args/arg.rs
118
src/args/arg.rs
|
@ -70,6 +70,8 @@ pub struct Arg<'a, 'b> where 'a: 'b {
|
|||
pub default_val: Option<&'a str>,
|
||||
#[doc(hidden)]
|
||||
pub disp_ord: usize,
|
||||
#[doc(hidden)]
|
||||
pub r_unless: Option<Vec<&'a str>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Default for Arg<'a, 'b> {
|
||||
|
@ -94,6 +96,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> {
|
|||
val_delim: Some(','),
|
||||
default_val: None,
|
||||
disp_ord: 999,
|
||||
r_unless: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +160,7 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
"value_name" => a.value_name(v.as_str().unwrap()),
|
||||
"use_delimiter" => a.use_delimiter(v.as_bool().unwrap()),
|
||||
"value_delimiter" => a.value_delimiter(v.as_str().unwrap()),
|
||||
"required_unless" => a.required_unless(v.as_str().unwrap()),
|
||||
"display_order" => a.display_order(v.as_i64().unwrap() as usize),
|
||||
"value_names" => {
|
||||
for ys in v.as_vec().unwrap() {
|
||||
|
@ -198,6 +202,23 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
}
|
||||
a
|
||||
}
|
||||
"required_unless_one" => {
|
||||
for ys in v.as_vec().unwrap() {
|
||||
if let Some(s) = ys.as_str() {
|
||||
a = a.required_unless(s);
|
||||
}
|
||||
}
|
||||
a
|
||||
}
|
||||
"required_unless_all" => {
|
||||
for ys in v.as_vec().unwrap() {
|
||||
if let Some(s) = ys.as_str() {
|
||||
a = a.required_unless(s);
|
||||
}
|
||||
}
|
||||
a.setb(ArgSettings::RequiredUnlessAll);
|
||||
a
|
||||
}
|
||||
s => panic!("Unknown Arg setting '{}' in YAML file for arg '{}'",
|
||||
s,
|
||||
name_str),
|
||||
|
@ -520,6 +541,101 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
if r { self.set(ArgSettings::Required) } else { self.unset(ArgSettings::Required) }
|
||||
}
|
||||
|
||||
pub fn required_unless(mut self, name: &'a str) -> Self {
|
||||
if let Some(ref mut vec) = self.r_unless {
|
||||
vec.push(name);
|
||||
} else {
|
||||
self.r_unless = Some(vec![name]);
|
||||
}
|
||||
self.required(true)
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
/// Sets args that override this arg's required setting. (i.e. this arg will be required unless
|
||||
/// all these other argument are present).
|
||||
///
|
||||
/// **NOTE:** If you wish for the this argument to only be required if *one of* these args are
|
||||
/// present see `Arg::required_unless_one`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::Arg;
|
||||
/// Arg::with_name("config")
|
||||
/// .required_unless_all(&["cfg", "dbg"])
|
||||
/// # ;
|
||||
/// ```
|
||||
///
|
||||
/// Setting `required_unless_all(names)` requires that the argument be used at runtime *unless*
|
||||
/// *all* the args in `names` are present. In the following example, the required argument is
|
||||
/// *not* provided, but it's not an error because all the `unless` args have been supplied.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let res = App::new("unlessall")
|
||||
/// .arg(Arg::with_name("cfg")
|
||||
/// .required_unless_all(&["dbg", "infile"])
|
||||
/// .takes_value(true)
|
||||
/// .long("config"))
|
||||
/// .arg(Arg::with_name("dbg")
|
||||
/// .long("debug"))
|
||||
/// .arg(Arg::with_name("infile")
|
||||
/// .short("i")
|
||||
/// .takes_value(true))
|
||||
/// .get_matches_from_safe(vec![
|
||||
/// "unlessall", "--debug", "-i", "file"
|
||||
/// ]);
|
||||
///
|
||||
/// assert!(res.is_ok());
|
||||
/// ```
|
||||
///
|
||||
/// Setting `required_unless_all(names)` and *not* supplying *all* of `names` or this arg is an
|
||||
/// error.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg, ErrorKind};
|
||||
/// let res = App::new("unlessall")
|
||||
/// .arg(Arg::with_name("cfg")
|
||||
/// .required_unless_all(&["dbg", "infile"])
|
||||
/// .takes_value(true)
|
||||
/// .long("config"))
|
||||
/// .arg(Arg::with_name("dbg")
|
||||
/// .long("debug"))
|
||||
/// .arg(Arg::with_name("infile")
|
||||
/// .short("i")
|
||||
/// .takes_value(true))
|
||||
/// .get_matches_from_safe(vec![
|
||||
/// "unlessall", "--debug"
|
||||
/// ]);
|
||||
///
|
||||
/// assert!(res.is_err());
|
||||
/// assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument);
|
||||
/// ```
|
||||
>>>>>>> 27cf173... fixup! wip: adding required_unless
|
||||
pub fn required_unless_all(mut self, names: &[&'a str]) -> Self {
|
||||
if let Some(ref mut vec) = self.r_unless {
|
||||
for s in names {
|
||||
vec.push(s);
|
||||
}
|
||||
} else {
|
||||
self.r_unless = Some(names.iter().map(|s| *s).collect::<Vec<_>>());
|
||||
}
|
||||
self.setb(ArgSettings::RequiredUnlessAll);
|
||||
self.required(true)
|
||||
}
|
||||
|
||||
pub fn required_unless_one(mut self, names: &[&'a str]) -> Self {
|
||||
if let Some(ref mut vec) = self.r_unless {
|
||||
for s in names {
|
||||
vec.push(s);
|
||||
}
|
||||
} else {
|
||||
self.r_unless = Some(names.iter().map(|s| *s).collect::<Vec<_>>());
|
||||
}
|
||||
self.required(true)
|
||||
}
|
||||
|
||||
/// Sets a conflicting argument by name. I.e. when using this argument,
|
||||
/// the following argument can't be present and vice versa.
|
||||
///
|
||||
|
@ -1908,6 +2024,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>>
|
|||
val_delim: a.val_delim,
|
||||
default_val: a.default_val,
|
||||
disp_ord: a.disp_ord,
|
||||
r_unless: a.r_unless.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1934,6 +2051,7 @@ impl<'a, 'b> Clone for Arg<'a, 'b> {
|
|||
val_delim: self.val_delim,
|
||||
default_val: self.default_val,
|
||||
disp_ord: self.disp_ord,
|
||||
r_unless: self.r_unless.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
|
|||
fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) }
|
||||
fn requires(&self) -> Option<&[&'e str]> { self.requires.as_ref().map(|o| &o[..]) }
|
||||
fn blacklist(&self) -> Option<&[&'e str]> { self.blacklist.as_ref().map(|o| &o[..]) }
|
||||
fn required_unless(&self) -> Option<&[&'e str]> { None }
|
||||
fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) }
|
||||
fn has_switch(&self) -> bool { true }
|
||||
fn takes_value(&self) -> bool { false }
|
||||
|
|
|
@ -27,6 +27,7 @@ pub struct OptBuilder<'n, 'e> {
|
|||
pub val_delim: Option<char>,
|
||||
pub default_val: Option<&'n str>,
|
||||
pub disp_ord: usize,
|
||||
pub r_unless: Option<Vec<&'e str>>,
|
||||
}
|
||||
|
||||
impl<'n, 'e> Default for OptBuilder<'n, 'e> {
|
||||
|
@ -49,6 +50,7 @@ impl<'n, 'e> Default for OptBuilder<'n, 'e> {
|
|||
val_delim: Some(','),
|
||||
default_val: None,
|
||||
disp_ord: 999,
|
||||
r_unless: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +86,7 @@ impl<'n, 'e> OptBuilder<'n, 'e> {
|
|||
settings: a.settings,
|
||||
default_val: a.default_val,
|
||||
disp_ord: a.disp_ord,
|
||||
r_unless: a.r_unless.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(ref vec) = ob.val_names {
|
||||
|
@ -91,6 +94,11 @@ impl<'n, 'e> OptBuilder<'n, 'e> {
|
|||
ob.num_vals = Some(vec.len() as u64);
|
||||
}
|
||||
}
|
||||
if let Some(ref vec) = ob.val_names {
|
||||
if vec.len() > 1 {
|
||||
ob.num_vals = Some(vec.len() as u64);
|
||||
}
|
||||
}
|
||||
if let Some(ref p) = a.validator {
|
||||
ob.validator = Some(p.clone());
|
||||
}
|
||||
|
@ -160,6 +168,7 @@ impl<'n, 'e> Clone for OptBuilder<'n, 'e> {
|
|||
possible_vals: self.possible_vals.clone(),
|
||||
default_val: self.default_val,
|
||||
validator: self.validator.clone(),
|
||||
r_unless: self.r_unless.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +178,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
|
|||
fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) }
|
||||
fn requires(&self) -> Option<&[&'e str]> { self.requires.as_ref().map(|o| &o[..]) }
|
||||
fn blacklist(&self) -> Option<&[&'e str]> { self.blacklist.as_ref().map(|o| &o[..]) }
|
||||
fn required_unless(&self) -> Option<&[&'e str]> { self.r_unless.as_ref().map(|o| &o[..]) }
|
||||
#[cfg_attr(feature = "lints", allow(map_clone))]
|
||||
fn val_names(&self) -> Option<&VecMap<&'e str>> { self.val_names.as_ref().map(|o| o) }
|
||||
fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) }
|
||||
|
|
|
@ -27,6 +27,7 @@ pub struct PosBuilder<'n, 'e> {
|
|||
pub val_delim: Option<char>,
|
||||
pub default_val: Option<&'n str>,
|
||||
pub disp_ord: usize,
|
||||
pub r_unless: Option<Vec<&'e str>>,
|
||||
}
|
||||
|
||||
impl<'n, 'e> Default for PosBuilder<'n, 'e> {
|
||||
|
@ -48,6 +49,7 @@ impl<'n, 'e> Default for PosBuilder<'n, 'e> {
|
|||
val_delim: Some(','),
|
||||
default_val: None,
|
||||
disp_ord: 999,
|
||||
r_unless: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +87,7 @@ impl<'n, 'e> PosBuilder<'n, 'e> {
|
|||
settings: a.settings,
|
||||
default_val: a.default_val,
|
||||
disp_ord: a.disp_ord,
|
||||
r_unless: a.r_unless.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
if a.max_vals.is_some()
|
||||
|
@ -147,6 +150,7 @@ impl<'n, 'e> Clone for PosBuilder<'n, 'e> {
|
|||
possible_vals: self.possible_vals.clone(),
|
||||
default_val: self.default_val,
|
||||
validator: self.validator.clone(),
|
||||
r_unless: self.r_unless.clone(),
|
||||
index: self.index,
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +161,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> {
|
|||
fn overrides(&self) -> Option<&[&'e str]> { self.overrides.as_ref().map(|o| &o[..]) }
|
||||
fn requires(&self) -> Option<&[&'e str]> { self.requires.as_ref().map(|o| &o[..]) }
|
||||
fn blacklist(&self) -> Option<&[&'e str]> { self.blacklist.as_ref().map(|o| &o[..]) }
|
||||
fn required_unless(&self) -> Option<&[&'e str]> { self.r_unless.as_ref().map(|o| &o[..]) }
|
||||
fn val_names(&self) -> Option<&VecMap<&'e str>> { self.val_names.as_ref() }
|
||||
fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) }
|
||||
fn set(&mut self, s: ArgSettings) { self.settings.set(s) }
|
||||
|
|
|
@ -2,15 +2,16 @@ use std::str::FromStr;
|
|||
use std::ascii::AsciiExt;
|
||||
|
||||
bitflags! {
|
||||
flags Flags: u8 {
|
||||
const REQUIRED = 0b00000001,
|
||||
const MULTIPLE = 0b00000010,
|
||||
const EMPTY_VALS = 0b00000100,
|
||||
const GLOBAL = 0b00001000,
|
||||
const HIDDEN = 0b00010000,
|
||||
const TAKES_VAL = 0b00100000,
|
||||
const USE_DELIM = 0b01000000,
|
||||
const NEXT_LINE_HELP = 0b10000000,
|
||||
flags Flags: u16 {
|
||||
const REQUIRED = 0b000000001,
|
||||
const MULTIPLE = 0b000000010,
|
||||
const EMPTY_VALS = 0b000000100,
|
||||
const GLOBAL = 0b000001000,
|
||||
const HIDDEN = 0b000010000,
|
||||
const TAKES_VAL = 0b000100000,
|
||||
const USE_DELIM = 0b001000000,
|
||||
const NEXT_LINE_HELP = 0b010000000,
|
||||
const R_UNLESS_ALL = 0b100000000,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +32,8 @@ impl ArgFlags {
|
|||
Hidden => HIDDEN,
|
||||
TakesValue => TAKES_VAL,
|
||||
UseValueDelimiter => USE_DELIM,
|
||||
NextLineHelp => NEXT_LINE_HELP
|
||||
NextLineHelp => NEXT_LINE_HELP,
|
||||
RequiredUnlessAll => R_UNLESS_ALL
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,6 +64,8 @@ pub enum ArgSettings {
|
|||
UseValueDelimiter,
|
||||
/// Prints the help text on the line after the argument
|
||||
NextLineHelp,
|
||||
#[doc(hidden)]
|
||||
RequiredUnlessAll,
|
||||
}
|
||||
|
||||
impl FromStr for ArgSettings {
|
||||
|
@ -76,6 +80,7 @@ impl FromStr for ArgSettings {
|
|||
"takesvalue" => Ok(ArgSettings::TakesValue),
|
||||
"usevaluedelimiter" => Ok(ArgSettings::UseValueDelimiter),
|
||||
"nextlinehelp" => Ok(ArgSettings::NextLineHelp),
|
||||
"requiredunlessall" => Ok(ArgSettings::RequiredUnlessAll),
|
||||
_ => Err("unknown ArgSetting, cannot convert from str".to_owned()),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue