Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ArgPrecedenceOverSubcommand app setting #1834

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 107 additions & 41 deletions src/build/app/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,48 @@ use std::str::FromStr;

bitflags! {
struct Flags: u64 {
const SC_NEGATE_REQS = 1;
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 NO_AUTO_HELP = 1 << 8;
const NO_AUTO_VERSION = 1 << 9;
const DISABLE_VERSION = 1 << 10;
const HIDDEN = 1 << 11;
const TRAILING_VARARG = 1 << 12;
const NO_BIN_NAME = 1 << 13;
const ALLOW_UNK_SC = 1 << 14;
const UTF8_STRICT = 1 << 15;
const UTF8_NONE = 1 << 16;
const LEADING_HYPHEN = 1 << 17;
const NO_POS_VALUES = 1 << 18;
const NEXT_LINE_HELP = 1 << 19;
const DERIVE_DISP_ORDER = 1 << 20;
const COLORED_HELP = 1 << 21;
const COLOR_ALWAYS = 1 << 22;
const COLOR_AUTO = 1 << 23;
const COLOR_NEVER = 1 << 24;
const DONT_DELIM_TRAIL = 1 << 25;
const ALLOW_NEG_NUMS = 1 << 26;
const LOW_INDEX_MUL_POS = 1 << 27;
const DISABLE_HELP_SC = 1 << 28;
const DONT_COLLAPSE_ARGS = 1 << 29;
const ARGS_NEGATE_SCS = 1 << 30;
const PROPAGATE_VALS_DOWN = 1 << 31;
const ALLOW_MISSING_POS = 1 << 32;
const TRAILING_VALUES = 1 << 33;
const VALID_NEG_NUM_FOUND = 1 << 34;
const BUILT = 1 << 35;
const VALID_ARG_FOUND = 1 << 36;
const INFER_SUBCOMMANDS = 1 << 37;
const CONTAINS_LAST = 1 << 38;
const ARGS_OVERRIDE_SELF = 1 << 39;
const HELP_REQUIRED = 1 << 40;
const SC_NEGATE_REQS = 1;
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 NO_AUTO_HELP = 1 << 8;
const NO_AUTO_VERSION = 1 << 9;
const DISABLE_VERSION = 1 << 10;
const HIDDEN = 1 << 11;
const TRAILING_VARARG = 1 << 12;
const NO_BIN_NAME = 1 << 13;
const ALLOW_UNK_SC = 1 << 14;
const UTF8_STRICT = 1 << 15;
const UTF8_NONE = 1 << 16;
const LEADING_HYPHEN = 1 << 17;
const NO_POS_VALUES = 1 << 18;
const NEXT_LINE_HELP = 1 << 19;
const DERIVE_DISP_ORDER = 1 << 20;
const COLORED_HELP = 1 << 21;
const COLOR_ALWAYS = 1 << 22;
const COLOR_AUTO = 1 << 23;
const COLOR_NEVER = 1 << 24;
const DONT_DELIM_TRAIL = 1 << 25;
const ALLOW_NEG_NUMS = 1 << 26;
const LOW_INDEX_MUL_POS = 1 << 27;
const DISABLE_HELP_SC = 1 << 28;
const DONT_COLLAPSE_ARGS = 1 << 29;
const ARGS_NEGATE_SCS = 1 << 30;
const PROPAGATE_VALS_DOWN = 1 << 31;
const ALLOW_MISSING_POS = 1 << 32;
const TRAILING_VALUES = 1 << 33;
const VALID_NEG_NUM_FOUND = 1 << 34;
const BUILT = 1 << 35;
const VALID_ARG_FOUND = 1 << 36;
const INFER_SUBCOMMANDS = 1 << 37;
const CONTAINS_LAST = 1 << 38;
const ARGS_OVERRIDE_SELF = 1 << 39;
const HELP_REQUIRED = 1 << 40;
const SUBCOMMAND_PRECEDENCE_OVER_ARG = 1 << 41;
}
}

