From 920b5595ed72abfb501ce054ab536067d8df2a66 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Wed, 29 Jun 2016 20:28:09 -0400 Subject: [PATCH] feat(Arg): adds new setting `Arg::require_delimiter` which requires val delimiter to parse multiple values Using this setting requires a value delimiter be present in order to parse multiple values. Otherwise it is assumed no values follow, and moves on to the next arg with a clean slate. These examples demonstrate what happens when `require_delimiter(true)` is used. Notice everything works in this first example, as we use a delimiter, as expected. ```rust let delims = App::new("reqdelims") .arg(Arg::with_name("opt") .short("o") .takes_value(true) .multiple(true) .require_delimiter(true)) // Simulate "$ reqdelims -o val1,val2,val3" .get_matches_from(vec![ "reqdelims", "-o", "val1,val2,val3", ]); assert!(delims.is_present("opt")); assert_eq!(delims.values_of("opt").unwrap().collect::>(), ["val1", "val2", "val3"]); ``` In this next example, we will *not* use a delimiter. Notice it's now an error. ```rust let res = App::new("reqdelims") .arg(Arg::with_name("opt") .short("o") .takes_value(true) .multiple(true) .require_delimiter(true)) // Simulate "$ reqdelims -o val1 val2 val3" .get_matches_from_safe(vec![ "reqdelims", "-o", "val1", "val2", "val3", ]); assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!(err.kind, ErrorKind::UnknownArgument); ``` What's happening is `-o` is getting `val1`, and because delimiters are required yet none were present, it stops parsing `-o`. At this point it reaches `val2` and because no positional arguments have been defined, it's an error of an unexpected argument. In this final example, we contrast the above with `clap`'s default behavior where the above is *not* an error. ```rust let delims = App::new("reqdelims") .arg(Arg::with_name("opt") .short("o") .takes_value(true) .multiple(true)) // Simulate "$ reqdelims -o val1 val2 val3" .get_matches_from(vec![ "reqdelims", "-o", "val1", "val2", "val3", ]); assert!(delims.is_present("opt")); assert_eq!(delims.values_of("opt").unwrap().collect::>(), ["val1", "val2", "val3"]); ``` --- src/app/parser.rs | 4 ++-- src/args/arg.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++ src/args/settings.rs | 25 ++++++++++++-------- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/app/parser.rs b/src/app/parser.rs index cb5725be43e..d51017fc040 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -1256,9 +1256,9 @@ impl<'a, 'b> Parser<'a, 'b> ret = try!(self.add_single_val_to_arg(arg, v, matcher)); } // If there was a delimiter used, we're not looking for more values - if val.contains_byte(delim as u32 as u8) { + if val.contains_byte(delim as u32 as u8) || arg.is_set(ArgSettings::RequireDelimiter) { ret = None; - } + } } } else { ret = try!(self.add_single_val_to_arg(arg, val, matcher)); diff --git a/src/args/arg.rs b/src/args/arg.rs index ac0c5dc8f5a..e55cba084c4 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -1919,6 +1919,62 @@ impl<'a, 'b> Arg<'a, 'b> { } } + /// Specifies whether or not an argument should allow grouping of multiple values via a + /// delimiter. I.e. should `--option=val1,val2,val3` be parsed as three values (`val1`, `val2`, + /// and `val3`) or as a single value (`val1,val2,val3`). Defaults to using `,` (comma) as the + /// value delimiter for all arguments that accept values (options and positional arguments) + /// + /// **NOTE:** The default is `true`. Setting the value to `true` will reset any previous use of + /// [`Arg::value_delimiter`] back to the default of `,` (comma). + /// + /// # Examples + /// + /// The following example shows the default behavior. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let delims = App::new("delims") + /// .arg(Arg::with_name("option") + /// .long("option") + /// .takes_value(true)) + /// .get_matches_from(vec![ + /// "delims", + /// "--option=val1,val2,val3", + /// ]); + /// + /// assert!(delims.is_present("option")); + /// assert_eq!(delims.occurrences_of("option"), 1); + /// assert_eq!(delims.values_of("option").unwrap().collect::>(), ["val1", "val2", "val3"]); + /// ``` + /// The next example shows the difference when turning delimiters off. + /// + /// ```rust + /// # use clap::{App, Arg}; + /// let nodelims = App::new("nodelims") + /// .arg(Arg::with_name("option") + /// .long("option") + /// .use_delimiter(false) + /// .takes_value(true)) + /// .get_matches_from(vec![ + /// "nodelims", + /// "--option=val1,val2,val3", + /// ]); + /// + /// assert!(nodelims.is_present("option")); + /// assert_eq!(nodelims.occurrences_of("option"), 1); + /// assert_eq!(nodelims.value_of("option").unwrap(), "val1,val2,val3"); + /// ``` + /// [`Arg::value_delimiter`]: ./struct.Arg.html#method.value_delimiter + pub fn require_delimiter(mut self, d: bool) -> Self { + if d { + self.setb(ArgSettings::UseValueDelimiter); + self.set(ArgSettings::RequireDelimiter) + } else { + self.unsetb(ArgSettings::UseValueDelimiter); + self.unset(ArgSettings::RequireDelimiter) + } + } + /// Specifies the separator to use when values are clumped together, defaults to `,` (comma). /// /// **NOTE:** implicitly sets [`Arg::use_delimiter(true)`] diff --git a/src/args/settings.rs b/src/args/settings.rs index c4573c07184..4921fcd17d3 100644 --- a/src/args/settings.rs +++ b/src/args/settings.rs @@ -3,15 +3,16 @@ use std::ascii::AsciiExt; bitflags! { flags Flags: u16 { - const REQUIRED = 0b000000001, - const MULTIPLE = 0b000000010, - const EMPTY_VALS = 0b000000100, - const GLOBAL = 0b000001000, - const HIDDEN = 0b000010000, - const TAKES_VAL = 0b000100000, - const USE_DELIM = 0b001000000, - const NEXT_LINE_HELP = 0b010000000, - const R_UNLESS_ALL = 0b100000000, + const REQUIRED = 0b0000000001, + const MULTIPLE = 0b0000000010, + const EMPTY_VALS = 0b0000000100, + const GLOBAL = 0b0000001000, + const HIDDEN = 0b0000010000, + const TAKES_VAL = 0b0000100000, + const USE_DELIM = 0b0001000000, + const NEXT_LINE_HELP = 0b0010000000, + const R_UNLESS_ALL = 0b0100000000, + const REQ_DELIM = 0b1000000000, } } @@ -33,7 +34,8 @@ impl ArgFlags { TakesValue => TAKES_VAL, UseValueDelimiter => USE_DELIM, NextLineHelp => NEXT_LINE_HELP, - RequiredUnlessAll => R_UNLESS_ALL + RequiredUnlessAll => R_UNLESS_ALL, + RequireDelimiter => REQ_DELIM } } @@ -67,6 +69,8 @@ pub enum ArgSettings { UseValueDelimiter, /// Prints the help text on the line after the argument NextLineHelp, + /// Requires the use of a value delimiter for all multiple values + RequireDelimiter, #[doc(hidden)] RequiredUnlessAll, } @@ -84,6 +88,7 @@ impl FromStr for ArgSettings { "usevaluedelimiter" => Ok(ArgSettings::UseValueDelimiter), "nextlinehelp" => Ok(ArgSettings::NextLineHelp), "requiredunlessall" => Ok(ArgSettings::RequiredUnlessAll), + "requiredelimiter" => Ok(ArgSettings::RequireDelimiter), _ => Err("unknown ArgSetting, cannot convert from str".to_owned()), } }