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 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}