mirror of
https://github.com/clap-rs/clap
synced 2025-01-18 23:53:54 +00:00
feat(builder): Set/Append Actions
This round out the new style actions and allow us to start deprecating occurrences. As part of an effort to unify code paths, this does change flag parsing to do splits. This will only be a problem if the user enables splits but we'll at least not crash. Once we also address #3776, we'll be able to have envs all work the same.
This commit is contained in:
parent
70767524c0
commit
95c812b411
3 changed files with 185 additions and 14 deletions
|
@ -24,6 +24,53 @@
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[allow(missing_copy_implementations)] // In the future, we may accept `Box<dyn ...>`
|
#[allow(missing_copy_implementations)] // In the future, we may accept `Box<dyn ...>`
|
||||||
pub enum ArgAction {
|
pub enum ArgAction {
|
||||||
|
/// When encountered, store the associated value(s) in [`ArgMatches`][crate::ArgMatches]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use clap::Command;
|
||||||
|
/// # use clap::Arg;
|
||||||
|
/// let cmd = Command::new("mycmd")
|
||||||
|
/// .arg(
|
||||||
|
/// Arg::new("flag")
|
||||||
|
/// .long("flag")
|
||||||
|
/// .action(clap::builder::ArgAction::Set)
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// let matches = cmd.try_get_matches_from(["mycmd", "--flag", "value"]).unwrap();
|
||||||
|
/// assert!(matches.is_present("flag"));
|
||||||
|
/// assert_eq!(matches.occurrences_of("flag"), 0);
|
||||||
|
/// assert_eq!(
|
||||||
|
/// matches.get_many::<String>("flag").unwrap_or_default().map(|v| v.as_str()).collect::<Vec<_>>(),
|
||||||
|
/// vec!["value"]
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
Set,
|
||||||
|
/// When encountered, store the associated value(s) in [`ArgMatches`][crate::ArgMatches]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use clap::Command;
|
||||||
|
/// # use clap::Arg;
|
||||||
|
/// let cmd = Command::new("mycmd")
|
||||||
|
/// .arg(
|
||||||
|
/// Arg::new("flag")
|
||||||
|
/// .long("flag")
|
||||||
|
/// .multiple_occurrences(true)
|
||||||
|
/// .action(clap::builder::ArgAction::Append)
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// let matches = cmd.try_get_matches_from(["mycmd", "--flag", "value1", "--flag", "value2"]).unwrap();
|
||||||
|
/// assert!(matches.is_present("flag"));
|
||||||
|
/// assert_eq!(matches.occurrences_of("flag"), 0);
|
||||||
|
/// assert_eq!(
|
||||||
|
/// matches.get_many::<String>("flag").unwrap_or_default().map(|v| v.as_str()).collect::<Vec<_>>(),
|
||||||
|
/// vec!["value1", "value2"]
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
Append,
|
||||||
/// When encountered, store the associated value(s) in [`ArgMatches`][crate::ArgMatches]
|
/// When encountered, store the associated value(s) in [`ArgMatches`][crate::ArgMatches]
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@ -201,6 +248,8 @@ pub enum ArgAction {
|
||||||
impl ArgAction {
|
impl ArgAction {
|
||||||
pub(crate) fn takes_value(&self) -> bool {
|
pub(crate) fn takes_value(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Set => true,
|
||||||
|
Self::Append => true,
|
||||||
Self::StoreValue => true,
|
Self::StoreValue => true,
|
||||||
Self::IncOccurrence => false,
|
Self::IncOccurrence => false,
|
||||||
Self::SetTrue => false,
|
Self::SetTrue => false,
|
||||||
|
@ -213,6 +262,8 @@ impl ArgAction {
|
||||||
|
|
||||||
pub(crate) fn default_value_parser(&self) -> Option<super::ValueParser> {
|
pub(crate) fn default_value_parser(&self) -> Option<super::ValueParser> {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Set => None,
|
||||||
|
Self::Append => None,
|
||||||
Self::StoreValue => None,
|
Self::StoreValue => None,
|
||||||
Self::IncOccurrence => None,
|
Self::IncOccurrence => None,
|
||||||
Self::SetTrue => Some(super::ValueParser::bool()),
|
Self::SetTrue => Some(super::ValueParser::bool()),
|
||||||
|
@ -228,6 +279,8 @@ impl ArgAction {
|
||||||
use crate::parser::AnyValueId;
|
use crate::parser::AnyValueId;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
Self::Set => None,
|
||||||
|
Self::Append => None,
|
||||||
Self::StoreValue => None,
|
Self::StoreValue => None,
|
||||||
Self::IncOccurrence => None,
|
Self::IncOccurrence => None,
|
||||||
Self::SetTrue => Some(AnyValueId::of::<bool>()),
|
Self::SetTrue => Some(AnyValueId::of::<bool>()),
|
||||||
|
|
|
@ -1138,6 +1138,41 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
|
||||||
source
|
source
|
||||||
);
|
);
|
||||||
match arg.get_action() {
|
match arg.get_action() {
|
||||||
|
ArgAction::Set => {
|
||||||
|
if source == ValueSource::CommandLine
|
||||||
|
&& matches!(ident, Some(Identifier::Short) | Some(Identifier::Long))
|
||||||
|
{
|
||||||
|
// Record flag's index
|
||||||
|
self.cur_idx.set(self.cur_idx.get() + 1);
|
||||||
|
debug!("Parser::react: cur_idx:={}", self.cur_idx.get());
|
||||||
|
}
|
||||||
|
matcher.remove(&arg.id);
|
||||||
|
self.start_custom_arg(matcher, arg, source);
|
||||||
|
self.push_arg_values(arg, raw_vals, matcher)?;
|
||||||
|
if cfg!(debug_assertions) && matcher.needs_more_vals(arg) {
|
||||||
|
debug!(
|
||||||
|
"Parser::react not enough values passed in, leaving it to the validator to complain",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(ParseResult::ValuesDone)
|
||||||
|
}
|
||||||
|
ArgAction::Append => {
|
||||||
|
if source == ValueSource::CommandLine
|
||||||
|
&& matches!(ident, Some(Identifier::Short) | Some(Identifier::Long))
|
||||||
|
{
|
||||||
|
// Record flag's index
|
||||||
|
self.cur_idx.set(self.cur_idx.get() + 1);
|
||||||
|
debug!("Parser::react: cur_idx:={}", self.cur_idx.get());
|
||||||
|
}
|
||||||
|
self.start_custom_arg(matcher, arg, source);
|
||||||
|
self.push_arg_values(arg, raw_vals, matcher)?;
|
||||||
|
if cfg!(debug_assertions) && matcher.needs_more_vals(arg) {
|
||||||
|
debug!(
|
||||||
|
"Parser::react not enough values passed in, leaving it to the validator to complain",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(ParseResult::ValuesDone)
|
||||||
|
}
|
||||||
ArgAction::StoreValue => {
|
ArgAction::StoreValue => {
|
||||||
if ident == Some(Identifier::Index)
|
if ident == Some(Identifier::Index)
|
||||||
&& arg.is_multiple_values_set()
|
&& arg.is_multiple_values_set()
|
||||||
|
@ -1189,10 +1224,10 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
|
||||||
}
|
}
|
||||||
1 => raw_vals,
|
1 => raw_vals,
|
||||||
_ => {
|
_ => {
|
||||||
panic!(
|
debug!("Parser::react ignoring trailing values: {:?}", raw_vals);
|
||||||
"Argument {:?} received too many values: {:?}",
|
let mut raw_vals = raw_vals;
|
||||||
arg.id, raw_vals
|
raw_vals.resize(1, Default::default());
|
||||||
)
|
raw_vals
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1208,10 +1243,10 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
|
||||||
}
|
}
|
||||||
1 => raw_vals,
|
1 => raw_vals,
|
||||||
_ => {
|
_ => {
|
||||||
panic!(
|
debug!("Parser::react ignoring trailing values: {:?}", raw_vals);
|
||||||
"Argument {:?} received too many values: {:?}",
|
let mut raw_vals = raw_vals;
|
||||||
arg.id, raw_vals
|
raw_vals.resize(1, Default::default());
|
||||||
)
|
raw_vals
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1231,10 +1266,10 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
|
||||||
}
|
}
|
||||||
1 => raw_vals,
|
1 => raw_vals,
|
||||||
_ => {
|
_ => {
|
||||||
panic!(
|
debug!("Parser::react ignoring trailing values: {:?}", raw_vals);
|
||||||
"Argument {:?} received too many values: {:?}",
|
let mut raw_vals = raw_vals;
|
||||||
arg.id, raw_vals
|
raw_vals.resize(1, Default::default());
|
||||||
)
|
raw_vals
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1339,14 +1374,26 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ArgAction::SetTrue | ArgAction::SetFalse | ArgAction::Count => {
|
ArgAction::Set
|
||||||
|
| ArgAction::Append
|
||||||
|
| ArgAction::SetTrue
|
||||||
|
| ArgAction::SetFalse
|
||||||
|
| ArgAction::Count => {
|
||||||
|
let mut arg_values = Vec::new();
|
||||||
|
let _parse_result =
|
||||||
|
self.split_arg_values(arg, &val, trailing_values, &mut arg_values);
|
||||||
let _ = self.react(
|
let _ = self.react(
|
||||||
None,
|
None,
|
||||||
ValueSource::EnvVariable,
|
ValueSource::EnvVariable,
|
||||||
arg,
|
arg,
|
||||||
vec![val.to_os_str().into_owned()],
|
arg_values,
|
||||||
matcher,
|
matcher,
|
||||||
)?;
|
)?;
|
||||||
|
if let Some(_parse_result) = _parse_result {
|
||||||
|
if _parse_result != ParseResult::ValuesDone {
|
||||||
|
debug!("Parser::add_env: Ignoring state {:?}; env variables are outside of the parse loop", _parse_result);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Early return on `Help` or `Version`.
|
// Early return on `Help` or `Version`.
|
||||||
ArgAction::Help | ArgAction::Version => {
|
ArgAction::Help | ArgAction::Version => {
|
||||||
|
|
|
@ -4,6 +4,77 @@ use clap::builder::ArgAction;
|
||||||
use clap::Arg;
|
use clap::Arg;
|
||||||
use clap::Command;
|
use clap::Command;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set() {
|
||||||
|
let cmd = Command::new("test").arg(Arg::new("mammal").long("mammal").action(ArgAction::Set));
|
||||||
|
|
||||||
|
let matches = cmd.clone().try_get_matches_from(["test"]).unwrap();
|
||||||
|
assert_eq!(matches.get_one::<String>("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", "dog"])
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(matches.get_one::<String>("mammal").unwrap(), "dog");
|
||||||
|
assert_eq!(matches.is_present("mammal"), true);
|
||||||
|
assert_eq!(matches.occurrences_of("mammal"), 0);
|
||||||
|
assert_eq!(matches.index_of("mammal"), Some(2));
|
||||||
|
|
||||||
|
let matches = cmd
|
||||||
|
.clone()
|
||||||
|
.try_get_matches_from(["test", "--mammal", "dog", "--mammal", "cat"])
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(matches.get_one::<String>("mammal").unwrap(), "cat");
|
||||||
|
assert_eq!(matches.is_present("mammal"), true);
|
||||||
|
assert_eq!(matches.occurrences_of("mammal"), 0);
|
||||||
|
assert_eq!(matches.index_of("mammal"), Some(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn append() {
|
||||||
|
let cmd = Command::new("test").arg(Arg::new("mammal").long("mammal").action(ArgAction::Append));
|
||||||
|
|
||||||
|
let matches = cmd.clone().try_get_matches_from(["test"]).unwrap();
|
||||||
|
assert_eq!(matches.get_one::<String>("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", "dog"])
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(matches.get_one::<String>("mammal").unwrap(), "dog");
|
||||||
|
assert_eq!(matches.is_present("mammal"), true);
|
||||||
|
assert_eq!(matches.occurrences_of("mammal"), 0);
|
||||||
|
assert_eq!(
|
||||||
|
matches.indices_of("mammal").unwrap().collect::<Vec<_>>(),
|
||||||
|
vec![2]
|
||||||
|
);
|
||||||
|
|
||||||
|
let matches = cmd
|
||||||
|
.clone()
|
||||||
|
.try_get_matches_from(["test", "--mammal", "dog", "--mammal", "cat"])
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
matches
|
||||||
|
.get_many::<String>("mammal")
|
||||||
|
.unwrap()
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec!["dog", "cat"]
|
||||||
|
);
|
||||||
|
assert_eq!(matches.is_present("mammal"), true);
|
||||||
|
assert_eq!(matches.occurrences_of("mammal"), 0);
|
||||||
|
assert_eq!(
|
||||||
|
matches.indices_of("mammal").unwrap().collect::<Vec<_>>(),
|
||||||
|
vec![2, 4]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn set_true() {
|
fn set_true() {
|
||||||
let cmd =
|
let cmd =
|
||||||
|
|
Loading…
Reference in a new issue