diff --git a/examples/basic.rs b/examples/arg_enum_basic.rs similarity index 100% rename from examples/basic.rs rename to examples/arg_enum_basic.rs diff --git a/examples/arg_enum_case_sensitive.rs b/examples/arg_enum_case_sensitive.rs new file mode 100644 index 00000000..0c425cfc --- /dev/null +++ b/examples/arg_enum_case_sensitive.rs @@ -0,0 +1,28 @@ +#[macro_use] +extern crate clap; +#[macro_use] +extern crate clap_derive; + +use clap::{App, Arg}; + +#[derive(ArgEnum, Debug)] +#[case_sensitive] +enum ArgChoice { + Foo, + Bar, + Baz, +} + +fn main() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches(); + + let t = value_t!(matches.value_of("arg"), ArgChoice) + .unwrap_or_else(|e| e.exit()); + + println!("{:?}", t); +} \ No newline at end of file diff --git a/src/arg_enum.rs b/src/arg_enum.rs index 44e00504..967a2acb 100644 --- a/src/arg_enum.rs +++ b/src/arg_enum.rs @@ -20,25 +20,32 @@ impl ClapDerive for ArgEnum { fn impl_from_str(ast: &DeriveInput) -> Result { let ident = &ast.ident; + let is_case_sensitive = ast.attrs.iter().any(|v| v.name() == "case_sensitive"); let variants = helpers::variants(ast)?; let strings = variants.iter() .map(|ref variant| String::from(variant.ident.as_ref())) .collect::>(); - // Yes, we actually need to do this. + // All of these need to be iterators. let ident_slice = [ident.clone()]; let idents = ident_slice.iter().cycle(); let for_error_message = strings.clone(); + let condition_function_slice = [match is_case_sensitive { + true => quote! { str::eq }, + false => quote! { ::std::ascii::AsciiExt::eq_ignore_ascii_case }, + }]; + let condition_function = condition_function_slice.iter().cycle(); + Ok(quote! { impl ::std::str::FromStr for #ident { type Err = String; fn from_str(input: &str) -> ::std::result::Result { match input { - #(val if ::std::ascii::AsciiExt::eq_ignore_ascii_case(val, #strings) => Ok(#idents::#variants),)* + #(val if #condition_function(val, #strings) => Ok(#idents::#variants),)* _ => Err({ let v = #for_error_message; format!("valid values: {}", diff --git a/src/lib.rs b/src/lib.rs index 7e2f5772..32facc15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ trait ClapDerive { } /// It is required to have this seperate and specificly defined. -#[proc_macro_derive(ArgEnum)] +#[proc_macro_derive(ArgEnum, attributes(case_sensitive))] pub fn derive_arg_enum(input: TokenStream) -> TokenStream { ArgEnum::derive(input).unwrap() } \ No newline at end of file diff --git a/tests/arg_enum_basic.rs b/tests/arg_enum_basic.rs new file mode 100644 index 00000000..8de3eb47 --- /dev/null +++ b/tests/arg_enum_basic.rs @@ -0,0 +1,45 @@ +#[macro_use] +extern crate clap; +#[macro_use] +extern crate clap_derive; + +use clap::{App, Arg}; + +#[derive(ArgEnum, Debug, PartialEq)] +enum ArgChoice { + Foo, + Bar, + Baz, +} + +#[test] +fn when_lowercase() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches_from_safe(vec![ + "", + "foo", + ]).unwrap(); + let t = value_t!(matches.value_of("arg"), ArgChoice); + assert!(t.is_ok()); + assert_eq!(t.unwrap(), ArgChoice::Foo); +} + +#[test] +fn when_capitalized() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches_from_safe(vec![ + "", + "Foo", + ]).unwrap(); + let t = value_t!(matches.value_of("arg"), ArgChoice); + assert!(t.is_ok()); + assert_eq!(t.unwrap(), ArgChoice::Foo); +} \ No newline at end of file diff --git a/tests/arg_enum_case_sensitive.rs b/tests/arg_enum_case_sensitive.rs new file mode 100644 index 00000000..17df4ee1 --- /dev/null +++ b/tests/arg_enum_case_sensitive.rs @@ -0,0 +1,45 @@ +#[macro_use] +extern crate clap; +#[macro_use] +extern crate clap_derive; + +use clap::{App, Arg}; + +#[derive(ArgEnum, Debug, PartialEq)] +#[case_sensitive] +enum ArgChoice { + Foo, + Bar, + Baz, +} + +#[test] +fn when_lowercase() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches_from_safe(vec![ + "", + "foo", + ]); // We expect this to fail. + assert!(matches.is_err()); + assert_eq!(matches.unwrap_err().kind, clap::ErrorKind::InvalidValue); +} + +#[test] +fn when_capitalized() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches_from_safe(vec![ + "", + "Foo", + ]).unwrap(); + let t = value_t!(matches.value_of("arg"), ArgChoice); + assert!(t.is_ok()); + assert_eq!(t.unwrap(), ArgChoice::Foo); +} \ No newline at end of file