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}