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:
Kevin K 2017-03-02 10:58:06 -05:00
parent 8adf353e0b
commit 150756b989
No known key found for this signature in database
GPG key ID: 17218E4B3692F01A
7 changed files with 282 additions and 122 deletions

View file

@ -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;

View file

@ -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);

View file

@ -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());
}
}

View file

@ -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,
}
}

View file

@ -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)
}}
}

View file

@ -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>
{

View file

@ -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"));
}