1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
extern crate proc_macro; extern crate syn; #[macro_use] extern crate quote; use proc_macro::TokenStream; use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{DataStruct, DeriveInput, Field, Ident, LitStr, MetaNameValue}; #[proc_macro_derive(StructOptToml, attributes(structopt))] pub fn structopt_toml(input: TokenStream) -> TokenStream { let input: DeriveInput = syn::parse(input).unwrap(); let gen = impl_structopt_toml(&input); gen.into() } fn impl_structopt_toml(input: &DeriveInput) -> proc_macro2::TokenStream { use syn::Data::*; let struct_name = &input.ident; let inner_impl = match input.data { Struct(DataStruct { fields: syn::Fields::Named(ref fields), .. }) => impl_structopt_for_struct(struct_name, &fields.named), _ => panic!("structopt_toml only supports non-tuple struct"), }; quote!(#inner_impl) } fn impl_structopt_for_struct( name: &Ident, fields: &Punctuated<Field, Comma>, ) -> proc_macro2::TokenStream { let merged_fields = gen_merged_fields(fields); quote! { impl ::structopt_toml::StructOptToml for #name { fn merge<'a>(from_toml: Self, from_args: Self, args: &::structopt_toml::clap::ArgMatches) -> Self where Self: Sized, Self: ::structopt_toml::structopt::StructOpt, Self: ::structopt_toml::serde::de::Deserialize<'a> { Self { #merged_fields } } } impl Default for #name { fn default() -> Self { let args = vec!["bin"]; #name::from_iter(args.iter()) } } } } fn gen_merged_fields(fields: &Punctuated<Field, Comma>) -> proc_macro2::TokenStream { use syn::Lit::*; use syn::Meta::*; use syn::NestedMeta::*; let fields = fields.iter().map(|field| { let iter = field .attrs .iter() .filter_map(|attr| { if attr.path.is_ident("structopt") { let meta = attr .parse_meta() .expect(&format!("invalid structopt syntax: {}", quote!(attr))); Some(meta) } else { None } }) .flat_map(|m| match m { List(l) => l.nested, tokens => panic!("unsupported syntax: {}", quote!(#tokens).to_string()), }) .map(|m| match m { Meta(m) => m, ref tokens => panic!("unsupported syntax: {}", quote!(#tokens).to_string()), }); let mut structopt_name = LitStr::new( &format!("{}", field.ident.as_ref().unwrap().clone()), field.ident.as_ref().unwrap().span(), ); for attr in iter { match attr { NameValue(MetaNameValue { path, lit: Str(value), .. }) => { if path.is_ident("name") { structopt_name = value; } } _ => (), } } let field_name = field.ident.as_ref().unwrap(); quote!( #field_name: { if args.is_present(#structopt_name) && args.occurrences_of(#structopt_name) > 0 { from_args.#field_name } else { from_toml.#field_name } } ) }); quote! ( #( #fields ),* ) }