Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(parser)!: Apply default_missing_value per occurrence #3998

Merged
merged 1 commit into from Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Disallow more `value_names` than `number_of_values` (#2695)
- Replaced `cmd.allow_invalid_for_utf8_external_subcommands` with `cmd.external_subcommand_value_parser` (#3733)
- Changed the default type of `allow_external_subcommands` from `String` to `OsString`
- `Arg::default_missing_value` now applies per occurrence rather than if a value is missing across all occurrences
- *(assert)* Always enforce that version is specified when the `ArgAction::Version` is used
- *(assert)* Add missing `#[track_caller]`s to make it easier to debug asserts
- *(assert)* Ensure `overrides_with` IDs are valid
Expand Down
92 changes: 23 additions & 69 deletions src/parser/parser.rs
Expand Up @@ -964,25 +964,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
if arg.is_require_equals_set() && !has_eq {
if arg.min_vals == Some(0) {
debug!("Requires equals, but min_vals == 0");
let mut arg_values = Vec::new();
// We assume this case is valid: require equals, but min_vals == 0.
if !arg.default_missing_vals.is_empty() {
debug!("Parser::parse_opt_value: has default_missing_vals");
for v in arg.default_missing_vals.iter() {
let trailing_values = false; // CLI should not be affecting default_missing_values
let _parse_result = self.split_arg_values(
arg,
&RawOsStr::new(v),
trailing_values,
&mut arg_values,
);
if let Some(_parse_result) = _parse_result {
if _parse_result != ParseResult::ValuesDone {
debug!("Parser::parse_opt_value: Ignoring state {:?}; no values accepted after default_missing_values", _parse_result);
}
}
}
};
let arg_values = Vec::new();
let react_result = self.react(
Some(ident),
ValueSource::CommandLine,
Expand Down Expand Up @@ -1133,11 +1115,32 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
ident: Option<Identifier>,
source: ValueSource,
arg: &Arg<'help>,
raw_vals: Vec<OsString>,
mut raw_vals: Vec<OsString>,
matcher: &mut ArgMatcher,
) -> ClapResult<ParseResult> {
self.resolve_pending(matcher)?;

if raw_vals.is_empty() {
// We assume this case is valid: require equals, but min_vals == 0.
if !arg.default_missing_vals.is_empty() {
debug!("Parser::react: has default_missing_vals");
for v in arg.default_missing_vals.iter() {
let trailing_values = false; // CLI should not be affecting default_missing_values
let _parse_result = self.split_arg_values(
arg,
&RawOsStr::new(v),
trailing_values,
&mut raw_vals,
);
if let Some(_parse_result) = _parse_result {
if _parse_result != ParseResult::ValuesDone {
debug!("Parser::parse_opt_value: Ignoring state {:?}; no values accepted after default_missing_values", _parse_result);
}
}
}
};
}

debug!(
"Parser::react action={:?}, identifier={:?}, source={:?}",
arg.get_action(),
Expand Down Expand Up @@ -1350,55 +1353,6 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
fn add_default_value(&self, arg: &Arg<'help>, matcher: &mut ArgMatcher) -> ClapResult<()> {
let trailing_values = false; // defaults are independent of the commandline

if !arg.default_missing_vals.is_empty() {
debug!(
"Parser::add_default_value:iter:{}: has default missing vals",
arg.name
);
match matcher.get(&arg.id) {
Some(ma) if ma.all_val_groups_empty() => {
debug!(
"Parser::add_default_value:iter:{}: has no user defined vals",
arg.name
);
// The flag occurred, we just want to add the val groups
let mut arg_values = Vec::new();
for v in arg.default_missing_vals.iter() {
let _parse_result = self.split_arg_values(
arg,
&RawOsStr::new(v),
trailing_values,
&mut arg_values,
);
if let Some(_parse_result) = _parse_result {
if _parse_result != ParseResult::ValuesDone {
debug!("Parser::add_default_value: Ignoring state {:?}; defaults are outside of the parse loop", _parse_result);
}
}
}
self.start_custom_arg(matcher, arg, ValueSource::CommandLine);
self.push_arg_values(arg, arg_values, matcher)?;
}
None => {
debug!("Parser::add_default_value:iter:{}: wasn't used", arg.name);
// do nothing
}
_ => {
debug!(
"Parser::add_default_value:iter:{}: has user defined vals",
arg.name
);
// do nothing
}
}
} else {
debug!(
"Parser::add_default_value:iter:{}: doesn't have default missing vals",
arg.name
);
// do nothing
}

if !arg.default_vals_ifs.is_empty() {
debug!("Parser::add_default_value: has conditional defaults");
if !matcher.contains(&arg.id) {
Expand Down
22 changes: 22 additions & 0 deletions tests/builder/default_missing_vals.rs
Expand Up @@ -143,6 +143,28 @@ fn opt_default_user_override() {
);
}

#[test]
fn default_missing_value_per_occurrence() {
// assert no change to usual argument handling when adding default_missing_value()
let r = Command::new("cmd")
.arg(
arg!(o: -o <opt> ... "some opt")
.min_values(0)
.default_value("default")
.default_missing_value("default_missing"),
)
.try_get_matches_from(vec!["", "-o", "-o=value", "-o"]);
assert!(r.is_ok(), "{}", r.unwrap_err());
let m = r.unwrap();
assert_eq!(
m.get_many::<String>("o")
.unwrap()
.map(|v| v.as_str())
.collect::<Vec<_>>(),
vec!["default_missing", "value", "default_missing"]
);
}

#[test]
#[allow(clippy::bool_assert_comparison)]
fn default_missing_value_flag_value() {
Expand Down