toml_example_derive/
lib.rs

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