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    // Resolve cfg-rs crate path without relying on proc_macro_crate.
35    // Default to ::cfg_rs, allow override via #[config(crate = "your_crate_name")]
36    let mut cfg_crate_path = quote!(::cfg_rs);
37
38    let fields = derive_config_fields(data);
39    let fs: Vec<Ident> = fields.iter().map(|f| f.name.clone()).collect();
40    let rs: Vec<&str> = fields.iter().map(|f| f.ren.as_str()).collect();
41    let ds: Vec<TokenStream> = fields
42        .iter()
43        .map(|f| match &f.def {
44            Some(d) => quote! {,Some(#d.into())},
45            _ => quote! {,None},
46        })
47        .collect();
48    let body = quote! {
49        Self {
50                #(#fs: context.parse_config(#rs #ds)?,)*
51        }
52    };
53
54    let prefix = match derive_config_prefix(attrs, &mut cfg_crate_path) {
55        Some(p) => quote! {
56            #[automatically_derived]
57            impl #cfg_crate_path::FromConfigWithPrefix for #name {
58                fn prefix() -> &'static str {
59                    #p
60                }
61            }
62        },
63        _ => quote! {},
64    };
65
66    quote! {
67        #[automatically_derived]
68        impl #cfg_crate_path::FromConfig for #name {
69            fn from_config(
70                context: &mut #cfg_crate_path::ConfigContext<'_>,
71                value: ::core::option::Option<#cfg_crate_path::ConfigValue<'_>>,
72            ) -> ::core::result::Result<Self, #cfg_crate_path::ConfigError> {
73                ::core::result::Result::Ok(#body)
74            }
75        }
76
77        #prefix
78    }
79}
80
81fn derive_config_prefix(attrs: Vec<Attribute>, crate_path: &mut TokenStream) -> Option<String> {
82    let mut prefix = None;
83    for attr in attrs {
84        if attr.path().is_ident("config") {
85            attr.parse_nested_meta(|meta| {
86                if meta.path.is_ident("prefix") {
87                    let value = meta.value()?;
88                    let s: LitStr = value.parse()?;
89                    prefix = Some(s.value());
90                    Ok(())
91                } else if meta.path.is_ident("crate") {
92                    let value = meta.value()?;
93                    let s: LitStr = value.parse()?;
94                    let ident = Ident::new(&s.value(), s.span());
95                    *crate_path = quote!(#ident);
96                    Ok(())
97                } else {
98                    Err(meta.error("Only support prefix"))
99                }
100            })
101            .unwrap();
102        }
103        if prefix.is_some() {
104            break;
105        }
106    }
107    prefix
108}
109
110struct FieldInfo {
111    name: Ident,
112    def: Option<String>,
113    ren: String,
114    desc: Option<String>,
115}
116
117fn derive_config_fields(data: DataStruct) -> Vec<FieldInfo> {
118    if let Fields::Named(fields) = data.fields {
119        let mut fs = vec![];
120        for field in fields.named {
121            fs.push(derive_config_field(field));
122        }
123        return fs;
124    }
125    panic!("Only support named body");
126}
127
128fn derive_config_field(field: Field) -> FieldInfo {
129    let name = field.ident.expect("Not possible");
130    let mut f = FieldInfo {
131        ren: name.to_string(),
132        name,
133        def: None,
134        desc: None,
135    };
136    derive_config_field_attr(&mut f, field.attrs);
137    f
138}
139
140fn derive_config_field_attr(f: &mut FieldInfo, attrs: Vec<Attribute>) {
141    for attr in attrs {
142        if attr.path().is_ident("config") {
143            attr.parse_nested_meta(|meta| {
144                if meta.path.is_ident("default") {
145                    f.def = Some(parse_lit(meta.value()?.parse::<Lit>()?));
146                } else if meta.path.is_ident("name") {
147                    f.ren = parse_lit(meta.value()?.parse::<Lit>()?);
148                } else if meta.path.is_ident("desc") {
149                    f.desc = Some(parse_lit(meta.value()?.parse::<Lit>()?));
150                } else {
151                    return Err(meta.error("Only support default/name/desc"));
152                }
153                Ok(())
154            })
155            .unwrap();
156        }
157    }
158}
159
160fn parse_lit(lit: Lit) -> String {
161    match lit {
162        Lit::Str(s) => s.value(),
163        Lit::ByteStr(s) => match String::from_utf8(s.value()) {
164            Ok(v) => v,
165            Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
166        },
167        Lit::Byte(b) => (b.value() as char).to_string(),
168        Lit::Int(i) => i.base10_digits().to_owned(),
169        Lit::Float(f) => f.base10_digits().to_owned(),
170        Lit::Bool(b) => b.value.to_string(),
171        Lit::Char(c) => c.value().to_string(),
172        Lit::Verbatim(_) => panic!("cfg-rs not support Verbatim"),
173        _ => panic!("cfg-rs not support new types"),
174    }
175}