mirror of
https://github.com/clap-rs/clap
synced 2025-03-04 15:27:16 +00:00
setting(InferSubcommands): adds a setting to allow one to infer shortened subcommands or aliases (i.e. for subcommmand "test", "t", "te", or "tes" would be allowed assuming no other ambiguities)
Closes #863
This commit is contained in:
parent
8adf353e0b
commit
150756b989
7 changed files with 282 additions and 122 deletions
|
@ -149,6 +149,8 @@ macro_rules! parse_positional {
|
|||
let _ = $_self.groups_for_arg($p.b.name)
|
||||
.and_then(|vec| Some($matcher.inc_occurrences_of(&*vec)));
|
||||
arg_post_processing!($_self, $p, $matcher);
|
||||
|
||||
$_self.set(AS::ValidArgFound);
|
||||
// Only increment the positional counter if it doesn't allow multiples
|
||||
if !$p.b.settings.is_set(ArgSettings::Multiple) {
|
||||
$pos_counter += 1;
|
||||
|
|
|
@ -534,6 +534,9 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_args(&self) -> bool { !(self.flags.is_empty() && self.opts.is_empty() && self.positionals.is_empty()) }
|
||||
|
||||
#[inline]
|
||||
pub fn has_opts(&self) -> bool { !self.opts.is_empty() }
|
||||
|
||||
|
@ -665,25 +668,50 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
}
|
||||
|
||||
// Checks if the arg matches a subcommand name, or any of it's aliases (if defined)
|
||||
#[inline]
|
||||
fn possible_subcommand(&self, arg_os: &OsStr) -> bool {
|
||||
fn possible_subcommand(&self, arg_os: &OsStr) -> (bool, Option<&str>) {
|
||||
debugln!("Parser::possible_subcommand: arg={:?}", arg_os);
|
||||
if self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound) {
|
||||
return false;
|
||||
fn starts(h: &str, n: &OsStr) -> bool {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
#[cfg(target_os = "windows")]
|
||||
use ossstringext::OsStrExt3;
|
||||
|
||||
let n_bytes = n.as_bytes();
|
||||
let h_bytes = OsStr::new(h).as_bytes();
|
||||
|
||||
h_bytes.starts_with(n_bytes)
|
||||
}
|
||||
self.subcommands
|
||||
.iter()
|
||||
.any(|s| {
|
||||
&s.p.meta.name[..] == &*arg_os ||
|
||||
(s.p.meta.aliases.is_some() &&
|
||||
s.p
|
||||
.meta
|
||||
.aliases
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|&(a, _)| a == &*arg_os))
|
||||
})
|
||||
|
||||
if self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound) {
|
||||
return (false, None);
|
||||
}
|
||||
if !self.is_set(AS::InferSubcommands) {
|
||||
if let Some(sc) = find_subcmd!(self, arg_os) {
|
||||
return (true, Some(&sc.p.meta.name));
|
||||
}
|
||||
} else {
|
||||
let v = self.subcommands
|
||||
.iter()
|
||||
.filter(|s| {
|
||||
starts(&s.p.meta.name[..], &*arg_os) ||
|
||||
(s.p.meta.aliases.is_some() &&
|
||||
s.p
|
||||
.meta
|
||||
.aliases
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|&&(a, _)| starts(a, &*arg_os))
|
||||
.count() == 1)
|
||||
})
|
||||
.map(|sc| &sc.p.meta.name)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if v.len() == 1 {
|
||||
return (true, Some(v[0]));
|
||||
}
|
||||
}
|
||||
return (false, None);
|
||||
}
|
||||
|
||||
fn parse_help_subcommand<I, T>(&self, it: &mut I) -> ClapResult<()>
|
||||
|
@ -786,29 +814,27 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
} else {
|
||||
false
|
||||
};
|
||||
debugln!("Parser::is_new_arg: Does arg allow leading hyphen...{:?}",
|
||||
arg_allows_tac);
|
||||
debugln!("Parser::is_new_arg: Arg::allow_leading_hyphen({:?})", arg_allows_tac);
|
||||
|
||||
// Is this a new argument, or values from a previous option?
|
||||
debug!("Parser::is_new_arg: Starts new arg...");
|
||||
let mut ret = if arg_os.starts_with(b"--") {
|
||||
sdebugln!("--");
|
||||
debugln!("Parser::is_new_arg: -- found");
|
||||
if arg_os.len_() == 2 {
|
||||
return true; // We have to return true so override everything else
|
||||
}
|
||||
true
|
||||
} else if arg_os.starts_with(b"-") {
|
||||
sdebugln!("-");
|
||||
debugln!("Parser::is_new_arg: - found");
|
||||
// a singe '-' by itself is a value and typically means "stdin" on unix systems
|
||||
!(arg_os.len_() == 1)
|
||||
} else {
|
||||
sdebugln!("Probable value");
|
||||
debugln!("Parser::is_new_arg: probably value");
|
||||
false
|
||||
};
|
||||
|
||||
ret = ret && !arg_allows_tac;
|
||||
|
||||
debugln!("Parser::is_new_arg: Starts new arg...{:?}", ret);
|
||||
debugln!("Parser::is_new_arg: starts_new_arg={:?}", ret);
|
||||
ret
|
||||
}
|
||||
|
||||
|
@ -824,6 +850,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
debugln!("Parser::get_matches_with;");
|
||||
// Verify all positional assertions pass
|
||||
self.verify_positionals();
|
||||
let has_args = self.has_args();
|
||||
|
||||
// Next we create the `--help` and `--version` arguments and add them if
|
||||
// necessary
|
||||
|
@ -845,12 +872,16 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
// Has the user already passed '--'? Meaning only positional args follow
|
||||
if !self.is_set(AS::TrailingValues) {
|
||||
// Does the arg match a subcommand name, or any of it's aliases (if defined)
|
||||
if self.possible_subcommand(&arg_os) {
|
||||
if &*arg_os == "help" && self.is_set(AS::NeedsSubcommandHelp) {
|
||||
try!(self.parse_help_subcommand(it));
|
||||
{
|
||||
let (is_match, sc_name) = self.possible_subcommand(&arg_os);
|
||||
if is_match {
|
||||
let sc_name = sc_name.expect(INTERNAL_ERROR_MSG);
|
||||
if sc_name == "help" && self.is_set(AS::NeedsSubcommandHelp) {
|
||||
try!(self.parse_help_subcommand(it));
|
||||
}
|
||||
subcmd_name = Some(sc_name.to_owned());
|
||||
break;
|
||||
}
|
||||
subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned());
|
||||
break;
|
||||
}
|
||||
|
||||
if !starts_new_arg {
|
||||
|
@ -868,6 +899,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
if arg_os.len_() == 2 {
|
||||
// The user has passed '--' which means only positional args follow no
|
||||
// matter what they start with
|
||||
debugln!("Parser::get_matches_with: found '--' setting TrailingVals=true");
|
||||
self.set(AS::TrailingValues);
|
||||
continue;
|
||||
}
|
||||
|
@ -900,11 +932,11 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
}
|
||||
}
|
||||
|
||||
if !self.is_set(AS::ArgsNegateSubcommands) {
|
||||
if !(self.is_set(AS::ArgsNegateSubcommands) &&
|
||||
self.is_set(AS::ValidArgFound)) &&
|
||||
!self.is_set(AS::InferSubcommands) {
|
||||
if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(),
|
||||
self.subcommands
|
||||
.iter()
|
||||
.map(|s| &s.p.meta.name)) {
|
||||
sc_names!(self)) {
|
||||
return Err(Error::invalid_subcommand(arg_os.to_string_lossy()
|
||||
.into_owned(),
|
||||
cdate,
|
||||
|
@ -920,15 +952,13 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
}
|
||||
|
||||
let low_index_mults = self.is_set(AS::LowIndexMultiplePositional) &&
|
||||
!self.positionals.is_empty() &&
|
||||
pos_counter == (self.positionals.len() - 1);
|
||||
let missing_pos = self.is_set(AS::AllowMissingPositional) &&
|
||||
!self.positionals.is_empty() &&
|
||||
pos_counter == (self.positionals.len() - 1);
|
||||
debugln!("Parser::get_matches_with: Low index multiples...{:?}",
|
||||
low_index_mults);
|
||||
debugln!("Parser::get_matches_with: Positional counter...{}",
|
||||
pos_counter);
|
||||
debugln!("Parser::get_matches_with: Low index multiples...{:?}",
|
||||
low_index_mults);
|
||||
if low_index_mults || missing_pos {
|
||||
if let Some(na) = it.peek() {
|
||||
let n = (*na).clone().into();
|
||||
|
@ -941,11 +971,12 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
} else {
|
||||
None
|
||||
};
|
||||
if self.is_new_arg(&n, needs_val_of) || self.possible_subcommand(&n) ||
|
||||
let sc_match = {
|
||||
self.possible_subcommand(&n).0
|
||||
};
|
||||
if self.is_new_arg(&n, needs_val_of) || sc_match ||
|
||||
suggestions::did_you_mean(&n.to_string_lossy(),
|
||||
self.subcommands
|
||||
.iter()
|
||||
.map(|s| &s.p.meta.name))
|
||||
sc_names!(self))
|
||||
.is_some() {
|
||||
debugln!("Parser::get_matches_with: Bumping the positional counter...");
|
||||
pos_counter += 1;
|
||||
|
@ -988,36 +1019,33 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
matches: sc_m.into(),
|
||||
});
|
||||
} else if !(self.is_set(AS::AllowLeadingHyphen) ||
|
||||
self.is_set(AS::AllowNegativeNumbers)) {
|
||||
self.is_set(AS::AllowNegativeNumbers)) && !self.is_set(AS::InferSubcommands) {
|
||||
return Err(Error::unknown_argument(&*arg_os.to_string_lossy(),
|
||||
"",
|
||||
&*self.create_current_usage(matcher, None),
|
||||
self.color()));
|
||||
} else if !(has_args) && self.has_subcommands() {
|
||||
if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(),
|
||||
sc_names!(self)) {
|
||||
return Err(Error::invalid_subcommand(arg_os.to_string_lossy()
|
||||
.into_owned(),
|
||||
cdate,
|
||||
self.meta
|
||||
.bin_name
|
||||
.as_ref()
|
||||
.unwrap_or(&self.meta.name),
|
||||
&*self.create_current_usage(matcher,
|
||||
None),
|
||||
self.color()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref pos_sc_name) = subcmd_name {
|
||||
// is this is a real subcommand, or an alias
|
||||
if self.subcommands.iter().any(|sc| &sc.p.meta.name == pos_sc_name) {
|
||||
try!(self.parse_subcommand(&*pos_sc_name, matcher, it));
|
||||
} else {
|
||||
let sc_name = &*self.subcommands
|
||||
.iter()
|
||||
.filter(|sc| sc.p.meta.aliases.is_some())
|
||||
.filter(|sc| {
|
||||
sc.p
|
||||
.meta
|
||||
.aliases
|
||||
.as_ref()
|
||||
.expect(INTERNAL_ERROR_MSG)
|
||||
.iter()
|
||||
.any(|&(a, _)| &a == &&*pos_sc_name)
|
||||
})
|
||||
.map(|sc| sc.p.meta.name.clone())
|
||||
.next()
|
||||
.expect(INTERNAL_ERROR_MSG);
|
||||
try!(self.parse_subcommand(sc_name, matcher, it));
|
||||
}
|
||||
let sc_name = {
|
||||
find_subcmd!(self, pos_sc_name).expect(INTERNAL_ERROR_MSG).p.meta.name.clone()
|
||||
};
|
||||
try!(self.parse_subcommand(&*sc_name, matcher, it));
|
||||
} else if self.is_set(AS::SubcommandRequired) {
|
||||
let bn = self.meta.bin_name.as_ref().unwrap_or(&self.meta.name);
|
||||
return Err(Error::missing_subcommand(bn,
|
||||
|
@ -1404,6 +1432,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
opt.to_string());
|
||||
self.settings.set(AS::ValidArgFound);
|
||||
let ret = try!(self.parse_opt(val, opt, val.is_some(), matcher));
|
||||
self.set(AS::ValidArgFound);
|
||||
arg_post_processing!(self, opt, matcher);
|
||||
|
||||
return Ok(ret);
|
||||
|
@ -1420,6 +1449,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
// Handle conflicts, requirements, etc.
|
||||
arg_post_processing!(self, flag, matcher);
|
||||
|
||||
self.set(AS::ValidArgFound);
|
||||
return Ok(None);
|
||||
} else if self.is_set(AS::AllowLeadingHyphen) {
|
||||
return Ok(None);
|
||||
|
@ -1489,6 +1519,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
|
||||
arg_post_processing!(self, opt, matcher);
|
||||
|
||||
self.set(AS::ValidArgFound);
|
||||
return Ok(ret);
|
||||
} else if let Some(flag) = find_flag_by_short!(self, c) {
|
||||
debugln!("Parser::parse_short_arg:iter: Found valid short flag -{}",
|
||||
|
@ -1497,6 +1528,8 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
// Only flags can be help or version
|
||||
try!(self.check_for_help_and_version_char(c));
|
||||
try!(self.parse_flag(flag, matcher));
|
||||
|
||||
self.set(AS::ValidArgFound);
|
||||
// Handle conflicts, requirements, overrides, etc.
|
||||
// Must be called here due to mutablilty
|
||||
arg_post_processing!(self, flag, matcher);
|
||||
|
@ -2264,6 +2297,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
None
|
||||
}
|
||||
|
||||
// Only used for completion scripts due to bin_name messiness
|
||||
#[cfg_attr(feature = "lints", allow(explicit_iter_loop))]
|
||||
pub fn find_subcommand(&'b self, sc: &str) -> Option<&'b App<'a, 'b>> {
|
||||
debugln!("Parser::find_subcommand: sc={}", sc);
|
||||
|
|
|
@ -5,44 +5,45 @@ use std::ops::BitOr;
|
|||
|
||||
bitflags! {
|
||||
flags Flags: u64 {
|
||||
const SC_NEGATE_REQS = 0b00000000000000000000000000000000000001,
|
||||
const SC_REQUIRED = 0b00000000000000000000000000000000000010,
|
||||
const A_REQUIRED_ELSE_HELP = 0b00000000000000000000000000000000000100,
|
||||
const GLOBAL_VERSION = 0b00000000000000000000000000000000001000,
|
||||
const VERSIONLESS_SC = 0b00000000000000000000000000000000010000,
|
||||
const UNIFIED_HELP = 0b00000000000000000000000000000000100000,
|
||||
const WAIT_ON_ERROR = 0b00000000000000000000000000000001000000,
|
||||
const SC_REQUIRED_ELSE_HELP= 0b00000000000000000000000000000010000000,
|
||||
const NEEDS_LONG_HELP = 0b00000000000000000000000000000100000000,
|
||||
const NEEDS_LONG_VERSION = 0b00000000000000000000000000001000000000,
|
||||
const NEEDS_SC_HELP = 0b00000000000000000000000000010000000000,
|
||||
const DISABLE_VERSION = 0b00000000000000000000000000100000000000,
|
||||
const HIDDEN = 0b00000000000000000000000001000000000000,
|
||||
const TRAILING_VARARG = 0b00000000000000000000000010000000000000,
|
||||
const NO_BIN_NAME = 0b00000000000000000000000100000000000000,
|
||||
const ALLOW_UNK_SC = 0b00000000000000000000001000000000000000,
|
||||
const UTF8_STRICT = 0b00000000000000000000010000000000000000,
|
||||
const UTF8_NONE = 0b00000000000000000000100000000000000000,
|
||||
const LEADING_HYPHEN = 0b00000000000000000001000000000000000000,
|
||||
const NO_POS_VALUES = 0b00000000000000000010000000000000000000,
|
||||
const NEXT_LINE_HELP = 0b00000000000000000100000000000000000000,
|
||||
const DERIVE_DISP_ORDER = 0b00000000000000001000000000000000000000,
|
||||
const COLORED_HELP = 0b00000000000000010000000000000000000000,
|
||||
const COLOR_ALWAYS = 0b00000000000000100000000000000000000000,
|
||||
const COLOR_AUTO = 0b00000000000001000000000000000000000000,
|
||||
const COLOR_NEVER = 0b00000000000010000000000000000000000000,
|
||||
const DONT_DELIM_TRAIL = 0b00000000000100000000000000000000000000,
|
||||
const ALLOW_NEG_NUMS = 0b00000000001000000000000000000000000000,
|
||||
const LOW_INDEX_MUL_POS = 0b00000000010000000000000000000000000000,
|
||||
const DISABLE_HELP_SC = 0b00000000100000000000000000000000000000,
|
||||
const DONT_COLLAPSE_ARGS = 0b00000001000000000000000000000000000000,
|
||||
const ARGS_NEGATE_SCS = 0b00000010000000000000000000000000000000,
|
||||
const PROPAGATE_VALS_DOWN = 0b00000100000000000000000000000000000000,
|
||||
const ALLOW_MISSING_POS = 0b00001000000000000000000000000000000000,
|
||||
const TRAILING_VALUES = 0b00010000000000000000000000000000000000,
|
||||
const VALID_NEG_NUM_FOUND = 0b00100000000000000000000000000000000000,
|
||||
const PROPOGATED = 0b01000000000000000000000000000000000000,
|
||||
const VALID_ARG_FOUND = 0b10000000000000000000000000000000000000
|
||||
const SC_NEGATE_REQS = 1 << 0,
|
||||
const SC_REQUIRED = 1 << 1,
|
||||
const A_REQUIRED_ELSE_HELP = 1 << 2,
|
||||
const GLOBAL_VERSION = 1 << 3,
|
||||
const VERSIONLESS_SC = 1 << 4,
|
||||
const UNIFIED_HELP = 1 << 5,
|
||||
const WAIT_ON_ERROR = 1 << 6,
|
||||
const SC_REQUIRED_ELSE_HELP= 1 << 7,
|
||||
const NEEDS_LONG_HELP = 1 << 8,
|
||||
const NEEDS_LONG_VERSION = 1 << 9,
|
||||
const NEEDS_SC_HELP = 1 << 10,
|
||||
const DISABLE_VERSION = 1 << 11,
|
||||
const HIDDEN = 1 << 12,
|
||||
const TRAILING_VARARG = 1 << 13,
|
||||
const NO_BIN_NAME = 1 << 14,
|
||||
const ALLOW_UNK_SC = 1 << 15,
|
||||
const UTF8_STRICT = 1 << 16,
|
||||
const UTF8_NONE = 1 << 17,
|
||||
const LEADING_HYPHEN = 1 << 18,
|
||||
const NO_POS_VALUES = 1 << 19,
|
||||
const NEXT_LINE_HELP = 1 << 20,
|
||||
const DERIVE_DISP_ORDER = 1 << 21,
|
||||
const COLORED_HELP = 1 << 22,
|
||||
const COLOR_ALWAYS = 1 << 23,
|
||||
const COLOR_AUTO = 1 << 24,
|
||||
const COLOR_NEVER = 1 << 25,
|
||||
const DONT_DELIM_TRAIL = 1 << 26,
|
||||
const ALLOW_NEG_NUMS = 1 << 27,
|
||||
const LOW_INDEX_MUL_POS = 1 << 28,
|
||||
const DISABLE_HELP_SC = 1 << 29,
|
||||
const DONT_COLLAPSE_ARGS = 1 << 30,
|
||||
const ARGS_NEGATE_SCS = 1 << 31,
|
||||
const PROPAGATE_VALS_DOWN = 1 << 32,
|
||||
const ALLOW_MISSING_POS = 1 << 33,
|
||||
const TRAILING_VALUES = 1 << 34,
|
||||
const VALID_NEG_NUM_FOUND = 1 << 35,
|
||||
const PROPOGATED = 1 << 36,
|
||||
const VALID_ARG_FOUND = 1 << 37,
|
||||
const INFER_SUBCOMMANDS = 1 << 38,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +103,8 @@ impl AppFlags {
|
|||
TrailingValues => TRAILING_VALUES,
|
||||
ValidNegNumFound => VALID_NEG_NUM_FOUND,
|
||||
Propogated => PROPOGATED,
|
||||
ValidArgFound => VALID_ARG_FOUND
|
||||
ValidArgFound => VALID_ARG_FOUND,
|
||||
InferSubcommands => INFER_SUBCOMMANDS
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -525,6 +527,27 @@ pub enum AppSettings {
|
|||
/// This can be useful if there are many values, or they are explained elsewhere.
|
||||
HidePossibleValuesInHelp,
|
||||
|
||||
/// Tries to match unknown args to partial [`subcommands`] or their aliases. For example to
|
||||
/// match a subcommand 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 a subcommand or alias `temp` because both start with `te`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use clap::{App, Arg, SubCommand, AppSettings};
|
||||
/// let m = App::new("prog")
|
||||
/// .setting(AppSettings::InferSubcommands)
|
||||
/// .subcommand(SubCommand::with_name("test"))
|
||||
/// .get_matches_from(vec![
|
||||
/// "prog", "te"
|
||||
/// ]);
|
||||
/// assert_eq!(m.subcommand_name(), Some("test"));
|
||||
/// ```
|
||||
/// [`subcommands`]: ./struct.SubCommand.html
|
||||
InferSubcommands,
|
||||
|
||||
/// 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.
|
||||
|
@ -851,6 +874,7 @@ impl FromStr for AppSettings {
|
|||
"globalversion" => Ok(AppSettings::GlobalVersion),
|
||||
"hidden" => Ok(AppSettings::Hidden),
|
||||
"hidepossiblevaluesinhelp" => Ok(AppSettings::HidePossibleValuesInHelp),
|
||||
"infersubcommands" => Ok(AppSettings::InferSubcommands),
|
||||
"lowindexmultiplepositional" => Ok(AppSettings::LowIndexMultiplePositional),
|
||||
"nobinaryname" => Ok(AppSettings::NoBinaryName),
|
||||
"nextlinehelp" => Ok(AppSettings::NextLineHelp),
|
||||
|
@ -943,6 +967,8 @@ mod test {
|
|||
AppSettings::Propogated);
|
||||
assert_eq!("trailingvalues".parse::<AppSettings>().unwrap(),
|
||||
AppSettings::TrailingValues);
|
||||
assert_eq!("infersubcommands".parse::<AppSettings>().unwrap(),
|
||||
AppSettings::InferSubcommands);
|
||||
assert!("hahahaha".parse::<AppSettings>().is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,20 +4,20 @@ use std::str::FromStr;
|
|||
|
||||
bitflags! {
|
||||
flags Flags: u16 {
|
||||
const REQUIRED = 0b00000000000001,
|
||||
const MULTIPLE = 0b00000000000010,
|
||||
const EMPTY_VALS = 0b00000000000100,
|
||||
const GLOBAL = 0b00000000001000,
|
||||
const HIDDEN = 0b00000000010000,
|
||||
const TAKES_VAL = 0b00000000100000,
|
||||
const USE_DELIM = 0b00000001000000,
|
||||
const NEXT_LINE_HELP = 0b00000010000000,
|
||||
const R_UNLESS_ALL = 0b00000100000000,
|
||||
const REQ_DELIM = 0b00001000000000,
|
||||
const DELIM_NOT_SET = 0b00010000000000,
|
||||
const HIDE_POS_VALS = 0b00100000000000,
|
||||
const ALLOW_TAC_VALS = 0b01000000000000,
|
||||
const REQUIRE_EQUALS = 0b10000000000000,
|
||||
const REQUIRED = 1 << 0,
|
||||
const MULTIPLE = 1 << 1,
|
||||
const EMPTY_VALS = 1 << 2,
|
||||
const GLOBAL = 1 << 3,
|
||||
const HIDDEN = 1 << 4,
|
||||
const TAKES_VAL = 1 << 5,
|
||||
const USE_DELIM = 1 << 6,
|
||||
const NEXT_LINE_HELP = 1 << 7,
|
||||
const R_UNLESS_ALL = 1 << 8,
|
||||
const REQ_DELIM = 1 << 9,
|
||||
const DELIM_NOT_SET = 1 << 10,
|
||||
const HIDE_POS_VALS = 1 << 11,
|
||||
const ALLOW_TAC_VALS = 1 << 12,
|
||||
const REQUIRE_EQUALS = 1 << 13,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -989,7 +989,7 @@ macro_rules! find_subcmd {
|
|||
$_self.subcommands
|
||||
.iter()
|
||||
.find(|s| {
|
||||
s.p.meta.name == $sc ||
|
||||
&*s.p.meta.name == $sc ||
|
||||
(s.p.meta.aliases.is_some() &&
|
||||
s.p
|
||||
.meta
|
||||
|
@ -1029,12 +1029,18 @@ macro_rules! _shorts_longs {
|
|||
|
||||
macro_rules! arg_names {
|
||||
($_self:ident) => {{
|
||||
_names!($_self)
|
||||
_names!(@args $_self)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! sc_names {
|
||||
($_self:ident) => {{
|
||||
_names!(@sc $_self)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! _names {
|
||||
($_self:ident) => {{
|
||||
(@args $_self:ident) => {{
|
||||
$_self.flags
|
||||
.iter()
|
||||
.map(|f| &*f.b.name)
|
||||
|
@ -1043,4 +1049,15 @@ macro_rules! _names {
|
|||
.chain($_self.positionals.values()
|
||||
.map(|p| &*p.b.name)))
|
||||
}};
|
||||
(@sc $_self:ident) => {{
|
||||
$_self.subcommands
|
||||
.iter()
|
||||
.map(|s| &*s.p.meta.name)
|
||||
.chain($_self.subcommands
|
||||
.iter()
|
||||
.filter(|s| s.p.meta.aliases.is_some())
|
||||
.flat_map(|s| s.p.meta.aliases.as_ref().unwrap().iter().map(|&(n, _)| n)))
|
||||
// .map(|n| &n)
|
||||
|
||||
}}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use fmt::Format;
|
|||
#[cfg(feature = "suggestions")]
|
||||
#[cfg_attr(feature = "lints", allow(needless_lifetimes))]
|
||||
pub fn did_you_mean<'a, T, I>(v: &str, possible_values: I) -> Option<&'a str>
|
||||
where T: AsRef<str> + 'a,
|
||||
where T: AsRef<str> + 'a + ?Sized,
|
||||
I: IntoIterator<Item = &'a T>
|
||||
{
|
||||
|
||||
|
|
|
@ -110,6 +110,83 @@ fn arg_required_else_help() {
|
|||
assert_eq!(err.kind, ErrorKind::MissingArgumentOrSubcommand);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_subcommands_fail_no_args() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from_safe(vec![
|
||||
"prog", "te"
|
||||
]);
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidSubcommand);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_subcommands_fail_with_args() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.arg(Arg::with_name("some"))
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from_safe(vec![
|
||||
"prog", "t"
|
||||
]);
|
||||
assert!(m.is_ok(), "{:?}", m.unwrap_err().kind);
|
||||
assert_eq!(m.unwrap().value_of("some"), Some("t"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_subcommands_fail_with_args2() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.arg(Arg::with_name("some"))
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from_safe(vec![
|
||||
"prog", "te"
|
||||
]);
|
||||
assert!(m.is_ok(), "{:?}", m.unwrap_err().kind);
|
||||
assert_eq!(m.unwrap().value_of("some"), Some("te"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_subcommands_pass() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.get_matches_from(vec![
|
||||
"prog", "te"
|
||||
]);
|
||||
assert_eq!(m.subcommand_name(), Some("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_subcommands_pass_close() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from(vec![
|
||||
"prog", "tes"
|
||||
]);
|
||||
assert_eq!(m.subcommand_name(), Some("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_subcommands_fail_suggestions() {
|
||||
let m = App::new("prog")
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.subcommand(SubCommand::with_name("test"))
|
||||
.subcommand(SubCommand::with_name("temp"))
|
||||
.get_matches_from_safe(vec![
|
||||
"prog", "temps"
|
||||
]);
|
||||
assert!(m.is_err());
|
||||
assert_eq!(m.unwrap_err().kind, ErrorKind::InvalidSubcommand);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_bin_name() {
|
||||
let result = App::new("arg_required")
|
||||
|
@ -452,7 +529,9 @@ fn propagate_vals_down() {
|
|||
.setting(AppSettings::PropagateGlobalValuesDown)
|
||||
.arg(Arg::from_usage("[cmd] 'command to run'").global(true))
|
||||
.subcommand(SubCommand::with_name("foo"))
|
||||
.get_matches_from(vec!["myprog", "set", "foo"]);
|
||||
.get_matches_from_safe(vec!["myprog", "set", "foo"]);
|
||||
assert!(m.is_ok(), "{:?}", m.unwrap_err().kind);
|
||||
let m = m.unwrap();
|
||||
assert_eq!(m.value_of("cmd"), Some("set"));
|
||||
let sub_m = m.subcommand_matches("foo").unwrap();
|
||||
assert_eq!(sub_m.value_of("cmd"), Some("set"));
|
||||
|
@ -464,7 +543,9 @@ fn allow_missing_positional() {
|
|||
.setting(AppSettings::AllowMissingPositional)
|
||||
.arg(Arg::from_usage("[src] 'some file'").default_value("src"))
|
||||
.arg_from_usage("<dest> 'some file'")
|
||||
.get_matches_from(vec!["test", "file"]);
|
||||
.get_matches_from_safe(vec!["test", "file"]);
|
||||
assert!(m.is_ok(), "{:?}", m.unwrap_err().kind);
|
||||
let m = m.unwrap();
|
||||
assert_eq!(m.value_of("src"), Some("src"));
|
||||
assert_eq!(m.value_of("dest"), Some("file"));
|
||||
}
|
Loading…
Add table
Reference in a new issue