From d07322169b281c77c0efc21e77887954835d132f Mon Sep 17 00:00:00 2001 From: Alan K Date: Fri, 20 Jul 2018 17:54:02 +0200 Subject: [PATCH 1/4] WIP: Getting the tests to work --- src/derives/arg_enum.rs | 58 +++++++++++++++++++---------------------- tests/arg_enum_basic.rs | 12 ++++++++- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/derives/arg_enum.rs b/src/derives/arg_enum.rs index fb94afb..cfadd3c 100644 --- a/src/derives/arg_enum.rs +++ b/src/derives/arg_enum.rs @@ -14,33 +14,32 @@ use syn::punctuated; use syn::token; pub fn derive_arg_enum(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { - unimplemented!() + let from_str_block = impl_from_str(ast); + let variants_block = impl_variants(ast); - // let from_str_block = impl_from_str(ast)?; - // let variants_block = impl_variants(ast)?; - - // quote! { - // #from_str_block - // #variants_block - // } + quote! { + #from_str_block + #variants_block + } } -/* fn impl_from_str(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { let ident = &ast.ident; - let is_case_sensitive = ast.attrs.iter().any(|v| v.name() == "case_sensitive"); - let variants = variants(ast)?; + let is_case_sensitive = ast.attrs + .iter() + .any(|v| v.path.segments.iter().any(|s| s.ident == "case_sensitive")); + let variants = variants(ast); let strings = variants .iter() - .map(|ref variant| String::from(variant.ident.as_ref())) + .map(|variant| variant.ident.to_string()) .collect::>(); // 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 for_error_message = strings.join(", "); let condition_function_slice = [match is_case_sensitive { true => quote! { str::eq }, @@ -48,47 +47,44 @@ fn impl_from_str(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { }]; let condition_function = condition_function_slice.iter().cycle(); - Ok(quote! { + quote! { impl ::std::str::FromStr for #ident { type Err = String; fn from_str(input: &str) -> ::std::result::Result { match input { #(val if #condition_function(val, #strings) => Ok(#idents::#variants),)* - _ => Err({ - let v = #for_error_message; - format!("valid values: {}", - v.join(" ,")) - }), + _ => Err( + format!("valid values: {}", #for_error_message) + ), } } } - }) + } } fn impl_variants(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { let ident = &ast.ident; - let variants = variants(ast)? - .iter() - .map(|ref variant| String::from(variant.ident.as_ref())) - .collect::>(); - let length = variants.len(); + let local_variants = variants(ast); - Ok(quote! { + quote! { impl #ident { - fn variants() -> [&'static str; #length] { - #variants + fn variants() -> impl ::std::iter::Iterator { + use ::std::str::FromStr; + + #local_variants + .iter() + .map(|variant| FromStr::from_str(&variant.ident.to_string()).unwrap()) } } - }) + } } fn variants(ast: &syn::DeriveInput) -> &punctuated::Punctuated { use syn::Data::*; match ast.data { - Enum(ref data) => data.variants, + Enum(ref data) => &data.variants, _ => panic!("Only enums are supported for deriving the ArgEnum trait"), } } -*/ diff --git a/tests/arg_enum_basic.rs b/tests/arg_enum_basic.rs index 53d8ac3..af60bf3 100644 --- a/tests/arg_enum_basic.rs +++ b/tests/arg_enum_basic.rs @@ -18,7 +18,7 @@ use clap::{App, Arg}; enum ArgChoice { Foo, Bar, - Baz, + Baz } #[test] @@ -37,6 +37,16 @@ fn when_lowercase() { assert_eq!(t.unwrap(), ArgChoice::Foo); } +#[test] +fn when_lowercase_derive() { + #[derive(Clap)] + struct Opt { + choice: ArgChoice, + } + + assert_eq!(Opt::parse_from(&["opt", "foo"]).choice, ArgChoice::Foo); +} + #[test] fn when_capitalized() { let matches = App::new(env!("CARGO_PKG_NAME")) From d1c344c360c8d57421c52b589989d6822e905287 Mon Sep 17 00:00:00 2001 From: Alan K Date: Sun, 29 Jul 2018 18:52:11 +0200 Subject: [PATCH 2/4] Fix failing `basic` test Converts the `short` argument to a `char` as expected by `clap-rs` --- src/derives/attrs.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/derives/attrs.rs b/src/derives/attrs.rs index 2944c85..7ed7408 100644 --- a/src/derives/attrs.rs +++ b/src/derives/attrs.rs @@ -366,7 +366,11 @@ impl Attrs { pub fn methods(&self) -> proc_macro2::TokenStream { let methods = self.methods.iter().map(|&Method { ref name, ref args }| { let name = syn::Ident::new(&name, proc_macro2::Span::call_site()); - quote!( .#name(#args) ) + if name == "short" { + quote!( .#name(#args.chars().nth(0).unwrap()) ) + } else { + quote!( .#name(#args) ) + } }); quote!( #(#methods)* ) } From 2dee5b8daed038d741c71d7175ccbba24616d4e2 Mon Sep 17 00:00:00 2001 From: Alan K Date: Sat, 11 Aug 2018 21:14:02 +0200 Subject: [PATCH 3/4] Print token stream - Added a print to show the token stream generated for the `variants` method - Changed the variants method to use the derived `from_str` method rather than the `FromStr` one --- src/derives/arg_enum.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/derives/arg_enum.rs b/src/derives/arg_enum.rs index cfadd3c..5ec8c02 100644 --- a/src/derives/arg_enum.rs +++ b/src/derives/arg_enum.rs @@ -25,7 +25,8 @@ pub fn derive_arg_enum(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { fn impl_from_str(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { let ident = &ast.ident; - let is_case_sensitive = ast.attrs + let is_case_sensitive = ast + .attrs .iter() .any(|v| v.path.segments.iter().any(|s| s.ident == "case_sensitive")); let variants = variants(ast); @@ -67,17 +68,19 @@ fn impl_variants(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { let ident = &ast.ident; let local_variants = variants(ast); - quote! { + let tokens = quote! { impl #ident { fn variants() -> impl ::std::iter::Iterator { use ::std::str::FromStr; #local_variants .iter() - .map(|variant| FromStr::from_str(&variant.ident.to_string()).unwrap()) + .map(|variant| #ident::from_str(&variant.ident.to_string()).unwrap()) } } - } + }; + println!("{}", tokens); + tokens } fn variants(ast: &syn::DeriveInput) -> &punctuated::Punctuated { From 8ac6e3e20fb89614df60e385938d4c45c3175a78 Mon Sep 17 00:00:00 2001 From: Alan K Date: Sat, 18 Aug 2018 16:51:26 +0200 Subject: [PATCH 4/4] Partially completed arg_enum tests --- src/derives/arg_enum.rs | 24 ++++++++++++++---------- tests/arg_enum_basic.rs | 14 ++++++-------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/derives/arg_enum.rs b/src/derives/arg_enum.rs index 5ec8c02..cc9e205 100644 --- a/src/derives/arg_enum.rs +++ b/src/derives/arg_enum.rs @@ -61,26 +61,30 @@ fn impl_from_str(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { } } } + + impl ::std::convert::Into<&'static str> for #ident { + fn into(self) -> &'static str { + match self { + #(val => stringify!(#variants),)* + } + } + } } } +// See if we can return an array instead of a vec fn impl_variants(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { let ident = &ast.ident; let local_variants = variants(ast); - let tokens = quote! { + quote! { impl #ident { - fn variants() -> impl ::std::iter::Iterator { - use ::std::str::FromStr; - - #local_variants - .iter() - .map(|variant| #ident::from_str(&variant.ident.to_string()).unwrap()) + fn variants() -> ::std::vec::Vec<#ident> { + use #ident::*; + vec![#local_variants] } } - }; - println!("{}", tokens); - tokens + } } fn variants(ast: &syn::DeriveInput) -> &punctuated::Punctuated { diff --git a/tests/arg_enum_basic.rs b/tests/arg_enum_basic.rs index af60bf3..64b8997 100644 --- a/tests/arg_enum_basic.rs +++ b/tests/arg_enum_basic.rs @@ -14,11 +14,11 @@ extern crate clap_derive; use clap::{App, Arg}; -#[derive(ArgEnum, Debug, PartialEq)] +#[derive(ArgEnum, Debug, PartialEq, Copy, Clone)] enum ArgChoice { Foo, Bar, - Baz + Baz, } #[test] @@ -28,9 +28,8 @@ fn when_lowercase() { Arg::with_name("arg") .required(true) .takes_value(true) - .possible_values(&ArgChoice::variants()), - ) - .get_matches_from_safe(vec!["", "foo"]) + .possible_values(ArgChoice::variants()), + ).get_matches_from_safe(vec!["", "foo"]) .unwrap(); let t = value_t!(matches.value_of("arg"), ArgChoice); assert!(t.is_ok()); @@ -54,9 +53,8 @@ fn when_capitalized() { Arg::with_name("arg") .required(true) .takes_value(true) - .possible_values(&ArgChoice::variants()), - ) - .get_matches_from_safe(vec!["", "Foo"]) + .possible_values(ArgChoice::variants()), + ).get_matches_from_safe(vec!["", "Foo"]) .unwrap(); let t = value_t!(matches.value_of("arg"), ArgChoice); assert!(t.is_ok());