mirror of
https://github.com/clap-rs/clap
synced 2024-12-14 06:42:33 +00:00
Merge pull request #2643 from ldm0/master
Fix attached_value abandoned even if it's not used.
This commit is contained in:
commit
0cde7760dd
2 changed files with 124 additions and 100 deletions
|
@ -29,6 +29,7 @@ pub(crate) enum ParseResult {
|
||||||
Pos(Id),
|
Pos(Id),
|
||||||
MaybeHyphenValue,
|
MaybeHyphenValue,
|
||||||
NotFound,
|
NotFound,
|
||||||
|
AttachedValueNotConsumed,
|
||||||
ValuesDone,
|
ValuesDone,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,7 +388,6 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
subcmd_name = Some(name.to_owned());
|
subcmd_name = Some(name.to_owned());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ParseResult::FlagSubCommandShort(_) => unreachable!(),
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
} else if arg_os.starts_with("-")
|
} else if arg_os.starts_with("-")
|
||||||
|
@ -433,7 +433,6 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
subcmd_name = Some(name.to_owned());
|
subcmd_name = Some(name.to_owned());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ParseResult::FlagSubCommand(_) => unreachable!(),
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1053,11 +1052,26 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
(long_arg, None)
|
(long_arg, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(opt) = self.app.args.get(&arg.to_os_string()) {
|
let opt = if let Some(opt) = self.app.args.get(&arg.to_os_string()) {
|
||||||
debug!(
|
debug!(
|
||||||
"Parser::parse_long_arg: Found valid opt or flag '{}'",
|
"Parser::parse_long_arg: Found valid opt or flag '{}'",
|
||||||
opt.to_string()
|
opt.to_string()
|
||||||
);
|
);
|
||||||
|
Some(opt)
|
||||||
|
} else if self.is_set(AS::InferLongArgs) {
|
||||||
|
let arg_str = arg.to_string_lossy();
|
||||||
|
self.app.args.args().find(|a| {
|
||||||
|
a.long
|
||||||
|
.map_or(false, |long| long.starts_with(arg_str.as_ref()))
|
||||||
|
|| a.aliases
|
||||||
|
.iter()
|
||||||
|
.any(|(alias, _)| alias.starts_with(arg_str.as_ref()))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(opt) = opt {
|
||||||
self.app.settings.set(AS::ValidArgFound);
|
self.app.settings.set(AS::ValidArgFound);
|
||||||
self.seen.push(opt.id.clone());
|
self.seen.push(opt.id.clone());
|
||||||
if opt.is_set(ArgSettings::TakesValue) {
|
if opt.is_set(ArgSettings::TakesValue) {
|
||||||
|
@ -1068,37 +1082,6 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_set(AS::InferLongArgs) {
|
|
||||||
let arg_str = arg.to_string_lossy();
|
|
||||||
let matches: Vec<_> = self
|
|
||||||
.app
|
|
||||||
.args
|
|
||||||
.args()
|
|
||||||
.filter(|a| {
|
|
||||||
a.long
|
|
||||||
.map_or(false, |long| long.starts_with(arg_str.as_ref()))
|
|
||||||
|| a.aliases
|
|
||||||
.iter()
|
|
||||||
.any(|(alias, _)| alias.starts_with(arg_str.as_ref()))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if let [opt] = matches.as_slice() {
|
|
||||||
debug!(
|
|
||||||
"Parser::parse_long_arg: Found valid opt or flag '{}'",
|
|
||||||
opt.to_string()
|
|
||||||
);
|
|
||||||
self.app.settings.set(AS::ValidArgFound);
|
|
||||||
self.seen.push(opt.id.clone());
|
|
||||||
if opt.is_set(ArgSettings::TakesValue) {
|
|
||||||
return self.parse_opt(&val, opt, matcher);
|
|
||||||
} else {
|
|
||||||
self.check_for_help_and_version_str(&arg)?;
|
|
||||||
return Ok(self.parse_flag(opt, matcher));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(sc_name) = self.possible_long_flag_subcommand(&arg) {
|
if let Some(sc_name) = self.possible_long_flag_subcommand(&arg) {
|
||||||
Ok(ParseResult::FlagSubCommand(sc_name.to_string()))
|
Ok(ParseResult::FlagSubCommand(sc_name.to_string()))
|
||||||
} else if self.is_set(AS::AllowLeadingHyphen) {
|
} else if self.is_set(AS::AllowLeadingHyphen) {
|
||||||
|
@ -1188,8 +1171,17 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// Default to "we're expecting a value later"
|
// Default to "we're expecting a value later".
|
||||||
return self.parse_opt(&val, opt, matcher);
|
//
|
||||||
|
// If attached value is not consumed, we may have more short
|
||||||
|
// flags to parse, continue.
|
||||||
|
//
|
||||||
|
// e.g. `-xvf`, when RequireEquals && x.min_vals == 0, we don't
|
||||||
|
// consume the `vf`, even if it's provided as value.
|
||||||
|
match self.parse_opt(&val, opt, matcher)? {
|
||||||
|
ParseResult::AttachedValueNotConsumed => continue,
|
||||||
|
x => return Ok(x),
|
||||||
|
}
|
||||||
} else if let Some(sc_name) = self.app.find_short_subcmd(c) {
|
} else if let Some(sc_name) = self.app.find_short_subcmd(c) {
|
||||||
debug!("Parser::parse_short_arg:iter:{}: subcommand={}", c, sc_name);
|
debug!("Parser::parse_short_arg:iter:{}: subcommand={}", c, sc_name);
|
||||||
let name = sc_name.to_string();
|
let name = sc_name.to_string();
|
||||||
|
@ -1224,24 +1216,53 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
|
|
||||||
fn parse_opt(
|
fn parse_opt(
|
||||||
&self,
|
&self,
|
||||||
val: &Option<ArgStr>,
|
attached_value: &Option<ArgStr>,
|
||||||
opt: &Arg<'help>,
|
opt: &Arg<'help>,
|
||||||
matcher: &mut ArgMatcher,
|
matcher: &mut ArgMatcher,
|
||||||
) -> ClapResult<ParseResult> {
|
) -> ClapResult<ParseResult> {
|
||||||
debug!("Parser::parse_opt; opt={}, val={:?}", opt.name, val);
|
debug!(
|
||||||
|
"Parser::parse_opt; opt={}, val={:?}",
|
||||||
|
opt.name, attached_value
|
||||||
|
);
|
||||||
debug!("Parser::parse_opt; opt.settings={:?}", opt.settings);
|
debug!("Parser::parse_opt; opt.settings={:?}", opt.settings);
|
||||||
// has_eq: --flag=value
|
// has_eq: --flag=value
|
||||||
let mut has_eq = false;
|
let has_eq = matches!(attached_value, Some(fv) if fv.starts_with("="));
|
||||||
let no_val = val.is_none();
|
|
||||||
let no_empty_vals = opt.is_set(ArgSettings::ForbidEmptyValues);
|
|
||||||
let min_vals_zero = opt.min_vals.unwrap_or(1) == 0;
|
|
||||||
let require_equals = opt.is_set(ArgSettings::RequireEquals);
|
|
||||||
|
|
||||||
debug!("Parser::parse_opt; Checking for val...");
|
debug!("Parser::parse_opt; Checking for val...");
|
||||||
if let Some(fv) = val {
|
// RequireEquals is set, but no '=' is provided, try throwing error.
|
||||||
has_eq = fv.starts_with("=");
|
if opt.is_set(ArgSettings::RequireEquals) && !has_eq {
|
||||||
|
if opt.min_vals == Some(0) {
|
||||||
|
debug!("Requires equals, but min_vals == 0");
|
||||||
|
// We assume this case is valid: require equals, but min_vals == 0.
|
||||||
|
if !opt.default_missing_vals.is_empty() {
|
||||||
|
debug!("Parser::parse_opt: has default_missing_vals");
|
||||||
|
self.add_multiple_vals_to_arg(
|
||||||
|
opt,
|
||||||
|
opt.default_missing_vals.iter().map(OsString::from),
|
||||||
|
matcher,
|
||||||
|
ValueType::CommandLine,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
self.inc_occurrence_of_arg(matcher, opt);
|
||||||
|
if attached_value.is_some() {
|
||||||
|
return Ok(ParseResult::AttachedValueNotConsumed);
|
||||||
|
} else {
|
||||||
|
return Ok(ParseResult::ValuesDone);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug!("Requires equals but not provided. Error.");
|
||||||
|
return Err(ClapError::no_equals(
|
||||||
|
opt,
|
||||||
|
Usage::new(self).create_usage_with_title(&[]),
|
||||||
|
self.app.color(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(fv) = attached_value {
|
||||||
let v = fv.trim_start_n_matches(1, b'=');
|
let v = fv.trim_start_n_matches(1, b'=');
|
||||||
if no_empty_vals && (v.is_empty() || (require_equals && !has_eq)) {
|
if opt.is_set(ArgSettings::ForbidEmptyValues) && v.is_empty() {
|
||||||
debug!("Found Empty - Error");
|
debug!("Found Empty - Error");
|
||||||
return Err(ClapError::empty_value(
|
return Err(ClapError::empty_value(
|
||||||
opt,
|
opt,
|
||||||
|
@ -1256,56 +1277,16 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
fv.starts_with("=")
|
fv.starts_with("=")
|
||||||
);
|
);
|
||||||
self.add_val_to_arg(opt, v, matcher, ValueType::CommandLine, false);
|
self.add_val_to_arg(opt, v, matcher, ValueType::CommandLine, false);
|
||||||
} else if require_equals {
|
self.inc_occurrence_of_arg(matcher, opt);
|
||||||
if min_vals_zero {
|
|
||||||
debug!("Requires equals, but min_vals == 0");
|
|
||||||
if !opt.default_missing_vals.is_empty() {
|
|
||||||
debug!("Parser::parse_opt: has default_missing_vals");
|
|
||||||
let vals = opt
|
|
||||||
.default_missing_vals
|
|
||||||
.iter()
|
|
||||||
.map(OsString::from)
|
|
||||||
.collect();
|
|
||||||
self.add_multiple_vals_to_arg(
|
|
||||||
opt,
|
|
||||||
vals,
|
|
||||||
matcher,
|
|
||||||
ValueType::CommandLine,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
debug!("Requires equals but not provided. Error.");
|
|
||||||
return Err(ClapError::no_equals(
|
|
||||||
opt,
|
|
||||||
Usage::new(self).create_usage_with_title(&[]),
|
|
||||||
self.app.color(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug!("None");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.inc_occurrence_of_arg(matcher, opt);
|
|
||||||
|
|
||||||
let needs_delimiter = opt.is_set(ArgSettings::RequireDelimiter);
|
|
||||||
let multiple = opt.is_set(ArgSettings::MultipleValues);
|
|
||||||
// @TODO @soundness: if doesn't have an equal, but requires equal is ValuesDone?!
|
|
||||||
if no_val && min_vals_zero && require_equals {
|
|
||||||
debug!("Parser::parse_opt: More arg vals not required...");
|
|
||||||
Ok(ParseResult::ValuesDone)
|
Ok(ParseResult::ValuesDone)
|
||||||
} else if no_val
|
} else {
|
||||||
|| (multiple && !needs_delimiter) && !has_eq && matcher.needs_more_vals(opt)
|
|
||||||
{
|
|
||||||
debug!("Parser::parse_opt: More arg vals required...");
|
debug!("Parser::parse_opt: More arg vals required...");
|
||||||
|
self.inc_occurrence_of_arg(matcher, opt);
|
||||||
matcher.new_val_group(&opt.id);
|
matcher.new_val_group(&opt.id);
|
||||||
for group in self.app.groups_for_arg(&opt.id) {
|
for group in self.app.groups_for_arg(&opt.id) {
|
||||||
matcher.new_val_group(&group);
|
matcher.new_val_group(&group);
|
||||||
}
|
}
|
||||||
Ok(ParseResult::Opt(opt.id.clone()))
|
Ok(ParseResult::Opt(opt.id.clone()))
|
||||||
} else {
|
|
||||||
debug!("Parser::parse_opt: More arg vals not required...");
|
|
||||||
Ok(ParseResult::ValuesDone)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1338,8 +1319,13 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
} else {
|
} else {
|
||||||
arg_split.collect()
|
arg_split.collect()
|
||||||
};
|
};
|
||||||
let vals = vals.into_iter().map(|x| x.into_os_string()).collect();
|
self.add_multiple_vals_to_arg(
|
||||||
self.add_multiple_vals_to_arg(arg, vals, matcher, ty, append);
|
arg,
|
||||||
|
vals.into_iter().map(|x| x.into_os_string()),
|
||||||
|
matcher,
|
||||||
|
ty,
|
||||||
|
append,
|
||||||
|
);
|
||||||
// If there was a delimiter used or we must use the delimiter to
|
// If there was a delimiter used or we must use the delimiter to
|
||||||
// separate the values or no more vals is needed, we're not
|
// separate the values or no more vals is needed, we're not
|
||||||
// looking for more values.
|
// looking for more values.
|
||||||
|
@ -1369,7 +1355,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
fn add_multiple_vals_to_arg(
|
fn add_multiple_vals_to_arg(
|
||||||
&self,
|
&self,
|
||||||
arg: &Arg<'help>,
|
arg: &Arg<'help>,
|
||||||
vals: Vec<OsString>,
|
vals: impl Iterator<Item = OsString>,
|
||||||
matcher: &mut ArgMatcher,
|
matcher: &mut ArgMatcher,
|
||||||
ty: ValueType,
|
ty: ValueType,
|
||||||
append: bool,
|
append: bool,
|
||||||
|
@ -1538,8 +1524,13 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
} else {
|
} else {
|
||||||
debug!("Parser::add_value:iter:{}: wasn't used", arg.name);
|
debug!("Parser::add_value:iter:{}: wasn't used", arg.name);
|
||||||
|
|
||||||
let vals = arg.default_vals.iter().map(OsString::from).collect();
|
self.add_multiple_vals_to_arg(
|
||||||
self.add_multiple_vals_to_arg(arg, vals, matcher, ty, false);
|
arg,
|
||||||
|
arg.default_vals.iter().map(OsString::from),
|
||||||
|
matcher,
|
||||||
|
ty,
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -1561,12 +1552,13 @@ impl<'help, 'app> Parser<'help, 'app> {
|
||||||
"Parser::add_value:iter:{}: has no user defined vals",
|
"Parser::add_value:iter:{}: has no user defined vals",
|
||||||
arg.name
|
arg.name
|
||||||
);
|
);
|
||||||
let vals = arg
|
self.add_multiple_vals_to_arg(
|
||||||
.default_missing_vals
|
arg,
|
||||||
.iter()
|
arg.default_missing_vals.iter().map(OsString::from),
|
||||||
.map(OsString::from)
|
matcher,
|
||||||
.collect();
|
ty,
|
||||||
self.add_multiple_vals_to_arg(arg, vals, matcher, ty, false);
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
debug!("Parser::add_value:iter:{}: wasn't used", arg.name);
|
debug!("Parser::add_value:iter:{}: wasn't used", arg.name);
|
||||||
|
|
|
@ -1022,6 +1022,38 @@ fn issue_1643_args_mutually_require_each_other() {
|
||||||
app.get_matches_from(&["test", "-u", "hello", "-r", "farewell"]);
|
app.get_matches_from(&["test", "-u", "hello", "-r", "farewell"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn short_flag_require_equals_with_minvals_zero() {
|
||||||
|
let m = App::new("foo")
|
||||||
|
.arg(
|
||||||
|
Arg::new("check")
|
||||||
|
.short('c')
|
||||||
|
.min_values(0)
|
||||||
|
.require_equals(true),
|
||||||
|
)
|
||||||
|
.arg(Arg::new("unique").short('u'))
|
||||||
|
.get_matches_from(&["foo", "-cu"]);
|
||||||
|
assert!(m.is_present("check"));
|
||||||
|
assert!(m.is_present("unique"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn issue_2624() {
|
||||||
|
let matches = App::new("foo")
|
||||||
|
.arg(
|
||||||
|
Arg::new("check")
|
||||||
|
.short('c')
|
||||||
|
.long("check")
|
||||||
|
.require_equals(true)
|
||||||
|
.min_values(0)
|
||||||
|
.possible_values(&["silent", "quiet", "diagnose-first"]),
|
||||||
|
)
|
||||||
|
.arg(Arg::new("unique").short('u').long("unique"))
|
||||||
|
.get_matches_from(&["foo", "-cu"]);
|
||||||
|
assert!(matches.is_present("check"));
|
||||||
|
assert!(matches.is_present("unique"));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic = "Argument or group 'extra' specified in 'requires*' for 'config' does not exist"]
|
#[should_panic = "Argument or group 'extra' specified in 'requires*' for 'config' does not exist"]
|
||||||
|
|
Loading…
Reference in a new issue