Merge pull request #2525 from tertsdiepraam/infer-long-args

Infer long arguments
This commit is contained in:
Pavan Kumar Sunkara 2021-06-06 20:55:10 +01:00 committed by GitHub
commit dc8d0f1b08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 80 additions and 5 deletions

View file

@ -50,6 +50,7 @@ bitflags! {
const DISABLE_HELP_FLAG = 1 << 42;
const USE_LONG_FORMAT_FOR_HELP_SC = 1 << 43;
const DISABLE_ENV = 1 << 44;
const INFER_LONG_ARGS = 1 << 45;
}
}
@ -153,7 +154,9 @@ impl_settings! { AppSettings, AppFlags,
InferSubcommands("infersubcommands")
=> Flags::INFER_SUBCOMMANDS,
AllArgsOverrideSelf("allargsoverrideself")
=> Flags::ARGS_OVERRIDE_SELF
=> Flags::ARGS_OVERRIDE_SELF,
InferLongArgs("inferlongargs")
=> Flags::INFER_LONG_ARGS
}
/// Application level settings, which affect how [`App`] operates
@ -826,6 +829,14 @@ pub enum AppSettings {
/// [aliases]: App::alias()
InferSubcommands,
/// Tries to match unknown args to partial long arguments or their [aliases]. For example, to
/// match an argument named `--test`, one could use `--t`, `--te`, `--tes`, and `--test`.
///
/// **NOTE:** The match *must not* be ambiguous at all in order to succeed. i.e. to match
/// `--te` to `--test` there could not also be another argument or alias `--temp` because both
/// start with `--te`
InferLongArgs,
/// Specifies that the parser should not assume the first argument passed is the binary name.
/// This is normally the case when using a "daemon" style mode, or an interactive CLI where one
/// one would not normally type the binary or program name for each command.

View file

@ -1041,6 +1041,7 @@ impl<'help, 'app> Parser<'help, 'app> {
debug!("No");
(long_arg, None)
};
if let Some(opt) = self.app.args.get(&arg.to_os_string()) {
debug!(
"Parser::parse_long_arg: Found valid opt or flag '{}'",
@ -1049,12 +1050,45 @@ impl<'help, 'app> Parser<'help, 'app> {
self.app.settings.set(AS::ValidArgFound);
self.seen.push(opt.id.clone());
if opt.is_set(ArgSettings::TakesValue) {
Ok(self.parse_opt(&val, opt, matcher)?)
return self.parse_opt(&val, opt, matcher);
} else {
self.check_for_help_and_version_str(&arg)?;
Ok(self.parse_flag(opt, matcher))
return Ok(self.parse_flag(opt, matcher));
}
} else if let Some(sc_name) = self.possible_long_flag_subcommand(&arg) {
}
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) {
Ok(ParseResult::FlagSubCommand(sc_name.to_string()))
} else if self.is_set(AS::AllowLeadingHyphen) {
Ok(ParseResult::MaybeHyphenValue)

View file

@ -1,6 +1,6 @@
mod utils;
use clap::{App, Arg, ArgMatches, ArgSettings, ErrorKind};
use clap::{App, AppSettings, Arg, ArgMatches, ArgSettings, ErrorKind};
#[cfg(feature = "suggestions")]
static DYM: &str =
@ -577,3 +577,33 @@ fn issue_2279() {
assert_eq!(after_help_heading.value_of("foo"), Some("bar"));
}
#[test]
fn infer_long_arg() {
let app = App::new("test")
.setting(AppSettings::InferLongArgs)
.arg(Arg::new("racetrack").long("racetrack").alias("autobahn"))
.arg(Arg::new("racecar").long("racecar").takes_value(true));
let matches = app.clone().get_matches_from(&["test", "--racec=hello"]);
assert!(!matches.is_present("racetrack"));
assert_eq!(matches.value_of("racecar"), Some("hello"));
let matches = app.clone().get_matches_from(&["test", "--racet"]);
assert!(matches.is_present("racetrack"));
assert_eq!(matches.value_of("racecar"), None);
let matches = app.clone().get_matches_from(&["test", "--auto"]);
assert!(matches.is_present("racetrack"));
assert_eq!(matches.value_of("racecar"), None);
let app = App::new("test")
.setting(AppSettings::InferLongArgs)
.arg(Arg::new("arg").long("arg"));
let matches = app.clone().get_matches_from(&["test", "--"]);
assert!(!matches.is_present("arg"));
let matches = app.clone().get_matches_from(&["test", "--a"]);
assert!(matches.is_present("arg"));
}