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
extern crate proc_macro; extern crate syn; #[macro_use] extern crate quote; use proc_macro::TokenStream; use syn::{DataStruct, DeriveInput, Field, Ident, Lit, LitStr, Meta, MetaNameValue, NestedMeta}; use syn::punctuated::Punctuated; use syn::token::Comma; #[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) -> quote::Tokens { 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>, ) -> quote::Tokens { 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>) -> quote::Tokens { use Meta::*; use NestedMeta::*; use Lit::*; let fields = fields.iter().map(|field| { let iter = field.attrs.iter() .filter_map(|attr| { let path = &attr.path; match quote!(#path) == quote!(structopt) { true => Some( attr.interpret_meta() .expect(&format!("invalid structopt syntax: {}", quote!(attr))) ), false => 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.unwrap().clone()), field.ident.unwrap().span); for attr in iter { match attr { NameValue(MetaNameValue { ident, lit: Str(value), .. }) => { if 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 ),* ) }