Merge pull request #3775 from epage/new

feat(parser): SetTrue/SetFalse/Count Actions
This commit is contained in:
Ed Page 2022-06-01 07:07:27 -05:00 committed by GitHub
commit 70767524c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 540 additions and 7 deletions

View file

@ -70,6 +70,81 @@ pub enum ArgAction {
/// assert_eq!(matches.get_many::<String>("flag").unwrap_or_default().count(), 0);
/// ```
IncOccurrence,
/// When encountered, act as if `"true"` was encountered on the command-line
///
/// No value is allowed
///
/// # Examples
///
/// ```rust
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("flag")
/// .long("flag")
/// .action(clap::builder::ArgAction::SetTrue)
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd", "--flag", "--flag"]).unwrap();
/// assert!(matches.is_present("flag"));
/// assert_eq!(matches.occurrences_of("flag"), 0);
/// assert_eq!(
/// matches.get_one::<bool>("flag").copied(),
/// Some(true)
/// );
/// ```
SetTrue,
/// When encountered, act as if `"false"` was encountered on the command-line
///
/// No value is allowed
///
/// # Examples
///
/// ```rust
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("flag")
/// .long("flag")
/// .action(clap::builder::ArgAction::SetFalse)
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd", "--flag", "--flag"]).unwrap();
/// assert!(matches.is_present("flag"));
/// assert_eq!(matches.occurrences_of("flag"), 0);
/// assert_eq!(
/// matches.get_one::<bool>("flag").copied(),
/// Some(false)
/// );
/// ```
SetFalse,
/// When encountered, increment a counter
///
/// No value is allowed
///
/// # Examples
///
/// ```rust
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("flag")
/// .long("flag")
/// .action(clap::builder::ArgAction::Count)
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd", "--flag", "--flag"]).unwrap();
/// assert!(matches.is_present("flag"));
/// assert_eq!(matches.occurrences_of("flag"), 0);
/// assert_eq!(
/// matches.get_one::<u64>("flag").copied(),
/// Some(2)
/// );
/// ```
Count,
/// When encountered, display [`Command::print_help`][super::App::print_help]
///
/// Depending on the flag, [`Command::print_long_help`][super::App::print_long_help] may be shown
@ -128,8 +203,40 @@ impl ArgAction {
match self {
Self::StoreValue => true,
Self::IncOccurrence => false,
Self::SetTrue => false,
Self::SetFalse => false,
Self::Count => false,
Self::Help => false,
Self::Version => false,
}
}
pub(crate) fn default_value_parser(&self) -> Option<super::ValueParser> {
match self {
Self::StoreValue => None,
Self::IncOccurrence => None,
Self::SetTrue => Some(super::ValueParser::bool()),
Self::SetFalse => Some(super::ValueParser::bool()),
Self::Count => Some(crate::value_parser!(u64)),
Self::Help => None,
Self::Version => None,
}
}
#[cfg(debug_assertions)]
pub(crate) fn value_type_id(&self) -> Option<crate::parser::AnyValueId> {
use crate::parser::AnyValueId;
match self {
Self::StoreValue => None,
Self::IncOccurrence => None,
Self::SetTrue => Some(AnyValueId::of::<bool>()),
Self::SetFalse => Some(AnyValueId::of::<bool>()),
Self::Count => Some(AnyValueId::of::<CountType>()),
Self::Help => None,
Self::Version => None,
}
}
}
pub(crate) type CountType = u64;

View file

