api: Adds Arg::case_insensitive(bool) which allows matching Arg::possible_values without worrying about ASCII case

When used with `Arg::possible_values` it allows the argument value to pass validation even if
the case differs from that of the specified `possible_value`.

```rust
let m = App::new("pv")
    .arg(Arg::with_name("option")
        .long("--option")
        .takes_value(true)
        .possible_value("test123")
        .case_insensitive(true))
    .get_matches_from(vec![
        "pv", "--option", "TeSt123",
    ]);

assert!(m.value_of("option").unwrap().eq_ignore_ascii_case("test123"));
```

This setting also works when multiple values can be defined:

```rust
let m = App::new("pv")
    .arg(Arg::with_name("option")
        .short("-o")
        .long("--option")
        .takes_value(true)
        .possible_value("test123")
        .possible_value("test321")
        .multiple(true)
        .case_insensitive(true))
    .get_matches_from(vec![
        "pv", "--option", "TeSt123", "teST123", "tESt321"
    ]);

let matched_vals = m.values_of("option").unwrap().collect::<Vec<_>>();
assert_eq!(&*matched_vals, &["TeSt123", "teST123", "tESt321"]);
```

Closes #1118
This commit is contained in:
Kevin K 2017-11-28 06:41:43 -05:00
parent e53c33d6b9
commit 80993357e1
No known key found for this signature in database
GPG key ID: 17218E4B3692F01A
5 changed files with 84 additions and 8 deletions

View file

