1#![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}