From 6edde30b8e8350aa559971e5d8c804f2a6389520 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Tue, 3 Jan 2017 21:56:37 -0500 Subject: [PATCH] setting(AllowMissingPositional): allows one to implement `$ prog [optional] ` Closes #636 --- src/app/parser.rs | 58 +++++++++++++++++-------- src/app/settings.rs | 101 +++++++++++++++++++++++++++++--------------- 2 files changed, 109 insertions(+), 50 deletions(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index b3487817..c14d7a7b 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -546,10 +546,6 @@ impl<'a, 'b> Parser<'a, 'b> a.b.settings.is_set(ArgSettings::Multiple) && (a.index as usize != self.positionals.len()) }) { - debug_assert!(self.positionals.values() - .filter(|p| p.b.settings.is_set(ArgSettings::Multiple) - && p.v.num_vals.is_none()).map(|_| 1).sum::() <= 1, - "Only one positional argument with .multiple(true) set is allowed per command"); debug_assert!({ let mut it = self.positionals.values().rev(); @@ -581,17 +577,42 @@ impl<'a, 'b> Parser<'a, 'b> // If it's required we also need to ensure all previous positionals are // required too - let mut found = false; - for p in self.positionals.values().rev() { - if found { - debug_assert!(p.b.settings.is_set(ArgSettings::Required), - "Found positional argument which is not required with a lower index \ - than a required positional argument: {:?} index {}", - p.b.name, - p.index); - } else if p.b.settings.is_set(ArgSettings::Required) { - found = true; - continue; + if self.is_set(AppSettings::AllowMissingPositional) { + let mut found = false; + let mut foundx2 = false; + for p in self.positionals.values().rev() { + if foundx2 && !p.b.settings.is_set(ArgSettings::Required) { + // [arg1] is Ok + // [arg1] Is not + debug_assert!(p.b.settings.is_set(ArgSettings::Required), + "Found positional argument which is not required with a lower index \ + than a required positional argument by two or more: {:?} index {}", + p.b.name, + p.index); + } else if p.b.settings.is_set(ArgSettings::Required) { + if found { + foundx2 = true; + continue; + } + found = true; + continue; + } else { + found = false; + } + } + } else { + let mut found = false; + for p in self.positionals.values().rev() { + if found { + debug_assert!(p.b.settings.is_set(ArgSettings::Required), + "Found positional argument which is not required with a lower index \ + than a required positional argument: {:?} index {}", + p.b.name, + p.index); + } else if p.b.settings.is_set(ArgSettings::Required) { + found = true; + continue; + } } } } @@ -861,12 +882,15 @@ impl<'a, 'b> Parser<'a, 'b> let low_index_mults = self.is_set(AppSettings::LowIndexMultiplePositional) && !self.positionals.is_empty() && pos_counter == (self.positionals.len() - 1); + let missing_pos = self.is_set(AppSettings::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); - if low_index_mults { + if low_index_mults || missing_pos { if let Some(na) = it.peek() { let n = (*na).clone().into(); - needs_val_of = if let None = needs_val_of { + needs_val_of = if needs_val_of.is_none() { if let Some(p) = self.positionals.get(pos_counter) { Some(p.b.name) } else { diff --git a/src/app/settings.rs b/src/app/settings.rs index ab96d3ca..60481550 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -5,39 +5,40 @@ use std::ops::BitOr; bitflags! { flags Flags: u64 { - const SC_NEGATE_REQS = 0b000000000000000000000000000000001, - const SC_REQUIRED = 0b000000000000000000000000000000010, - const A_REQUIRED_ELSE_HELP = 0b000000000000000000000000000000100, - const GLOBAL_VERSION = 0b000000000000000000000000000001000, - const VERSIONLESS_SC = 0b000000000000000000000000000010000, - const UNIFIED_HELP = 0b000000000000000000000000000100000, - const WAIT_ON_ERROR = 0b000000000000000000000000001000000, - const SC_REQUIRED_ELSE_HELP= 0b000000000000000000000000010000000, - const NEEDS_LONG_HELP = 0b000000000000000000000000100000000, - const NEEDS_LONG_VERSION = 0b000000000000000000000001000000000, - const NEEDS_SC_HELP = 0b000000000000000000000010000000000, - const DISABLE_VERSION = 0b000000000000000000000100000000000, - const HIDDEN = 0b000000000000000000001000000000000, - const TRAILING_VARARG = 0b000000000000000000010000000000000, - const NO_BIN_NAME = 0b000000000000000000100000000000000, - const ALLOW_UNK_SC = 0b000000000000000001000000000000000, - const UTF8_STRICT = 0b000000000000000010000000000000000, - const UTF8_NONE = 0b000000000000000100000000000000000, - const LEADING_HYPHEN = 0b000000000000001000000000000000000, - const NO_POS_VALUES = 0b000000000000010000000000000000000, - const NEXT_LINE_HELP = 0b000000000000100000000000000000000, - const DERIVE_DISP_ORDER = 0b000000000001000000000000000000000, - const COLORED_HELP = 0b000000000010000000000000000000000, - const COLOR_ALWAYS = 0b000000000100000000000000000000000, - const COLOR_AUTO = 0b000000001000000000000000000000000, - const COLOR_NEVER = 0b000000010000000000000000000000000, - const DONT_DELIM_TRAIL = 0b000000100000000000000000000000000, - const ALLOW_NEG_NUMS = 0b000001000000000000000000000000000, - const LOW_INDEX_MUL_POS = 0b000010000000000000000000000000000, - const DISABLE_HELP_SC = 0b000100000000000000000000000000000, - const DONT_COLLAPSE_ARGS = 0b001000000000000000000000000000000, - const ARGS_NEGATE_SCS = 0b010000000000000000000000000000000, - const PROPAGATE_VALS_DOWN = 0b100000000000000000000000000000000, + const SC_NEGATE_REQS = 0b0000000000000000000000000000000001, + const SC_REQUIRED = 0b0000000000000000000000000000000010, + const A_REQUIRED_ELSE_HELP = 0b0000000000000000000000000000000100, + const GLOBAL_VERSION = 0b0000000000000000000000000000001000, + const VERSIONLESS_SC = 0b0000000000000000000000000000010000, + const UNIFIED_HELP = 0b0000000000000000000000000000100000, + const WAIT_ON_ERROR = 0b0000000000000000000000000001000000, + const SC_REQUIRED_ELSE_HELP= 0b0000000000000000000000000010000000, + const NEEDS_LONG_HELP = 0b0000000000000000000000000100000000, + const NEEDS_LONG_VERSION = 0b0000000000000000000000001000000000, + const NEEDS_SC_HELP = 0b0000000000000000000000010000000000, + const DISABLE_VERSION = 0b0000000000000000000000100000000000, + const HIDDEN = 0b0000000000000000000001000000000000, + const TRAILING_VARARG = 0b0000000000000000000010000000000000, + const NO_BIN_NAME = 0b0000000000000000000100000000000000, + const ALLOW_UNK_SC = 0b0000000000000000001000000000000000, + const UTF8_STRICT = 0b0000000000000000010000000000000000, + const UTF8_NONE = 0b0000000000000000100000000000000000, + const LEADING_HYPHEN = 0b0000000000000001000000000000000000, + const NO_POS_VALUES = 0b0000000000000010000000000000000000, + const NEXT_LINE_HELP = 0b0000000000000100000000000000000000, + const DERIVE_DISP_ORDER = 0b0000000000001000000000000000000000, + const COLORED_HELP = 0b0000000000010000000000000000000000, + const COLOR_ALWAYS = 0b0000000000100000000000000000000000, + const COLOR_AUTO = 0b0000000001000000000000000000000000, + const COLOR_NEVER = 0b0000000010000000000000000000000000, + const DONT_DELIM_TRAIL = 0b0000000100000000000000000000000000, + const ALLOW_NEG_NUMS = 0b0000001000000000000000000000000000, + const LOW_INDEX_MUL_POS = 0b0000010000000000000000000000000000, + const DISABLE_HELP_SC = 0b0000100000000000000000000000000000, + const DONT_COLLAPSE_ARGS = 0b0001000000000000000000000000000000, + const ARGS_NEGATE_SCS = 0b0010000000000000000000000000000000, + const PROPAGATE_VALS_DOWN = 0b0100000000000000000000000000000000, + const ALLOW_MISSING_POS = 0b1000000000000000000000000000000000, } } @@ -72,6 +73,7 @@ impl AppFlags { AllowInvalidUtf8 => UTF8_NONE, AllowLeadingHyphen => LEADING_HYPHEN, AllowNegativeNumbers => ALLOW_NEG_NUMS, + AllowMissingPositional => ALLOW_MISSING_POS, ColoredHelp => COLORED_HELP, ColorAlways => COLOR_ALWAYS, ColorAuto => COLOR_AUTO, @@ -199,6 +201,39 @@ pub enum AppSettings { /// [`AllowLeadingHyphen`]: ./enum.AppSettings.html#variant.AllowLeadingHyphen AllowNegativeNumbers, + /// Allows one to implement a CLI where the second to last positional argument is optional, but + /// the final positional argument is required. Such as `$ prog [optional] ` where one + /// of the two following usages is allowed: + /// + /// * `$ prog [optional] ` + /// * `$ prog ` + /// + /// This would otherwise not be allowed. This is useful when `[optional]` has a default value. + /// + /// **Note:** In addition to using this setting, the second positional argument *must* be + /// [required] + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, Arg, AppSettings}; + /// // Assume there is an external subcommand named "subcmd" + /// let m = App::new("myprog") + /// .setting(AppSettings::AllowMissingPositional) + /// .arg(Arg::with_name("arg1") + /// .default_value("something")) + /// .arg(Arg::with_name("arg2") + /// .required(true)) + /// .get_matches_from(vec![ + /// "myprog", "other" + /// ]); + /// + /// assert_eq!(m.value_of("arg1"), Some("something")); + /// assert_eq!(m.value_of("arg2"), Some("other")); + /// ``` + /// [required]: ./struct.Arg.html#method.required + AllowMissingPositional, + /// Specifies that an unexpected positional argument, /// which would otherwise cause a [`ErrorKind::UnknownArgument`] error, /// should instead be treated as a [`SubCommand`] within the [`ArgMatches`] struct.