toml_input_derive/
lib.rs

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