@ -4900,7 +4900,9 @@ impl<'help> Arg<'help> {
}
if self.value_parser.is_none() {
if self.is_allow_invalid_utf8_set() {
if let Some(default) = self.action.as_ref().and_then(|a| a.default_value_parser()) {
self.value_parser = Some(default);
} else if self.is_allow_invalid_utf8_set() {
self.value_parser = Some(super::ValueParser::os_string());
} else {
self.value_parser = Some(super::ValueParser::string());

View file

@ -649,6 +649,16 @@ fn assert_arg(arg: &Arg) {
arg.name,
arg.get_action()
);
if let Some(action_type_id) = arg.get_action().value_type_id() {
assert_eq!(
action_type_id,
arg.get_value_parser().type_id(),
"Argument `{}`'s selected action {:?} contradicts `value_parser` ({:?})",
arg.name,
arg.get_action(),
arg.get_value_parser()
);
}
if arg.get_value_hint() != ValueHint::Unknown {
assert!(

View file

@ -55,6 +55,7 @@ pub use command::App;
#[cfg(feature = "regex")]
pub use self::regex::RegexRef;
pub(crate) use action::CountType;
pub(crate) use arg::display_arg_val;
pub(crate) use arg_predicate::ArgPredicate;
pub(crate) use value_parser::ValueParserInner;

View file

@ -1182,6 +1182,67 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
matcher.add_index_to(&arg.id, self.cur_idx.get());
Ok(ParseResult::ValuesDone)
}
ArgAction::SetTrue => {
let raw_vals = match raw_vals.len() {
0 => {
vec![OsString::from("true")]
}
1 => raw_vals,
_ => {
panic!(
"Argument {:?} received too many values: {:?}",
arg.id, raw_vals
)
}
};
matcher.remove(&arg.id);
self.start_custom_arg(matcher, arg, source);
self.push_arg_values(arg, raw_vals, matcher)?;
Ok(ParseResult::ValuesDone)
}
ArgAction::SetFalse => {
let raw_vals = match raw_vals.len() {
0 => {
vec![OsString::from("false")]
}
1 => raw_vals,
_ => {
panic!(
"Argument {:?} received too many values: {:?}",
arg.id, raw_vals
)
}
};
matcher.remove(&arg.id);
self.start_custom_arg(matcher, arg, source);
self.push_arg_values(arg, raw_vals, matcher)?;
Ok(ParseResult::ValuesDone)
}
ArgAction::Count => {
let raw_vals = match raw_vals.len() {
0 => {
let existing_value = *matcher
.get_one::<crate::builder::CountType>(arg.get_id())
.unwrap_or(&0);
let next_value = existing_value + 1;
vec![OsString::from(next_value.to_string())]
}
1 => raw_vals,
_ => {
panic!(
"Argument {:?} received too many values: {:?}",
arg.id, raw_vals
)
}
};
matcher.remove(&arg.id);
self.start_custom_arg(matcher, arg, source);
self.push_arg_values(arg, raw_vals, matcher)?;
Ok(ParseResult::ValuesDone)
}
ArgAction::Help => {
debug_assert_eq!(raw_vals, Vec::<OsString>::new());
let use_long = match ident {
@ -1278,6 +1339,15 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
)?;
}
}
ArgAction::SetTrue | ArgAction::SetFalse | ArgAction::Count => {
let _ = self.react(
None,
ValueSource::EnvVariable,
arg,
vec![val.to_os_str().into_owned()],
matcher,
)?;
}
// Early return on `Help` or `Version`.
ArgAction::Help | ArgAction::Version => {
let _ =
@ -1294,12 +1364,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
fn add_defaults(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
debug!("Parser::add_defaults");
for arg in self.cmd.get_opts() {
debug!("Parser::add_defaults:iter:{}:", arg.name);
self.add_default_value(arg, matcher)?;
}
for arg in self.cmd.get_positionals() {
for arg in self.cmd.get_arguments() {
debug!("Parser::add_defaults:iter:{}:", arg.name);
self.add_default_value(arg, matcher)?;
}

347
tests/builder/action.rs Normal file
View file

@ -0,0 +1,347 @@
#![allow(clippy::bool_assert_comparison)]
use clap::builder::ArgAction;
use clap::Arg;
use clap::Command;
#[test]
fn set_true() {
let cmd =
Command::new("test").arg(Arg::new("mammal").long("mammal").action(ArgAction::SetTrue));
let matches = cmd.clone().try_get_matches_from(["test"]).unwrap();
assert_eq!(matches.get_one::<bool>("mammal"), None);
assert_eq!(matches.is_present("mammal"), false);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), None);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), true);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(1));
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal", "--mammal"])
.unwrap();
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), true);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(2));
}
#[test]
fn set_true_with_default_value() {
let cmd = Command::new("test").arg(
Arg::new("mammal")
.long("mammal")
.action(ArgAction::SetTrue)
.default_value("false"),
);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), true);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(1));
let matches = cmd.clone().try_get_matches_from(["test"]).unwrap();
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), false);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(1));
}
#[test]
fn set_true_with_default_value_if_present() {
let cmd = Command::new("test")
.arg(
Arg::new("mammal")
.long("mammal")
.action(ArgAction::SetTrue)
.default_value_if("dog", None, Some("true")),
)
.arg(Arg::new("dog").long("dog").action(ArgAction::SetTrue));
let matches = cmd.clone().try_get_matches_from(["test", "--dog"]).unwrap();
assert_eq!(*matches.get_one::<bool>("dog").unwrap(), true);
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), true);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(matches.get_one::<bool>("dog"), None);
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), true);
}
#[test]
fn set_true_with_default_value_if_value() {
let cmd = Command::new("test")
.arg(
Arg::new("mammal")
.long("mammal")
.action(ArgAction::SetTrue)
.default_value_if("dog", Some("true"), Some("true")),
)
.arg(Arg::new("dog").long("dog").action(ArgAction::SetTrue));
let matches = cmd.clone().try_get_matches_from(["test", "--dog"]).unwrap();
assert_eq!(*matches.get_one::<bool>("dog").unwrap(), true);
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), true);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(matches.get_one::<bool>("dog"), None);
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), true);
}
#[test]
fn set_true_with_required_if_eq() {
let cmd = Command::new("test")
.arg(
Arg::new("mammal")
.long("mammal")
.action(ArgAction::SetTrue)
.required_if_eq("dog", "true"),
)
.arg(Arg::new("dog").long("dog").action(ArgAction::SetTrue));
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(matches.get_one::<u64>("dog"), None);
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), true);
cmd.clone()
.try_get_matches_from(["test", "--dog"])
.unwrap_err();
let matches = cmd
.clone()
.try_get_matches_from(["test", "--dog", "--mammal"])
.unwrap();
assert_eq!(*matches.get_one::<bool>("dog").unwrap(), true);
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), true);
}
#[test]
fn set_false() {
let cmd = Command::new("test").arg(
Arg::new("mammal")
.long("mammal")
.action(ArgAction::SetFalse),
);
let matches = cmd.clone().try_get_matches_from(["test"]).unwrap();
assert_eq!(matches.get_one::<bool>("mammal"), None);
assert_eq!(matches.is_present("mammal"), false);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), None);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), false);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(1));
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal", "--mammal"])
.unwrap();
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), false);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(2));
}
#[test]
fn set_false_with_default_value() {
let cmd = Command::new("test").arg(
Arg::new("mammal")
.long("mammal")
.action(ArgAction::SetFalse)
.default_value("true"),
);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), false);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(1));
let matches = cmd.clone().try_get_matches_from(["test"]).unwrap();
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), true);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(1));
}
#[test]
fn set_false_with_default_value_if_present() {
let cmd = Command::new("test")
.arg(
Arg::new("mammal")
.long("mammal")
.action(ArgAction::SetFalse)
.default_value_if("dog", None, Some("false")),
)
.arg(Arg::new("dog").long("dog").action(ArgAction::SetFalse));
let matches = cmd.clone().try_get_matches_from(["test", "--dog"]).unwrap();
assert_eq!(*matches.get_one::<bool>("dog").unwrap(), false);
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), false);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(matches.get_one::<bool>("dog"), None);
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), false);
}
#[test]
fn set_false_with_default_value_if_value() {
let cmd = Command::new("test")
.arg(
Arg::new("mammal")
.long("mammal")
.action(ArgAction::SetFalse)
.default_value_if("dog", Some("false"), Some("false")),
)
.arg(Arg::new("dog").long("dog").action(ArgAction::SetFalse));
let matches = cmd.clone().try_get_matches_from(["test", "--dog"]).unwrap();
assert_eq!(*matches.get_one::<bool>("dog").unwrap(), false);
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), false);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(matches.get_one::<bool>("dog"), None);
assert_eq!(*matches.get_one::<bool>("mammal").unwrap(), false);
}
#[test]
fn count() {
let cmd = Command::new("test").arg(Arg::new("mammal").long("mammal").action(ArgAction::Count));
let matches = cmd.clone().try_get_matches_from(["test"]).unwrap();
assert_eq!(matches.get_one::<u64>("mammal"), None);
assert_eq!(matches.is_present("mammal"), false);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), None);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(*matches.get_one::<u64>("mammal").unwrap(), 1);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(1));
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal", "--mammal"])
.unwrap();
assert_eq!(*matches.get_one::<u64>("mammal").unwrap(), 2);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(2));
}
#[test]
fn count_with_default_value() {
let cmd = Command::new("test").arg(
Arg::new("mammal")
.long("mammal")
.action(ArgAction::Count)
.default_value("10"),
);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(*matches.get_one::<u64>("mammal").unwrap(), 1);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(1));
let matches = cmd.clone().try_get_matches_from(["test"]).unwrap();
assert_eq!(*matches.get_one::<u64>("mammal").unwrap(), 10);
assert_eq!(matches.is_present("mammal"), true);
assert_eq!(matches.occurrences_of("mammal"), 0);
assert_eq!(matches.index_of("mammal"), Some(1));
}
#[test]
fn count_with_default_value_if_present() {
let cmd = Command::new("test")
.arg(
Arg::new("mammal")
.long("mammal")
.action(ArgAction::Count)
.default_value_if("dog", None, Some("10")),
)
.arg(Arg::new("dog").long("dog").action(ArgAction::Count));
let matches = cmd.clone().try_get_matches_from(["test", "--dog"]).unwrap();
assert_eq!(*matches.get_one::<u64>("dog").unwrap(), 1);
assert_eq!(*matches.get_one::<u64>("mammal").unwrap(), 10);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(matches.get_one::<u64>("dog"), None);
assert_eq!(*matches.get_one::<u64>("mammal").unwrap(), 1);
}
#[test]
fn count_with_default_value_if_value() {
let cmd = Command::new("test")
.arg(
Arg::new("mammal")
.long("mammal")
.action(ArgAction::Count)
.default_value_if("dog", Some("2"), Some("10")),
)
.arg(Arg::new("dog").long("dog").action(ArgAction::Count));
let matches = cmd.clone().try_get_matches_from(["test", "--dog"]).unwrap();
assert_eq!(*matches.get_one::<u64>("dog").unwrap(), 1);
assert_eq!(matches.get_one::<u64>("mammal"), None);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--dog", "--dog"])
.unwrap();
assert_eq!(*matches.get_one::<u64>("dog").unwrap(), 2);
assert_eq!(*matches.get_one::<u64>("mammal").unwrap(), 10);
let matches = cmd
.clone()
.try_get_matches_from(["test", "--mammal"])
.unwrap();
assert_eq!(matches.get_one::<u64>("dog"), None);
assert_eq!(*matches.get_one::<u64>("mammal").unwrap(), 1);
}

View file

@ -1,3 +1,4 @@
mod action;
mod app_from_crate;
mod app_settings;
mod arg_aliases;