toml_example_derive/
lib.rs

1extern crate proc_macro;
2
3use proc_macro2::TokenStream;
4use proc_macro2::Ident;
5use proc_macro_error2::{abort, proc_macro_error};
6use quote::quote;
7use syn::{
8    AngleBracketedGenericArguments,
9    AttrStyle::Outer,
10    Attribute, DeriveInput,
11    Expr::Lit,
12    ExprLit, Field, Fields,
13    Fields::Named,
14    GenericArgument,
15    Lit::Str,
16    Meta::{List, NameValue},
17    MetaList, MetaNameValue, PathArguments, PathSegment, Result, Type, TypePath,
18};
19mod case;
20
21struct Intermediate {
22    struct_name: Ident,
23    struct_doc: String,
24    field_example: String,
25}
26
27struct FieldMeta {
28    docs: Vec<String>,
29    default_source: Option<DefaultSource>,
30    nesting_format: Option<NestingFormat>,
31    require: bool,
32    skip: bool,
33    rename: Option<String>,
34    rename_rule: case::RenameRule,
35}
36
37#[derive(Debug)]
38enum DefaultSource {
39    DefaultValue(String),
40    DefaultFn(Option<String>),
41    #[allow(dead_code)]
42    SerdeDefaultFn(String),
43}
44
45#[derive(PartialEq)]
46enum NestingType {
47    None,
48    Vec,
49    Dict,
50}
51
52#[derive(PartialEq)]
53enum NestingFormat {
54    Section(NestingType),
55    Prefix,
56}
57
58fn default_value(ty: String) -> String {
59    match ty.as_str() {
60        "usize" | "u8" | "u16" | "u32" | "u64" | "u128" | "isize" | "i8" | "i16" | "i32"
61        | "i64" | "i128" => "0",
62        "f32" | "f64" => "0.0",
63        _ => "\"\"",
64    }
65    .to_string()
66}
67
68/// return type without Option, Vec
69fn parse_type(
70    ty: &Type,
71    default: &mut String,
72    optional: &mut bool,
73    nesting_format: &mut Option<NestingFormat>,
74) -> Option<String> {
75    let mut r#type = None;
76    if let Type::Path(TypePath { path, .. }) = ty {
77        if let Some(PathSegment { ident, arguments }) = path.segments.last() {
78            let id = ident.to_string();
79            if arguments.is_none() {
80                r#type = Some(id.clone());
81                *default = default_value(id);
82            } else if id == "Option" {
83                *optional = true;
84                if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
85                    args, ..
86                }) = arguments
87                {
88                    if let Some(GenericArgument::Type(ty)) = args.first() {
89                        r#type = parse_type(ty, default, &mut false, nesting_format);
90                    }
91                }
92            } else if id == "Vec" {
93                if nesting_format.is_some() {
94                    *nesting_format = Some(NestingFormat::Section(NestingType::Vec));
95                }
96                if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
97                    args, ..
98                }) = arguments
99                {
100                    if let Some(GenericArgument::Type(ty)) = args.first() {
101                        let mut item_default_value = String::new();
102                        r#type = parse_type(ty, &mut item_default_value, &mut false, &mut None);
103                        *default = if item_default_value.is_empty() {
104                            "[  ]".to_string()
105                        } else {
106                            format!("[ {item_default_value:}, ]")
107                        }
108                    }
109                }
110            } else if id == "HashMap" || id == "BTreeMap" {
111                if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
112                    args, ..
113                }) = arguments
114                {
115                    if let Some(GenericArgument::Type(ty)) = args.last() {
116                        let mut item_default_value = String::new();
117                        r#type = parse_type(ty, &mut item_default_value, &mut false, &mut None);
118                    }
119                }
120                if nesting_format.is_some() {
121                    *nesting_format = Some(NestingFormat::Section(NestingType::Dict));
122                }
123            }
124            // TODO else Complex struct in else
125        }
126    }
127    r#type
128}
129
130fn parse_attrs(
131    attrs: &[Attribute],
132) -> FieldMeta {
133    let mut docs = Vec::new();
134    let mut default_source = None;
135    let mut nesting_format = None;
136    let mut require = false;
137    let mut skip = false;
138    let mut rename = None;
139    let mut rename_rule = case::RenameRule::None;
140
141    for attr in attrs.iter() {
142        match (attr.style, &attr.meta) {
143            (Outer, NameValue(MetaNameValue { path, value, .. })) => {
144                for seg in path.segments.iter() {
145                    if seg.ident == "doc" {
146                        if let Lit(ExprLit {
147                            lit: Str(lit_str), ..
148                        }) = value
149                        {
150                            docs.push(lit_str.value());
151                        }
152                    }
153                }
154            }
155            (
156                Outer,
157                List(MetaList {
158                    path,
159                    tokens: _tokens,
160                    ..
161                }),
162            ) if path
163                .segments
164                .last()
165                .map(|s| s.ident == "serde")
166                .unwrap_or_default() =>
167            {
168                #[cfg(feature = "serde")]
169                {
170                    let token_str = _tokens.to_string();
171                    if token_str.starts_with("default") {
172                        if let Some((_, s)) = token_str.split_once('=') {
173                            default_source = Some(DefaultSource::SerdeDefaultFn(
174                                s.trim().trim_matches('"').into(),
175                            ));
176                        } else {
177                            default_source = Some(DefaultSource::DefaultFn(None));
178                        }
179                    }
180                    if token_str == "skip_deserializing" || token_str == "skip" {
181                        skip = true;
182                    }
183                    if token_str.starts_with("rename") {
184                        if token_str.starts_with("rename_all") {
185                            if let Some((_, s)) = token_str.split_once('=') {
186                                rename_rule = if let Ok(r) =
187                                    case::RenameRule::from_str(s.trim().trim_matches('"'))
188                                {
189                                    r
190                                } else {
191                                    abort!(&_tokens, "unsupported rename rule")
192                                }
193                            }
194                        } else if let Some((_, s)) = token_str.split_once('=') {
195                            rename = Some(s.trim().trim_matches('"').into());
196                        }
197                    }
198                }
199            }
200            (Outer, List(MetaList { path, tokens, .. }))
201                if path
202                    .segments
203                    .last()
204                    .map(|s| s.ident == "toml_example")
205                    .unwrap_or_default() =>
206            {
207                let token_str = tokens.to_string();
208                if token_str.starts_with("default") {
209                    if let Some((_, s)) = token_str.split_once('=') {
210                        default_source = Some(DefaultSource::DefaultValue(s.trim().into()));
211                    } else {
212                        default_source = Some(DefaultSource::DefaultFn(None));
213                    }
214                } else if token_str.starts_with("nesting") {
215                    if let Some((_, s)) = token_str.split_once('=') {
216                        nesting_format = match s.trim() {
217                            "prefix" => Some(NestingFormat::Prefix),
218                            "section" => Some(NestingFormat::Section(NestingType::None)),
219                            _ => abort!(&attr, "please use prefix or section for nesting derive"),
220                        }
221                    } else {
222                        nesting_format = Some(NestingFormat::Section(NestingType::None));
223                    }
224                } else if token_str == "require" {
225                    require = true;
226                } else if token_str == "skip" {
227                    skip = true;
228                } else {
229                    abort!(&attr, format!("{} is not allowed attribute", token_str))
230                }
231            }
232            _ => (),
233        }
234    }
235
236    FieldMeta{
237        docs,
238        default_source,
239        nesting_format,
240        require,
241        skip,
242        rename,
243        rename_rule,
244    }
245}
246
247fn parse_field(
248    field: &Field,
249) -> (
250    DefaultSource,
251    Vec<String>,
252    bool,
253    Option<NestingFormat>,
254    bool,
255    Option<String>,
256) {
257    let mut default_value = String::new();
258    let mut optional = false;
259    let FieldMeta {docs, default_source, mut nesting_format, require, skip, rename, ..} =
260        parse_attrs(&field.attrs);
261    let ty = parse_type(
262        &field.ty,
263        &mut default_value,
264        &mut optional,
265        &mut nesting_format,
266    );
267    let default_source = match default_source {
268        Some(DefaultSource::DefaultFn(_)) => DefaultSource::DefaultFn(ty),
269        Some(DefaultSource::SerdeDefaultFn(f)) => DefaultSource::SerdeDefaultFn(f),
270        Some(DefaultSource::DefaultValue(v)) => DefaultSource::DefaultValue(v),
271        _ => DefaultSource::DefaultValue(default_value),
272    };
273    (
274        default_source,
275        docs,
276        optional && !require,
277        nesting_format,
278        skip,
279        rename,
280    )
281}
282
283fn push_doc_string(example: &mut String, docs: Vec<String>) {
284    for doc in docs.into_iter() {
285        example.push('#');
286        example.push_str(&doc);
287        example.push('\n');
288    }
289}
290
291fn default_key(default: DefaultSource) -> String {
292    if let DefaultSource::DefaultValue(v) = default {
293        let key = v.trim_matches('\"').replace(' ', "").replace('.', "-");
294        if !key.is_empty() {
295            return key;
296        }
297    }
298    "example".into()
299}
300
301#[proc_macro_derive(TomlExample, attributes(toml_example))]
302#[proc_macro_error]
303pub fn derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
304    Intermediate::from_ast(syn::parse_macro_input!(item as syn::DeriveInput))
305        .unwrap()
306        .to_token_stream()
307        .unwrap()
308        .into()
309}
310
311// Transient intermediate for structure parsing
312impl Intermediate{
313    pub fn from_ast(
314        DeriveInput {
315            ident, data, attrs, ..
316        }: syn::DeriveInput,
317    ) -> Result<Intermediate> {
318        let struct_name = ident.clone();
319
320        let FieldMeta{ docs, rename_rule, .. } = parse_attrs(&attrs);
321
322        let struct_doc = {
323            let mut doc = String::new();
324            push_doc_string(&mut doc, docs);
325            doc
326        };
327
328        let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &data {
329            fields
330        } else {
331            abort!(ident, "TomlExample derive only use for struct")
332        };
333
334        let field_example = Self::parse_field_examples(fields, rename_rule);
335
336        Ok(Intermediate {
337            struct_name,
338            struct_doc,
339            field_example,
340        })
341    }
342    pub fn to_token_stream(&self) -> Result<TokenStream> {
343        let Intermediate {
344            struct_name,
345            struct_doc,
346            field_example,
347        } = self;
348
349        let field_example_stream: proc_macro2::TokenStream = field_example.parse()?;
350
351        Ok(quote! {
352            impl toml_example::TomlExample for #struct_name {
353                fn toml_example() -> String {
354                    #struct_name::toml_example_with_prefix("", "")
355                }
356                fn toml_example_with_prefix(label: &str, prefix: &str) -> String{
357                    #struct_doc.to_string() + label + &#field_example_stream
358                }
359            }
360        })
361    }
362
363    fn parse_field_examples(fields: &Fields, rename_rule: case::RenameRule) -> String {
364        // Always put nesting field example in the last to avoid #18
365        let mut field_example = "r##\"".to_string();
366        let mut nesting_field_example = "".to_string();
367
368        if let Named(named_fields) = fields {
369            for f in named_fields.named.iter() {
370                let field_type = parse_type(&f.ty, &mut String::new(), &mut false, &mut None);
371                if let Some(mut field_name) = f.ident.as_ref().map(|i| i.to_string()) {
372                    let (default, doc_str, optional, nesting_format, skip, rename) = parse_field(f);
373                    if skip {
374                        continue;
375                    }
376                    if let Some(rename) = rename {
377                        field_name = rename;
378                    } else {
379                        field_name = rename_rule.apply_to_field(&field_name);
380                    }
381                    if nesting_format
382                        .as_ref()
383                        .map(|f| matches!(f, NestingFormat::Section(_)))
384                        .unwrap_or_default()
385                    {
386                        if let Some(field_type) = field_type {
387                            push_doc_string(&mut nesting_field_example, doc_str);
388                            nesting_field_example.push_str("\"##.to_string()");
389                            let key = default_key(default);
390                            match nesting_format {
391                                Some(NestingFormat::Section(NestingType::Vec)) if optional => nesting_field_example.push_str(&format!(
392                                    " + &{field_type}::toml_example_with_prefix(\"# [[{field_name:}]]\n\", \"# \")"
393                                )),
394                                Some(NestingFormat::Section(NestingType::Vec)) => nesting_field_example.push_str(&format!(
395                                    " + &{field_type}::toml_example_with_prefix(\"[[{field_name:}]]\n\", \"\")"
396                                )),
397                                Some(NestingFormat::Section(NestingType::Dict)) if optional => nesting_field_example.push_str(&format!(
398                                    " + &{field_type}::toml_example_with_prefix(\"# [{field_name:}.{key}]\n\", \"# \")"
399                                )),
400                                Some(NestingFormat::Section(NestingType::Dict)) => nesting_field_example.push_str(&format!(
401                                    " + &{field_type}::toml_example_with_prefix(\"[{field_name:}.{key}]\n\", \"\")"
402                                )),
403                                _ if optional => nesting_field_example.push_str(&format!(
404                                    " + &{field_type}::toml_example_with_prefix(\"# [{field_name:}]\n\", \"# \")"
405                                )),
406                                _ => nesting_field_example.push_str(&format!(
407                                    " + &{field_type}::toml_example_with_prefix(\"[{field_name:}]\n\", \"\")"
408                                ))
409                            };
410                            nesting_field_example.push_str(" + &r##\"");
411                        } else {
412                            abort!(&f.ident, "nesting only work on inner structure")
413                        }
414                    } else if nesting_format == Some(NestingFormat::Prefix) {
415                        push_doc_string(&mut field_example, doc_str);
416                        if let Some(field_type) = field_type {
417                            field_example.push_str("\"##.to_string()");
418                            if optional {
419                                field_example.push_str(&format!(
420                                    " + &{field_type}::toml_example_with_prefix(\"\", \"# {field_name:}.\")"
421                                ));
422                            } else {
423                                field_example.push_str(&format!(
424                                    " + &{field_type}::toml_example_with_prefix(\"\", \"{field_name:}.\")"
425                                ));
426                            }
427                            field_example.push_str(" + &r##\"");
428                        } else {
429                            abort!(&f.ident, "nesting only work on inner structure")
430                        }
431                    } else {
432                        push_doc_string(&mut field_example, doc_str);
433                        if optional {
434                            field_example.push_str("# ");
435                        }
436                        match default {
437                            DefaultSource::DefaultValue(default) => {
438                                field_example.push_str("\"##.to_string() + prefix + &r##\"");
439                                field_example.push_str(field_name.trim_start_matches("r#"));
440                                field_example.push_str(" = ");
441                                field_example.push_str(&default);
442                                field_example.push('\n');
443                            }
444                            DefaultSource::DefaultFn(None) => {
445                                field_example.push_str("\"##.to_string() + prefix + &r##\"");
446                                field_example.push_str(&field_name);
447                                field_example.push_str(" = \"\"\n");
448                            }
449                            DefaultSource::DefaultFn(Some(ty)) => {
450                                field_example.push_str("\"##.to_string() + prefix + &r##\"");
451                                field_example.push_str(&field_name);
452                                field_example.push_str(" = \"##.to_string()");
453                                field_example
454                                    .push_str(&format!(" + &format!(\"{{:?}}\",  {ty}::default())"));
455                                field_example.push_str(" + &r##\"\n");
456                            }
457                            DefaultSource::SerdeDefaultFn(fn_str) => {
458                                field_example.push_str("\"##.to_string() + prefix + &r##\"");
459                                field_example.push_str(&field_name);
460                                field_example.push_str(" = \"##.to_string()");
461                                field_example.push_str(&format!(
462                                    " + &format!(\"{{:?}}\",  {fn_str}())"
463                                ));
464                                field_example.push_str("+ &r##\"\n");
465                            }
466                        }
467                        field_example.push('\n');
468                    }
469                }
470            }
471        }
472        field_example += &nesting_field_example;
473        field_example.push_str("\"##.to_string()");
474
475        field_example
476    }
477}