toml_input_derive/
lib.rs

1extern crate proc_macro;
2
3use darling::{
4    ast::{self, Data, Fields},
5    FromField, FromVariant,
6};
7use darling::{FromDeriveInput, FromMeta};
8use proc_macro2::TokenStream;
9use quote::{quote, ToTokens};
10use syn::{
11    parse_macro_input, Attribute, DeriveInput, Expr, ExprLit, Ident, Lit, Meta, PathArguments,
12    Type, TypePath,
13};
14mod serde_parse;
15
16#[proc_macro_derive(TomlInput, attributes(toml_input))]
17pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
18    let input = parse_macro_input!(tokens as DeriveInput);
19    let StructRaw {
20        ident,
21        attrs,
22        data,
23        enum_style,
24    } = StructRaw::from_derive_input(&input).unwrap();
25    let config = Config {
26        enum_style,
27        ..Default::default()
28    };
29    let schema_token;
30    let value_token;
31    match data {
32        Data::Enum(variants) => {
33            schema_token = quote_enum_schema(&ident, &attrs, variants, config);
34            value_token = quote_enum_value();
35        }
36        Data::Struct(fields) => {
37            schema_token = quote_struct_schema(&ident, &attrs, fields.clone(), config);
38            value_token = quote_struct_value(&attrs, fields.clone());
39        }
40    }
41    let token = quote! {
42        impl toml_input::TomlInput for #ident {
43            fn schema() -> Result<toml_input::Schema, toml_input::Error> {
44                use toml;
45                use toml_input::schema;
46                use toml_input::config::EnumStyle;
47                #schema_token
48            }
49            fn into_value(self) -> Result<toml_input::Value, toml_input::Error> {
50                #value_token
51            }
52        }
53    };
54    token.into()
55}
56
57fn quote_enum_schema(
58    ident: &Ident,
59    attrs: &[Attribute],
60    variants: Vec<VariantRaw>,
61    config: Config,
62) -> TokenStream {
63    let enum_ident = ident;
64    let enum_docs = parse_docs(attrs);
65    let inner_type = enum_ident.to_string();
66    let mut tokens = Vec::new();
67    for variant in variants {
68        let VariantRaw { attrs, enum_style } = variant;
69        let variant_docs = parse_docs(&attrs);
70        let variant_config = Config {
71            enum_style: enum_style.or(config.enum_style.clone()),
72            ..Default::default()
73        };
74        let enum_style_token = variant_config.enum_style_token(quote! {variant});
75        let variant_token = quote! {
76            let mut variant = schema::VariantSchema::default();
77            variant.docs = #variant_docs.to_string();
78            let value = variant_iter.next().ok_or(toml_input::Error::EnumEmpty)?;
79            let tag = std::convert::AsRef::as_ref(&value).to_string();
80            let raw = toml::Value::try_from(value)?;
81            let prim_value = toml_input::PrimValue {tag, raw: Some(raw)};
82            variant.value = prim_value;
83            #enum_style_token
84            prim_schema.variants.push(variant);
85        };
86        tokens.push(variant_token);
87    }
88    let enum_style_token = config.enum_style_token(quote! {meta});
89    let enum_token = quote! {
90        use strum::IntoEnumIterator;
91        let default = <#enum_ident as Default>::default();
92        let mut prim_schema = schema::PrimSchema::default();
93        let mut meta = schema::Meta::default();
94        meta.wrap_type = "".to_string();
95        meta.inner_type = #inner_type.to_string();
96        let tag = default.as_ref().to_string();
97        let raw = toml::Value::try_from(default)?;
98        meta.inner_default = toml_input::PrimValue{tag, raw: Some(raw)};
99        meta.defined_docs = #enum_docs.to_string();
100        #enum_style_token;
101        prim_schema.meta = meta;
102        let mut variant_iter = #enum_ident::iter();
103        prim_schema.variants = Vec::new();
104        #(#tokens)*
105        Ok(schema::Schema::Prim(prim_schema))
106    };
107    enum_token
108}
109
110fn quote_enum_value() -> TokenStream {
111    let enum_token = quote! {
112        let tag = self.as_ref().to_string();
113        let raw = toml::Value::try_from(self)?;
114        let prim = toml_input::PrimValue {tag, raw: Some(raw)};
115        Ok(toml_input::Value::Prim(prim))
116    };
117    enum_token
118}
119
120fn quote_struct_schema(
121    ident: &Ident,
122    attrs: &[Attribute],
123    fields: Fields<FieldRaw>,
124    config: Config,
125) -> TokenStream {
126    let struct_ident = ident;
127    let struct_docs = parse_docs(attrs);
128    let inner_type = struct_ident.to_string();
129    let struct_rule = serde_parse::rename_rule(attrs);
130    let mut tokens = Vec::new();
131    for field in fields {
132        let FieldRaw {
133            ident,
134            attrs,
135            ty,
136            enum_style,
137            inner_default,
138        } = field;
139        let field_ident = ident.unwrap();
140        let field_docs = parse_docs(&attrs);
141        let field_rule = serde_parse::rename_rule(&attrs);
142        let field_name = field_ident.to_string();
143        let field_name = struct_rule.case_to(field_name);
144        let field_name = field_rule.alias(field_name);
145        let field_flatten = serde_parse::flatten(&attrs);
146        let field_config = Config {
147            enum_style: enum_style.or(config.enum_style.clone()),
148            inner_default,
149        };
150        let enum_style_token = field_config.enum_style_token(quote! {field});
151        let inner_type = extract_inner_type(&ty);
152        let inner_default_token = field_config.inner_default_token(quote! {field}, inner_type);
153        let field_token = quote! {
154            let mut field = schema::FieldSchema::default();
155            field.ident = #field_name.to_string();
156            field.docs = #field_docs.to_string();
157            field.flat = #field_flatten;
158            field.schema = <#ty as toml_input::TomlInput>::schema()?;
159            #enum_style_token
160            #inner_default_token
161            table.fields.push(field);
162        };
163        tokens.push(field_token);
164    }
165    let enum_style_token = config.enum_style_token(quote! {table});
166    let struct_token = quote! {
167        use std::str::FromStr;
168        let default = <#struct_ident as Default>::default();
169        let mut table = schema::TableSchema::default();
170        let mut meta = schema::Meta::default();
171        meta.wrap_type = "".to_string();
172        meta.inner_type = #inner_type.to_string();
173        let raw = toml::Value::try_from(default)?;
174        meta.inner_default = toml_input::PrimValue::new(raw);
175        meta.defined_docs = #struct_docs.to_string();
176        table.meta = meta;
177        #enum_style_token
178        table.fields = Vec::new();
179        #(#tokens)*
180        Ok(schema::Schema::Table(table))
181    };
182    struct_token
183}
184
185fn quote_struct_value(attrs: &[Attribute], fields: Fields<FieldRaw>) -> TokenStream {
186    let struct_rule = serde_parse::rename_rule(attrs);
187    let mut tokens = Vec::new();
188    for field in fields {
189        let FieldRaw { ident, attrs, .. } = field;
190        let field_ident = ident.unwrap();
191        let field_rule = serde_parse::rename_rule(&attrs);
192        let field_name = field_ident.to_string();
193        let field_name = struct_rule.case_to(field_name);
194        let field_name = field_rule.alias(field_name);
195        let field_flatten = serde_parse::flatten(&attrs);
196        let field_token = quote! {
197            let mut field = toml_input::FieldValue::default();
198            field.ident = #field_name.to_string();
199            field.flat = #field_flatten;
200            field.value = self.#field_ident.into_value()?;
201            table.fields.push(field);
202        };
203        tokens.push(field_token);
204    }
205    let struct_token = quote! {
206        let mut table = toml_input::TableValue::default();
207        #(#tokens)*
208        Ok(toml_input::Value::Table(table))
209    };
210    struct_token
211}
212
213#[derive(Debug, Clone, FromDeriveInput)]
214#[darling(
215    supports(struct_named, enum_any),
216    attributes(toml_input),
217    forward_attrs(doc, serde)
218)]
219struct StructRaw {
220    ident: Ident,
221    attrs: Vec<Attribute>,
222    data: ast::Data<VariantRaw, FieldRaw>,
223    enum_style: Option<EnumStyle>,
224}
225
226#[derive(Debug, Clone, FromField)]
227#[darling(attributes(toml_input), forward_attrs(doc, serde))]
228struct FieldRaw {
229    ident: Option<Ident>,
230    attrs: Vec<Attribute>,
231    ty: Type,
232    enum_style: Option<EnumStyle>,
233    inner_default: Option<String>,
234}
235
236#[derive(Debug, Clone, FromVariant)]
237#[darling(attributes(toml_input), forward_attrs(doc, serde))]
238struct VariantRaw {
239    attrs: Vec<Attribute>,
240    enum_style: Option<EnumStyle>,
241}
242
243fn parse_docs(attrs: &[Attribute]) -> String {
244    let mut docs = Vec::new();
245    for attr in attrs {
246        if !attr.path().is_ident("doc") {
247            continue;
248        }
249        if let Meta::NameValue(name_value) = &attr.meta {
250            if let Expr::Lit(ExprLit {
251                lit: Lit::Str(lit_str),
252                ..
253            }) = name_value.value.clone()
254            {
255                docs.push(lit_str.value());
256            }
257        }
258    }
259    docs.join("\n").to_string()
260}
261
262fn extract_inner_type(ty: &syn::Type) -> TokenStream {
263    if let Type::Path(TypePath { path, .. }) = ty {
264        if let Some(segment) = path.segments.last() {
265            if let PathArguments::AngleBracketed(args) = &segment.arguments {
266                if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
267                    return inner_ty.into_token_stream();
268                }
269            }
270        }
271    }
272    TokenStream::new()
273}
274
275#[derive(Clone, Default)]
276struct Config {
277    enum_style: Option<EnumStyle>,
278    inner_default: Option<String>,
279}
280
281impl Config {
282    fn enum_style_token(&self, tag: TokenStream) -> TokenStream {
283        let mut token = TokenStream::new();
284        if let Some(enum_style) = &self.enum_style {
285            token = quote! {
286                #tag.config.enum_style = Some(#enum_style);
287            };
288        }
289        token
290    }
291
292    fn inner_default_token(&self, tag: TokenStream, inner_type: TokenStream) -> TokenStream {
293        let mut token = TokenStream::new();
294        if inner_type.is_empty() {
295            return token;
296        }
297        if let Some(text) = &self.inner_default {
298            token = quote! {
299                let value = #inner_type::from_str(#text).map_err(|err| toml_input::Error::FromStrError(err.to_string()))?;
300                let raw = toml::Value::try_from(value)?;
301                #tag.set_inner_default(raw);
302            };
303        }
304        token
305    }
306}
307
308#[derive(Debug, Clone, FromMeta)]
309#[derive(Default)]
310enum EnumStyle {
311    Single,
312    #[default]
313    Expand,
314    Fold,
315    Flex,
316    Flex4,
317    Flex5,
318    Flex6,
319    Flex7,
320    Flex8,
321    Flex9,
322    Flex10,
323    Flex11,
324    Flex12,
325}
326
327
328impl ToTokens for EnumStyle {
329    fn to_tokens(&self, tokens: &mut TokenStream) {
330        let token = match self {
331            EnumStyle::Single => quote! { EnumStyle::Single },
332            EnumStyle::Expand => quote! { EnumStyle::Expand },
333            EnumStyle::Fold => quote! { EnumStyle::Fold },
334            EnumStyle::Flex => quote! { EnumStyle::Flex(4) },
335            EnumStyle::Flex4 => quote! { EnumStyle::Flex(4) },
336            EnumStyle::Flex5 => quote! { EnumStyle::Flex(5) },
337            EnumStyle::Flex6 => quote! { EnumStyle::Flex(6) },
338            EnumStyle::Flex7 => quote! { EnumStyle::Flex(7) },
339            EnumStyle::Flex8 => quote! { EnumStyle::Flex(8) },
340            EnumStyle::Flex9 => quote! { EnumStyle::Flex(9) },
341            EnumStyle::Flex10 => quote! { EnumStyle::Flex(10) },
342            EnumStyle::Flex11 => quote! { EnumStyle::Flex(11) },
343            EnumStyle::Flex12 => quote! { EnumStyle::Flex(12) },
344        };
345        tokens.extend(token);
346    }
347}