cfg_derive/
lib.rs

1//! Auto derive FromConfig.
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![warn(
4    anonymous_parameters,
5    missing_copy_implementations,
6    missing_debug_implementations,
7    missing_docs,
8    nonstandard_style,
9    rust_2018_idioms,
10    single_use_lifetimes,
11    trivial_casts,
12    trivial_numeric_casts,
13    unreachable_pub,
14    unused_extern_crates,
15    unused_qualifications,
16    variant_size_differences
17)]
18use quote::{__private::TokenStream, quote};
19use syn::*;
20
21#[allow(missing_docs)]
22#[proc_macro_derive(FromConfig, attributes(config))]
23pub fn derive_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
24    let input: DeriveInput = parse_macro_input!(input as DeriveInput);
25    let name = input.ident;
26    let body = match input.data {
27        Data::Struct(data) => derive_config_struct(&name, input.attrs, data),
28        _ => panic!("Only support struct"),
29    };
30    proc_macro::TokenStream::from(quote! {#body})
31}
32
33fn derive_config_struct(name: &Ident, attrs: Vec<Attribute>, data: DataStruct) -> TokenStream {
34    let fields = derive_config_fields(data);
35    let fs: Vec<Ident> = fields.iter().map(|f| f.name.clone()).collect();
36    let rs: Vec<&str> = fields.iter().map(|f| f.ren.as_str()).collect();
37    let ds: Vec<TokenStream> = fields
38        .iter()
39        .map(|f| match &f.def {
40            Some(d) => quote! {,Some(#d.into())},
41            _ => quote! {,None},
42        })
43        .collect();
44    let body = quote! {
45        Self {
46                #(#fs: context.parse_config(#rs#ds)?,)*
47        }
48    };
49
50    let prefix = match derive_config_attr(attrs) {
51        Some(p) => quote! {
52            #[automatically_derived]
53            impl FromConfigWithPrefix for #name {
54                fn prefix() -> &'static str {
55                    #p
56                }
57            }
58        },
59        _ => quote! {},
60    };
61
62    quote! {
63        #[automatically_derived]
64        impl FromConfig for #name {
65            fn from_config(
66                context: &mut ConfigContext<'_>,
67                value: Option<ConfigValue<'_>>,
68            ) -> Result<Self, ConfigError> {
69                Ok(#body)
70            }
71        }
72
73        #prefix
74    }
75}
76
77fn derive_config_attr(attrs: Vec<Attribute>) -> Option<String> {
78    let mut prefix = None;
79    for attr in attrs {
80        if attr.path().is_ident("config") {
81            attr.parse_nested_meta(|meta| {
82                if meta.path.is_ident("prefix") {
83                    let value = meta.value()?;
84                    let s: LitStr = value.parse()?;
85                    prefix = Some(s.value());
86                    Ok(())
87                } else {
88                    Err(meta.error("Only support prefix"))
89                }
90            })
91            .unwrap();
92        }
93        if prefix.is_some() {
94            break;
95        }
96    }
97    prefix
98}
99
100struct FieldInfo {
101    name: Ident,
102    def: Option<String>,
103    ren: String,
104    desc: Option<String>,
105}
106
107fn derive_config_fields(data: DataStruct) -> Vec<FieldInfo> {
108    if let Fields::Named(fields) = data.fields {
109        let mut fs = vec![];
110        for field in fields.named {
111            fs.push(derive_config_field(field));
112        }
113        return fs;
114    }
115    panic!("Only support named body");
116}
117
118fn derive_config_field(field: Field) -> FieldInfo {
119    let name = field.ident.expect("Not possible");
120    let mut f = FieldInfo {
121        ren: name.to_string(),
122        name,
123        def: None,
124        desc: None,
125    };
126    derive_config_field_attr(&mut f, field.attrs);
127    f
128}
129
130fn derive_config_field_attr(f: &mut FieldInfo, attrs: Vec<Attribute>) {
131    for attr in attrs {
132        if attr.path().is_ident("config") {
133            attr.parse_nested_meta(|meta| {
134                if meta.path.is_ident("default") {
135                    f.def = Some(parse_lit(meta.value()?.parse::<Lit>()?));
136                } else if meta.path.is_ident("name") {
137                    f.ren = parse_lit(meta.value()?.parse::<Lit>()?);
138                } else if meta.path.is_ident("desc") {
139                    f.desc = Some(parse_lit(meta.value()?.parse::<Lit>()?));
140                } else {
141                    return Err(meta.error("Only support default/name/desc"));
142                }
143                Ok(())
144            })
145            .unwrap();
146        }
147    }
148}
149
150fn parse_lit(lit: Lit) -> String {
151    match lit {
152        Lit::Str(s) => s.value(),
153        Lit::ByteStr(s) => match String::from_utf8(s.value()) {
154            Ok(v) => v,
155            Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
156        },
157        Lit::Byte(b) => (b.value() as char).to_string(),
158        Lit::Int(i) => i.base10_digits().to_owned(),
159        Lit::Float(f) => f.base10_digits().to_owned(),
160        Lit::Bool(b) => b.value.to_string(),
161        Lit::Char(c) => c.value().to_string(),
162        Lit::Verbatim(_) => panic!("cfg-rs not support Verbatim"),
163        _ => panic!("cfg-rs not support new types"),
164    }
165}