From 5e2af8c96adb5ab75fa2d1536237ebcb41869494 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Fri, 30 Dec 2016 23:19:08 -0500 Subject: [PATCH] setting: adds a setting to disable args being allowed between subcommands (`ArgsNegateSubcommands`) Specifies that use of a valid [argument] negates [subcomands] being used after. By default `clap` allows arguments between subcommands such as ` [cmd_args] [cmd2_args] [cmd3_args]`. This setting disables that functionality and says that arguments can only follow the *final* subcommand. For instance using this setting makes only the following invocations possible: * ` [cmd3_args]` * ` [cmd2_args]` * ` [cmd_args]` Closes #793 --- src/app/parser.rs | 31 +++++++++++----- src/app/settings.rs | 89 +++++++++++++++++++++++++++++---------------- 2 files changed, 80 insertions(+), 40 deletions(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index 743314d6..47e85564 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -59,9 +59,11 @@ pub struct Parser<'a, 'b> settings: AppFlags, pub g_settings: Vec, pub meta: AppMeta<'b>, - trailing_vals: bool, pub id: usize, + trailing_vals: bool, valid_neg_num: bool, + // have we found a valid arg yet + valid_arg: bool, } impl<'a, 'b> Default for Parser<'a, 'b> { @@ -87,6 +89,7 @@ impl<'a, 'b> Default for Parser<'a, 'b> { trailing_vals: false, id: 0, valid_neg_num: false, + valid_arg: false, } } } @@ -458,7 +461,8 @@ impl<'a, 'b> Parser<'a, 'b> .next() .expect(INTERNAL_ERROR_MSG); return Some(format!(" [{}]{}", p.name_no_brackets(), p.multiple_str())); - } else if self.is_set(AppSettings::DontCollapseArgsInUsage) && !self.positionals.is_empty() { + } else if self.is_set(AppSettings::DontCollapseArgsInUsage) && + !self.positionals.is_empty() { return Some(self.positionals .values() .filter(|p| !p.is_set(ArgSettings::Required)) @@ -608,6 +612,9 @@ impl<'a, 'b> Parser<'a, 'b> #[inline] fn possible_subcommand(&self, arg_os: &OsStr) -> bool { debugln!("Parser::possible_subcommand;"); + if self.is_set(AppSettings::ArgsNegateSubcommands) && self.valid_arg { + return false; + } self.subcommands .iter() .any(|s| { @@ -835,6 +842,8 @@ impl<'a, 'b> Parser<'a, 'b> } subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned()); break; + } else if self.is_set(AppSettings::ArgsNegateSubcommands) { + (); } else if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(), self.subcommands @@ -877,6 +886,7 @@ impl<'a, 'b> Parser<'a, 'b> } if let Some(p) = self.positionals.get(pos_counter) { parse_positional!(self, p, arg_os, pos_counter, matcher); + self.valid_arg = true; } else if self.settings.is_set(AppSettings::AllowExternalSubcommands) { // Get external subcommand name let sc_name = match arg_os.to_str() { @@ -922,17 +932,15 @@ impl<'a, 'b> Parser<'a, 'b> let sc_name = &*self.subcommands .iter() .filter(|sc| sc.p.meta.aliases.is_some()) - .filter_map(|sc| if sc.p + .filter(|sc| sc.p .meta .aliases .as_ref() - .unwrap() + .expect(INTERNAL_ERROR_MSG) .iter() - .any(|&(a, _)| &a == &&*pos_sc_name) { - Some(sc.p.meta.name.clone()) - } else { - None - }) + .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)); @@ -1321,12 +1329,14 @@ impl<'a, 'b> Parser<'a, 'b> if let Some(opt) = find_by_long!(self, &arg, opts) { debugln!("Parser::parse_long_arg: Found valid opt '{}'", opt.to_string()); + self.valid_arg = true; let ret = try!(self.parse_opt(val, opt, matcher)); arg_post_processing!(self, opt, matcher); return Ok(ret); } else if let Some(flag) = find_by_long!(self, &arg, flags) { debugln!("Parser::parse_long_arg: Found valid flag '{}'", flag.to_string()); + self.valid_arg = true; // Only flags could be help or version, and we need to check the raw long // so this is the first point to check try!(self.check_for_help_and_version_str(arg)); @@ -1383,6 +1393,7 @@ impl<'a, 'b> Parser<'a, 'b> .iter() .find(|&o| o.s.short.is_some() && o.s.short.unwrap() == c) { debugln!("Parser::parse_short_arg:iter: Found valid short opt -{} in '{}'", c, arg); + self.valid_arg = true; // Check for trailing concatenated value let p: Vec<_> = arg.splitn(2, c).collect(); debugln!("Parser::parse_short_arg:iter: arg: {:?}, arg_os: {:?}, full_arg: {:?}", @@ -1410,6 +1421,7 @@ impl<'a, 'b> Parser<'a, 'b> .iter() .find(|&f| f.s.short.is_some() && f.s.short.unwrap() == c) { debugln!("Parser::parse_short_arg:iter: Found valid short flag -{}", c); + self.valid_arg = true; // Only flags can be help or version try!(self.check_for_help_and_version_char(c)); try!(self.parse_flag(flag, matcher)); @@ -2164,6 +2176,7 @@ impl<'a, 'b> Clone for Parser<'a, 'b> id: self.id, valid_neg_num: self.valid_neg_num, r_ifs: self.r_ifs.clone(), + valid_arg: self.valid_arg, } } } diff --git a/src/app/settings.rs b/src/app/settings.rs index 2a3bd071..756c759d 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -4,37 +4,38 @@ use std::str::FromStr; bitflags! { flags Flags: u32 { - const SC_NEGATE_REQS = 0b0000000000000000000000000000001, - const SC_REQUIRED = 0b0000000000000000000000000000010, - const A_REQUIRED_ELSE_HELP = 0b0000000000000000000000000000100, - const GLOBAL_VERSION = 0b0000000000000000000000000001000, - const VERSIONLESS_SC = 0b0000000000000000000000000010000, - const UNIFIED_HELP = 0b0000000000000000000000000100000, - const WAIT_ON_ERROR = 0b0000000000000000000000001000000, - const SC_REQUIRED_ELSE_HELP= 0b0000000000000000000000010000000, - const NEEDS_LONG_HELP = 0b0000000000000000000000100000000, - const NEEDS_LONG_VERSION = 0b0000000000000000000001000000000, - const NEEDS_SC_HELP = 0b0000000000000000000010000000000, - const DISABLE_VERSION = 0b0000000000000000000100000000000, - const HIDDEN = 0b0000000000000000001000000000000, - const TRAILING_VARARG = 0b0000000000000000010000000000000, - const NO_BIN_NAME = 0b0000000000000000100000000000000, - const ALLOW_UNK_SC = 0b0000000000000001000000000000000, - const UTF8_STRICT = 0b0000000000000010000000000000000, - const UTF8_NONE = 0b0000000000000100000000000000000, - const LEADING_HYPHEN = 0b0000000000001000000000000000000, - const NO_POS_VALUES = 0b0000000000010000000000000000000, - const NEXT_LINE_HELP = 0b0000000000100000000000000000000, - const DERIVE_DISP_ORDER = 0b0000000001000000000000000000000, - const COLORED_HELP = 0b0000000010000000000000000000000, - const COLOR_ALWAYS = 0b0000000100000000000000000000000, - const COLOR_AUTO = 0b0000001000000000000000000000000, - const COLOR_NEVER = 0b0000010000000000000000000000000, - const DONT_DELIM_TRAIL = 0b0000100000000000000000000000000, - const ALLOW_NEG_NUMS = 0b0001000000000000000000000000000, - const LOW_INDEX_MUL_POS = 0b0010000000000000000000000000000, - const DISABLE_HELP_SC = 0b0100000000000000000000000000000, - const DONT_COLLAPSE_ARGS = 0b1000000000000000000000000000000, + const SC_NEGATE_REQS = 0b00000000000000000000000000000001, + const SC_REQUIRED = 0b00000000000000000000000000000010, + const A_REQUIRED_ELSE_HELP = 0b00000000000000000000000000000100, + const GLOBAL_VERSION = 0b00000000000000000000000000001000, + const VERSIONLESS_SC = 0b00000000000000000000000000010000, + const UNIFIED_HELP = 0b00000000000000000000000000100000, + const WAIT_ON_ERROR = 0b00000000000000000000000001000000, + const SC_REQUIRED_ELSE_HELP= 0b00000000000000000000000010000000, + const NEEDS_LONG_HELP = 0b00000000000000000000000100000000, + const NEEDS_LONG_VERSION = 0b00000000000000000000001000000000, + const NEEDS_SC_HELP = 0b00000000000000000000010000000000, + const DISABLE_VERSION = 0b00000000000000000000100000000000, + const HIDDEN = 0b00000000000000000001000000000000, + const TRAILING_VARARG = 0b00000000000000000010000000000000, + const NO_BIN_NAME = 0b00000000000000000100000000000000, + const ALLOW_UNK_SC = 0b00000000000000001000000000000000, + const UTF8_STRICT = 0b00000000000000010000000000000000, + const UTF8_NONE = 0b00000000000000100000000000000000, + const LEADING_HYPHEN = 0b00000000000001000000000000000000, + const NO_POS_VALUES = 0b00000000000010000000000000000000, + const NEXT_LINE_HELP = 0b00000000000100000000000000000000, + const DERIVE_DISP_ORDER = 0b00000000001000000000000000000000, + const COLORED_HELP = 0b00000000010000000000000000000000, + const COLOR_ALWAYS = 0b00000000100000000000000000000000, + const COLOR_AUTO = 0b00000001000000000000000000000000, + const COLOR_NEVER = 0b00000010000000000000000000000000, + const DONT_DELIM_TRAIL = 0b00000100000000000000000000000000, + const ALLOW_NEG_NUMS = 0b00001000000000000000000000000000, + const LOW_INDEX_MUL_POS = 0b00010000000000000000000000000000, + const DISABLE_HELP_SC = 0b00100000000000000000000000000000, + const DONT_COLLAPSE_ARGS = 0b01000000000000000000000000000000, + const ARGS_NEGATE_SCS = 0b10000000000000000000000000000000, } } @@ -57,6 +58,7 @@ impl AppFlags { impl_settings! { AppSettings, ArgRequiredElseHelp => A_REQUIRED_ELSE_HELP, + ArgsNegateSubcommands => ARGS_NEGATE_SCS, AllowExternalSubcommands => ALLOW_UNK_SC, AllowInvalidUtf8 => UTF8_NONE, AllowLeadingHyphen => LEADING_HYPHEN, @@ -223,6 +225,28 @@ pub enum AppSettings { /// [`ArgMatches`]: ./struct.ArgMatches.html AllowExternalSubcommands, + /// Specifies that use of a valid [argument] negates [subcomands] being used after. By default + /// `clap` allows arguments between subcommands such as + /// ` [cmd_args] [cmd2_args] [cmd3_args]`. This setting disables that + /// functionality and says that arguments can only follow the *final* subcommand. For instance + /// using this setting makes only the following invocations possible: + /// + /// * ` [cmd3_args]` + /// * ` [cmd2_args]` + /// * ` [cmd_args]` + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, AppSettings}; + /// App::new("myprog") + /// .setting(AppSettings::ArgsNegateSubcommands) + /// # ; + /// ``` + /// [subcommands]: ./struct.SubCommand.html + /// [argument]: ./struct.Arg.html + ArgsNegateSubcommands, + /// Specifies that the help text should be displayed (and then exit gracefully), /// if no arguments are present at runtime (i.e. an empty run such as, `$ myprog`. /// @@ -707,6 +731,7 @@ impl FromStr for AppSettings { fn from_str(s: &str) -> Result::Err> { match &*s.to_ascii_lowercase() { "argrequiredelsehelp" => Ok(AppSettings::ArgRequiredElseHelp), + "argsnegatesubcommands" => Ok(AppSettings::ArgsNegateSubcommands), "allowinvalidutf8" => Ok(AppSettings::AllowInvalidUtf8), "allowleadinghyphen" => Ok(AppSettings::AllowLeadingHyphen), "allowexternalsubcommands" => Ok(AppSettings::AllowExternalSubcommands), @@ -745,6 +770,8 @@ mod test { #[test] fn app_settings_fromstr() { + assert_eq!("argsnegatesubcommands".parse::().unwrap(), + AppSettings::ArgsNegateSubcommands); assert_eq!("argrequiredelsehelp".parse::().unwrap(), AppSettings::ArgRequiredElseHelp); assert_eq!("allowexternalsubcommands".parse::().unwrap(),