Skip to content

Commit

Permalink
Merge pull request #3777 from epage/set
Browse files Browse the repository at this point in the history
feat(builder): Set/Append Actions
  • Loading branch information
epage authored Jun 1, 2022
2 parents 7076752 + 95c812b commit b417e62
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 14 deletions.
53 changes: 53 additions & 0 deletions src/builder/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,53 @@
#[non_exhaustive]
#[allow(missing_copy_implementations)] // In the future, we may accept `Box<dyn ...>`
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]
///
/// # Examples
Expand Down Expand Up @@ -201,6 +248,8 @@ pub enum ArgAction {
impl ArgAction {
pub(crate) fn takes_value(&self) -> bool {
match self {
Self::Set => true,
Self::Append => true,
Self::StoreValue => true,
Self::IncOccurrence => false,
Self::SetTrue => false,
Expand All @@ -213,6 +262,8 @@ impl ArgAction {

pub(crate) fn default_value_parser(&self) -> Option<super::ValueParser> {
match self {
Self::Set => None,
Self::Append => None,
Self::StoreValue => None,
Self::IncOccurrence => None,
Self::SetTrue => Some(super::ValueParser::bool()),
Expand All @@ -228,6 +279,8 @@ impl ArgAction {
use crate::parser::AnyValueId;

match self {
Self::Set => None,
Self::Append => None,
Self::StoreValue => None,
Self::IncOccurrence => None,
Self::SetTrue => Some(AnyValueId::of::<bool>()),
Expand Down
75 changes: 61 additions & 14 deletions src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,41 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
source
);
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 => {
if ident == Some(Identifier::Index)
&& arg.is_multiple_values_set()
Expand Down Expand Up @@ -1189,10 +1224,10 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
}
1 => raw_vals,
_ => {
panic!(
"Argument {:?} received too many values: {:?}",
arg.id, raw_vals
)
debug!("Parser::react ignoring trailing values: {:?}", raw_vals);
let mut raw_vals = raw_vals;
raw_vals.resize(1, Default::default());
raw_vals
}
};

Expand All @@ -1208,10 +1243,10 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
}
1 => raw_vals,
_ => {
panic!(
"Argument {:?} received too many values: {:?}",
arg.id, raw_vals
)
debug!("Parser::react ignoring trailing values: {:?}", raw_vals);
let mut raw_vals = raw_vals;
raw_vals.resize(1, Default::default());
raw_vals
}
};

Expand All @@ -1231,10 +1266,10 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
}
1 => raw_vals,
_ => {
panic!(
"Argument {:?} received too many values: {:?}",
arg.id, raw_vals
)
debug!("Parser::react ignoring trailing values: {:?}", raw_vals);
let mut raw_vals = raw_vals;
raw_vals.resize(1, Default::default());
raw_vals
}
};

Expand Down Expand Up @@ -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(
None,
ValueSource::EnvVariable,
arg,
vec![val.to_os_str().into_owned()],
arg_values,
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`.
ArgAction::Help | ArgAction::Version => {
Expand Down
71 changes: 71 additions & 0 deletions tests/builder/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,77 @@ use clap::builder::ArgAction;
use clap::Arg;
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]
fn set_true() {
let cmd =
Expand Down

0 comments on commit b417e62

Please sign in to comment.