1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(ModuleConfig)]
pub fn derive_module_config(input: TokenStream) -> TokenStream {
let dinput = parse_macro_input!(input as DeriveInput);
impl_module_config(dinput)
}
fn impl_module_config(dinput: DeriveInput) -> proc_macro::TokenStream {
let struct_ident = &dinput.ident;
let (_impl_generics, ty_generics, where_clause) = dinput.generics.split_for_impl();
let mut from_config = quote! {};
let mut load_config = quote! {};
if let syn::Data::Struct(data) = dinput.data {
if let syn::Fields::Named(fields_named) = data.fields {
let mut load_tokens = quote! {};
let mut fields = quote! {};
for field in fields_named.named.iter() {
let ident = field.ident.as_ref().unwrap();
let new_load_tokens = quote! {
stringify!(#ident) => self.#ident.load_config(v),
};
let new_field = quote! {
stringify!(#ident),
};
load_tokens = quote! {
#load_tokens
#new_load_tokens
};
fields = quote! {
#fields
#new_field
};
}
load_config = quote! {
fn load_config(&mut self, config: &'a toml::Value) {
if let toml::Value::Table(config) = config {
config.iter().for_each(|(k, v)| {
match k.as_str() {
#load_tokens
unknown => {
::log::warn!("Unknown config key '{}'", unknown);
let did_you_mean = ::std::array::IntoIter::new([#fields])
.filter_map(|field| {
let score = ::strsim::jaro_winkler(unknown, field);
(score > 0.8).then(|| (score, field))
})
.max_by(
|(score_a, _field_a), (score_b, _field_b)| {
score_a.partial_cmp(score_b).unwrap_or(::std::cmp::Ordering::Equal)
},
);
if let Some((_score, field)) = did_you_mean {
::log::warn!("Did you mean '{}'?", field);
}
},
}
});
}
}
};
from_config = quote! {
fn from_config(config: &'a toml::Value) -> Option<Self> {
let mut out = Self::default();
out.load_config(config);
Some(out)
}
};
}
}
TokenStream::from(quote! {
impl<'a> ModuleConfig<'a> for #struct_ident #ty_generics #where_clause {
#from_config
#load_config
}
})
}
|