Skip to content

Commit

Permalink
feat(parser): SetTrue/SetFalse/Count Actions
Browse files Browse the repository at this point in the history
This is the minimum set of actions for the derive to move off of
`parse`.  These are inspired by Python's native actions.

These new actions have a "unified" behavior with defaults/envs.  This
mostly means that occurrences aren't tracked.  Occurrences were used as
a substitute for `ValueSource` or for counting values.  Both cases
shouldn't be needed anymore but we can re-evaluate this later if needed.
  • Loading branch information
epage committed Jun 1, 2022
1 parent 2bc542d commit e4754bb
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 1 deletion.
106 changes: 106 additions & 0 deletions src/builder/action.rs
@@ -1,3 +1,5 @@
use crate::parser::AnyValueId;

/// Behavior of arguments when they are encountered while parsing
///
/// # Examples
Expand Down Expand Up @@ -70,6 +72,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::Arg::print_help]
///
/// Depending on the flag, [`Command::print_long_help`][super::Arg::print_long_help] may be shown
Expand Down Expand Up @@ -128,8 +205,37 @@ 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,
}
}

pub(crate) fn value_type_id(&self) -> Option<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;
4 changes: 3 additions & 1 deletion src/builder/arg.rs
Expand Up @@ -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());
Expand Down
10 changes: 10 additions & 0 deletions src/builder/debug_asserts.rs
Expand Up @@ -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!(
Expand Down
1 change: 1 addition & 0 deletions src/builder/mod.rs
Expand Up @@ -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;
91 changes: 91 additions & 0 deletions src/parser/parser.rs
Expand Up @@ -1182,6 +1182,88 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
matcher.add_index_to(&arg.id, self.cur_idx.get());
Ok(ParseResult::ValuesDone)
}
ArgAction::SetTrue => {
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());
}
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 => {
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());
}
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 => {
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());
}
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 {
Expand Down Expand Up @@ -1278,6 +1360,15 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
)?;
}
}
ArgAction::SetTrue | ArgAction::SetFalse | ArgAction::Count => {
let _ = self.react(
None,
ValueSource::EnvVariable,
arg,
vec![val.to_os_string()],
matcher,
)?;
}
// Early return on `Help` or `Version`.
ArgAction::Help | ArgAction::Version => {
let _ =
Expand Down

0 comments on commit e4754bb

Please sign in to comment.