econf_derive/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4
5use proc_macro2::{Ident, TokenStream as TokenStream2};
6use quote::quote;
7use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, LitStr};
8
9#[proc_macro_derive(LoadEnv, attributes(econf))]
10pub fn load_env(input: TokenStream) -> TokenStream {
11    let input = parse_macro_input!(input as DeriveInput);
12
13    let name = input.ident;
14    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
15    let content = content(&name, &input.data);
16
17    let expanded = quote! {
18        impl #impl_generics ::econf::LoadEnv for #name #ty_generics #where_clause {
19            fn load(self, path: &str, loader: &mut ::econf::Loader) -> Self {
20                #content
21            }
22        }
23    };
24
25    TokenStream::from(expanded)
26}
27
28fn is_skip(f: &Field) -> bool {
29    f.attrs.iter().any(|attr| {
30        if attr.path().is_ident("econf") {
31            if let Ok(args) = attr.parse_args::<Ident>() {
32                return args == "skip";
33            }
34        }
35
36        false
37    })
38}
39
40fn find_renaming(f: &Field) -> Option<String> {
41    let mut rename = None;
42    for attr in &f.attrs {
43        if attr.path().is_ident("econf") {
44            attr.parse_nested_meta(|meta| {
45                if meta.path.is_ident("rename") {
46                    let s: LitStr = meta.value()?.parse()?;
47                    rename = Some(s.value());
48                }
49
50                Ok(())
51            })
52            .expect("failed to parse nested meta");
53        }
54    }
55
56    rename
57}
58
59fn content(name: &Ident, data: &Data) -> TokenStream2 {
60    match data {
61        Data::Struct(data) => match &data.fields {
62            Fields::Named(fields) => {
63                let fields = fields.named.iter().map(|f| {
64                    let ident = &f.ident;
65                    if is_skip(f) {
66                        return quote! {
67                            #ident: self.#ident,
68                        };
69                    }
70                    match find_renaming(f) {
71                        Some(overwritten_name) => quote! {
72                            #ident: self.#ident.load(&(path.to_owned() + "_" + #overwritten_name), loader),
73                        },
74                        None => quote! {
75                            #ident: self.#ident.load(&(path.to_owned() + "_" + stringify!(#ident)), loader),
76                        }
77                    }
78                });
79                quote! {
80                    Self { #(
81                        #fields
82                    )* }
83                }
84            }
85            Fields::Unnamed(fields) => {
86                let fields = fields.unnamed.iter().enumerate().map(|(i, f)| {
87                    let i = syn::Index::from(i);
88                    let i = &i;
89                    if is_skip(f) {
90                        return quote! { self.#i, };
91                    }
92                    match find_renaming(f) {
93                        Some(overwritten_name) => quote! {
94                            self.#i.load(&(path.to_owned() + "_" + #overwritten_name), loader),
95                        },
96                        None => quote! {
97                            self.#i.load(&(path.to_owned() + "_" + &#i.to_string()), loader),
98                        },
99                    }
100                });
101                quote! {
102                    Self ( #(
103                        #fields
104                    )* )
105                }
106            }
107            Fields::Unit => quote!(#name),
108        },
109        Data::Enum(data) => {
110            data.variants.iter().for_each(|f| match f.fields {
111                Fields::Named(_) => panic!("Enum variant with named fields are not supported"),
112                Fields::Unnamed(_) => panic!("Enum variant with unnamed fields are not supported"),
113                Fields::Unit => {}
114            });
115
116            quote! {
117                loader.load_from_str(self, path)
118            }
119        }
120        Data::Union(_) => unimplemented!("Unions are not supported"),
121    }
122}