From 1fec268e51736602e38e67c76266f439e2e0ef12 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Tue, 28 Nov 2017 06:41:43 -0500 Subject: [PATCH] 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::>(); assert_eq!(&*matched_vals, &["TeSt123", "teST123", "tESt321"]); ``` Closes #1118 --- src/app/validator.rs | 7 +++++- src/args/arg.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++ src/args/settings.rs | 9 +++++++- src/macros.rs | 18 ++++++++++----- 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/app/validator.rs b/src/app/validator.rs index 70eb266bab6f..d490733daff6 100644 --- a/src/app/validator.rs +++ b/src/app/validator.rs @@ -89,7 +89,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, diff --git a/src/args/arg.rs b/src/args/arg.rs index 5d8e077429cd..fb01dce76887 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -2390,6 +2390,58 @@ 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}; + /// 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::>(); + /// 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 diff --git a/src/args/settings.rs b/src/args/settings.rs index 65bae7e21c56..be37660cfb71 100644 --- a/src/args/settings.rs +++ b/src/args/settings.rs @@ -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::().unwrap(), ArgSettings::HideDefaultValue); + assert_eq!("caseinsensitive".parse::().unwrap(), + ArgSettings::CaseInsensitive); assert!("hahahaha".parse::().is_err()); } } diff --git a/src/macros.rs b/src/macros.rs index 3aadb2d7fd85..110d8a893313 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -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(" 'the foo'") -/// .get_matches(); +/// .arg(Arg::from_usage(" '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)*);