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 ),*
    )
}