From 604fa547e0f5a7380b3e2fa51e9905bb4d083baa Mon Sep 17 00:00:00 2001 From: Kevin K Date: Thu, 2 Mar 2017 10:58:06 -0500 Subject: [PATCH] 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 --- src/app/macros.rs | 2 + src/app/parser.rs | 158 +++++++++++++++++++++++++----------------- src/app/settings.rs | 104 ++++++++++++++++----------- src/args/settings.rs | 28 ++++---- src/macros.rs | 23 +++++- src/suggestions.rs | 2 +- tests/app_settings.rs | 85 ++++++++++++++++++++++- 7 files changed, 281 insertions(+), 121 deletions(-) diff --git a/src/app/macros.rs b/src/app/macros.rs index 7224fa42479..c60ee342fb3 100644 --- a/src/app/macros.rs +++ b/src/app/macros.rs @@ -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; diff --git a/src/app/parser.rs b/src/app/parser.rs index 55fdfc4a247..a5947590c8a 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -524,6 +524,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() } @@ -655,25 +658,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); + 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) + } + if self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound) { - return false; + return (false, None); } - 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::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::>(); + + if v.len() == 1 { + return (true, Some(v[0])); + } + } + return (false, None); } fn parse_help_subcommand(&self, it: &mut I) -> ClapResult<()> @@ -776,29 +804,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 } @@ -814,6 +840,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 @@ -835,12 +862,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 { @@ -858,6 +889,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; } @@ -890,11 +922,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, @@ -910,15 +942,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(); @@ -931,11 +961,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; @@ -978,36 +1009,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, @@ -1394,6 +1422,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); @@ -1410,6 +1439,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); @@ -1479,6 +1509,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 -{}", @@ -1487,6 +1518,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); @@ -2254,6 +2287,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); diff --git a/src/app/settings.rs b/src/app/settings.rs index 42edc6105fc..3b7a326ba2c 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -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::().unwrap(), AppSettings::TrailingValues); + assert_eq!("infersubcommands".parse::().unwrap(), + AppSettings::InferSubcommands); assert!("hahahaha".parse::().is_err()); } } diff --git a/src/args/settings.rs b/src/args/settings.rs index d2a85561b26..28281dcebb1 100644 --- a/src/args/settings.rs +++ b/src/args/settings.rs @@ -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, } } diff --git a/src/macros.rs b/src/macros.rs index 2ce6d67972c..e54837411da 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -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! _names { +macro_rules! sc_names { ($_self:ident) => {{ + _names!(@sc $_self) + }}; +} + +macro_rules! _names { + (@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) + + }} } diff --git a/src/suggestions.rs b/src/suggestions.rs index d11c6646391..107e957932f 100644 --- a/src/suggestions.rs +++ b/src/suggestions.rs @@ -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 + 'a, + where T: AsRef + 'a + ?Sized, I: IntoIterator { diff --git a/tests/app_settings.rs b/tests/app_settings.rs index 17aa8bd8e91..f933b3bc2e6 100644 --- a/tests/app_settings.rs +++ b/tests/app_settings.rs @@ -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(" '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")); } \ No newline at end of file