Expand All @@ -69,6 +70,8 @@ impl Default for AppFlags {
impl_settings! { AppSettings, AppFlags,
ArgRequiredElseHelp("argrequiredelsehelp")
=> Flags::A_REQUIRED_ELSE_HELP,
SubcommandPrecedenceOverArg("subcommandprecedenceoverarg")
=> Flags::SUBCOMMAND_PRECEDENCE_OVER_ARG,
ArgsNegateSubcommands("argsnegatesubcommands")
=> Flags::ARGS_NEGATE_SCS,
AllowExternalSubcommands("allowexternalsubcommands")
Expand Down Expand Up @@ -436,6 +439,63 @@ pub enum AppSettings {
/// [`Arg::default_value`]: ./struct.Arg.html#method.default_value
ArgRequiredElseHelp,

/// Instructs the parser to stop when encountering a subcommand instead of greedily consuming
/// args.
///
/// By default, if an option taking multiple values is followed by a subcommand, the
/// subcommand will be parsed as another value.
///
/// ```text
/// app --foo val1 val2 subcommand
/// --------- ----------
/// values another value
/// ```
///
/// This setting instructs the parser to stop when encountering a subcommand instead of
/// greedily consuming arguments.
///
/// ```text
/// app --foo val1 val2 subcommand
/// --------- ----------
/// values subcommand
/// ```
///
/// **Note:** Make sure you apply it as `global_setting` if you want it to be propagated to
/// sub-sub commands!
///
/// # Examples
///
/// ```rust
/// # use clap::{App, AppSettings, Arg};
/// let app = App::new("app").subcommand(App::new("sub")).arg(
/// Arg::with_name("arg")
/// .long("arg")
/// .multiple(true)
/// .takes_value(true),
/// );
///
/// let matches = app
/// .clone()
/// .try_get_matches_from(&["app", "--arg", "1", "2", "3", "sub"])
/// .unwrap();
/// assert_eq!(
/// matches.values_of("arg").unwrap().collect::<Vec<_>>(),
/// &["1", "2", "3", "sub"]
/// );
/// assert!(matches.subcommand_matches("sub").is_none());
///
/// let app = app.setting(AppSettings::SubcommandPrecedenceOverArg);
/// let matches = app
/// .try_get_matches_from(&["app", "--arg", "1", "2", "3", "sub"])
/// .unwrap();
/// assert_eq!(
/// matches.values_of("arg").unwrap().collect::<Vec<_>>(),
/// &["1", "2", "3"]
/// );
/// assert!(matches.subcommand_matches("sub").is_some());
/// ```
SubcommandPrecedenceOverArg,

/// Uses colorized help messages.
///
/// **NOTE:** Must be compiled with the `color` cargo feature
Expand Down Expand Up @@ -988,6 +1048,12 @@ mod test {
"argrequiredelsehelp".parse::<AppSettings>().unwrap(),
AppSettings::ArgRequiredElseHelp
);
assert_eq!(
"subcommandprecedenceoverarg"
.parse::<AppSettings>()
.unwrap(),
AppSettings::SubcommandPrecedenceOverArg
);
assert_eq!(
"allowexternalsubcommands".parse::<AppSettings>().unwrap(),
AppSettings::AllowExternalSubcommands
Expand Down
3 changes: 2 additions & 1 deletion src/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,8 @@ where
if !self.is_set(AS::TrailingValues) {
// Does the arg match a subcommand name, or any of it's aliases (if defined)
match needs_val_of {
ParseResult::Opt(_) | ParseResult::Pos(_) => (),
ParseResult::Opt(_) | ParseResult::Pos(_)
if !self.is_set(AS::SubcommandPrecedenceOverArg) => {}
_ => {
let sc_name = self.possible_subcommand(arg_os);
debugln!(
Expand Down