toml_example_derive/
lib.rs

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