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::<Vec<_>>(), ["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::<Vec<_>>(), ["val1", "val2", "val3"]);
```
This commit is contained in:
Kevin K 2016-06-29 20:28:09 -04:00
parent 40017ed091
commit 920b5595ed
3 changed files with 73 additions and 12 deletions

View file

@ -1256,7 +1256,7 @@ 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;
}
}

View file

@ -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::<Vec<_>>(), ["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)`]

View file

@ -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()),
}
}