Skip to content

Commit

Permalink
setting: adds a setting to disable args being allowed between subcomm…
Browse files Browse the repository at this point in the history
…ands (`ArgsNegateSubcommands`)

Specifies that use of a valid [argument] negates [subcomands] being used after. By default
`clap` allows arguments between subcommands such as
`<cmd> [cmd_args] <cmd2> [cmd2_args] <cmd3> [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:

* `<cmd> <cmd2> <cmd3> [cmd3_args]`
* `<cmd> <cmd2> [cmd2_args]`
* `<cmd> [cmd_args]`

Closes #793
  • Loading branch information
kbknapp committed Dec 31, 2016
1 parent 25cbca4 commit 5e2af8c
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 40 deletions.
31 changes: 22 additions & 9 deletions src/app/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ pub struct Parser<'a, 'b>
settings: AppFlags,
pub g_settings: Vec<AppSettings>,
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> {
Expand All @@ -87,6 +89,7 @@ impl<'a, 'b> Default for Parser<'a, 'b> {
trailing_vals: false,
id: 0,
valid_neg_num: false,
valid_arg: false,
}
}
}
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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: {:?}",
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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,
}
}
}
89 changes: 58 additions & 31 deletions src/app/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand All @@ -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,
Expand Down Expand Up @@ -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> [cmd_args] <cmd2> [cmd2_args] <cmd3> [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:
///
/// * `<cmd> <cmd2> <cmd3> [cmd3_args]`
/// * `<cmd> <cmd2> [cmd2_args]`
/// * `<cmd> [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`.
///
Expand Down Expand Up @@ -707,6 +731,7 @@ impl FromStr for AppSettings {
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::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),
Expand Down Expand Up @@ -745,6 +770,8 @@ mod test {

#[test]
fn app_settings_fromstr() {
assert_eq!("argsnegatesubcommands".parse::<AppSettings>().unwrap(),
AppSettings::ArgsNegateSubcommands);
assert_eq!("argrequiredelsehelp".parse::<AppSettings>().unwrap(),
AppSettings::ArgRequiredElseHelp);
assert_eq!("allowexternalsubcommands".parse::<AppSettings>().unwrap(),
Expand Down

0 comments on commit 5e2af8c

Please sign in to comment.