@ -1,5 +1,7 @@
// std
use std::fmt::Display;
#[allow(unused_imports)]
use std::ascii::AsciiExt;
// Internal
use INTERNAL_ERROR_MSG;
@ -89,7 +91,12 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
if let Some(p_vals) = arg.possible_vals() {
debugln!("Validator::validate_values: possible_vals={:?}", p_vals);
let val_str = val.to_string_lossy();
if !p_vals.contains(&&*val_str) {
let ok = if arg.is_set(ArgSettings::CaseInsensitive) {
p_vals.iter().any(|pv| pv.eq_ignore_ascii_case(&*val_str))
} else {
p_vals.contains(&&*val_str)
};
if !ok {
return Err(Error::invalid_value(val_str,
p_vals,
arg,

View file

@ -2390,6 +2390,59 @@ impl<'a, 'b> Arg<'a, 'b> {
self
}
/// When used with [`Arg::possible_values`] it allows the argument value to pass validation even if
/// the case differs from that of the specified `possible_value`.
///
/// **Pro Tip:** Use this setting with [`arg_enum!`]
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg};
/// # use std::ascii::AsciiExt;
/// let m = App::new("pv")
/// .arg(Arg::with_name("option")
/// .long("--option")
/// .takes_value(true)
/// .possible_value("test123")
/// .case_insensitive(true))
/// .get_matches_from(vec![
/// "pv", "--option", "TeSt123",
/// ]);
///
/// assert!(m.value_of("option").unwrap().eq_ignore_ascii_case("test123"));
/// ```
///
/// This setting also works when multiple values can be defined:
///
/// ```rust
/// # use clap::{App, Arg};
/// let m = App::new("pv")
/// .arg(Arg::with_name("option")
/// .short("-o")
/// .long("--option")
/// .takes_value(true)
/// .possible_value("test123")
/// .possible_value("test321")
/// .multiple(true)
/// .case_insensitive(true))
/// .get_matches_from(vec![
/// "pv", "--option", "TeSt123", "teST123", "tESt321"
/// ]);
///
/// let matched_vals = m.values_of("option").unwrap().collect::<Vec<_>>();
/// assert_eq!(&*matched_vals, &["TeSt123", "teST123", "tESt321"]);
/// ```
/// [`Arg::case_insensitive(true)`]: ./struct.Arg.html#method.possible_values
/// [`arg_enum!`]: ./macro.arg_enum.html
pub fn case_insensitive(self, ci: bool) -> Self {
if ci {
self.set(ArgSettings::CaseInsensitive)
} else {
self.unset(ArgSettings::CaseInsensitive)
}
}
/// Specifies the name of the [`ArgGroup`] the argument belongs to.
///
/// # Examples

View file

@ -4,7 +4,7 @@ use std::ascii::AsciiExt;
use std::str::FromStr;
bitflags! {
struct Flags: u16 {
struct Flags: u32 {
const REQUIRED = 1;
const MULTIPLE = 1 << 1;
const EMPTY_VALS = 1 << 2;
@ -21,6 +21,7 @@ bitflags! {
const REQUIRE_EQUALS = 1 << 13;
const LAST = 1 << 14;
const HIDE_DEFAULT_VAL = 1 << 15;
const CASE_INSENSITIVE = 1 << 16;
}
}
@ -47,6 +48,7 @@ impl ArgFlags {
AllowLeadingHyphen => Flags::ALLOW_TAC_VALS,
RequireEquals => Flags::REQUIRE_EQUALS,
Last => Flags::LAST,
CaseInsensitive => Flags::CASE_INSENSITIVE,
HideDefaultValue => Flags::HIDE_DEFAULT_VAL
}
}
@ -92,6 +94,8 @@ pub enum ArgSettings {
Last,
/// Hides the default value from the help string
HideDefaultValue,
/// Makes `Arg::possible_values` case insensitive
CaseInsensitive,
#[doc(hidden)]
RequiredUnlessAll,
#[doc(hidden)]
@ -118,6 +122,7 @@ impl FromStr for ArgSettings {
"requireequals" => Ok(ArgSettings::RequireEquals),
"last" => Ok(ArgSettings::Last),
"hidedefaultvalue" => Ok(ArgSettings::HideDefaultValue),
"caseinsensitive" => Ok(ArgSettings::CaseInsensitive),
_ => Err("unknown ArgSetting, cannot convert from str".to_owned()),
}
}
@ -161,6 +166,8 @@ mod test {
ArgSettings::Last);
assert_eq!("hidedefaultvalue".parse::<ArgSettings>().unwrap(),
ArgSettings::HideDefaultValue);
assert_eq!("caseinsensitive".parse::<ArgSettings>().unwrap(),
ArgSettings::CaseInsensitive);
assert!("hahahaha".parse::<ArgSettings>().is_err());
}
}

View file

@ -262,7 +262,8 @@ macro_rules! _clap_count_exprs {
/// retrieve a `Vec<&'static str>` of the variant names, as well as implementing [`FromStr`] and
/// [`Display`] automatically.
///
/// **NOTE:** Case insensitivity is supported for ASCII characters only
/// **NOTE:** Case insensitivity is supported for ASCII characters only. It's highly recommended to
/// use [`Arg::case_insensitive(true)`] for args that will be used with these enums
///
/// **NOTE:** This macro automatically implements [`std::str::FromStr`] and [`std::fmt::Display`]
///
@ -270,12 +271,12 @@ macro_rules! _clap_count_exprs {
///
/// # Examples
///
/// ```no_run
/// ```rust
/// # #[macro_use]
/// # extern crate clap;
/// # use clap::{App, Arg};
/// arg_enum!{
/// #[derive(Debug)]
/// #[derive(PartialEq, Debug)]
/// pub enum Foo {
/// Bar,
/// Baz,
@ -286,17 +287,22 @@ macro_rules! _clap_count_exprs {
/// // and implements std::str::FromStr to use with the value_t! macros
/// fn main() {
/// let m = App::new("app")
/// .arg_from_usage("<foo> 'the foo'")
/// .get_matches();
/// .arg(Arg::from_usage("<foo> 'the foo'")
/// .possible_values(&Foo::variants())
/// .case_insensitive(true))
/// .get_matches_from(vec![
/// "app", "baz"
/// ]);
/// let f = value_t!(m, "foo", Foo).unwrap_or_else(|e| e.exit());
///
/// // Use f like any other Foo variant...
/// assert_eq!(f, Foo::Baz);
/// }
/// ```
/// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
/// [`std::str::FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
/// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
/// [`std::fmt::Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
/// [`Arg::case_insensitive(true)`]: ./struct.Arg.html#method.case_insensitive
#[macro_export]
macro_rules! arg_enum {
(@as_item $($i:item)*) => ($($i)*);

View file

@ -3,6 +3,9 @@ extern crate regex;
include!("../clap-test.rs");
#[allow(unsused_imports)]
use std::ascii::AsciiExt;
use clap::{App, Arg, ErrorKind};
#[cfg(feature="suggestions")]