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 flatten: bool,
36 rename: Option<String>,
37 rename_rule: case::RenameRule,
38}
39
40struct ParsedField {
41 docs: Vec<String>,
42 default: DefaultSource,
43 nesting_format: Option<NestingFormat>,
44 skip: bool,
45 is_enum: bool,
46 flatten: bool,
47 name: String,
48 optional: bool,
49 ty: Option<String>,
50}
51
52impl ParsedField {
53 fn push_doc_to_string(&self, s: &mut String) {
54 push_doc_string(s, &self.docs);
55 }
56
57 fn default_key(&self) -> String {
59 if let DefaultSource::DefaultValue(v) = &self.default {
60 let key = v.trim_matches('\"').replace(' ', "").replace('.', "-");
61 if !key.is_empty() {
62 return key;
63 }
64 }
65 "example".into()
66 }
67
68 fn label(&self) -> String {
69 let label = match self.nesting_format {
70 Some(NestingFormat::Section(NestingType::Dict)) => {
71 if self.flatten {
72 self.default_key()
73 } else {
74 format!("{}.{}", self.name, self.default_key())
75 }
76 }
77 Some(NestingFormat::Prefix) => String::new(),
78 _ => {
79 if self.flatten {
80 String::new()
81 } else {
82 self.name.to_string()
83 }
84 }
85 };
86 if label.is_empty() {
87 String::from("label")
88 } else {
89 format!(
90 "
91 if label.is_empty() {{
92 \"{label}\".to_string()
93 }} else {{
94 label.to_string() + \".\" + \"{label}\"
95 }}
96 "
97 )
98 }
99 }
100
101 fn label_format(&self) -> (&str, &str) {
102 match self.nesting_format {
103 Some(NestingFormat::Section(NestingType::Vec)) => {
104 if self.flatten {
105 abort!(
106 "flatten",
107 format!(
108 "Only structs and maps can be flattened! \
109 (But field `{}` is a collection)",
110 self.name
111 )
112 )
113 }
114 ("[[", "]]")
115 }
116 Some(NestingFormat::Section(NestingType::Dict)) => ("[", "]"),
117 Some(NestingFormat::Prefix) => ("", ""),
118 _ => {
119 if self.flatten {
120 ("", "")
121 } else {
122 ("[", "]")
123 }
124 }
125 }
126 }
127
128 fn prefix(&self) -> String {
129 let opt_prefix = if self.optional {
130 "# ".to_string()
131 } else {
132 String::new()
133 };
134 if self.nesting_format == Some(NestingFormat::Prefix) {
135 format!("{}{}.", opt_prefix, self.name)
136 } else {
137 opt_prefix
138 }
139 }
140}
141
142#[derive(Debug)]
143enum DefaultSource {
144 DefaultValue(String),
145 DefaultFn(Option<String>),
146 #[allow(dead_code)]
147 SerdeDefaultFn(String),
148}
149
150#[derive(PartialEq)]
151enum NestingType {
152 None,
153 Vec,
154 Dict,
155}
156
157#[derive(PartialEq)]
158enum NestingFormat {
159 Section(NestingType),
160 Prefix,
161}
162
163fn default_value(ty: String) -> String {
164 match ty.as_str() {
165 "usize" | "u8" | "u16" | "u32" | "u64" | "u128" | "isize" | "i8" | "i16" | "i32"
166 | "i64" | "i128" => "0",
167 "f32" | "f64" => "0.0",
168 _ => "\"\"",
169 }
170 .to_string()
171}
172
173fn parse_type(
175 ty: &Type,
176 default: &mut String,
177 optional: &mut bool,
178 nesting_format: &mut Option<NestingFormat>,
179) -> Option<String> {
180 let mut r#type = None;
181 if let Type::Path(TypePath { path, .. }) = ty {
182 if let Some(PathSegment { ident, arguments }) = path.segments.last() {
183 let id = ident.to_string();
184 if arguments.is_none() {
185 r#type = Some(id.clone());
186 *default = default_value(id);
187 } else if id == "Option" {
188 *optional = true;
189 if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
190 args, ..
191 }) = arguments
192 {
193 if let Some(GenericArgument::Type(ty)) = args.first() {
194 r#type = parse_type(ty, default, &mut false, nesting_format);
195 }
196 }
197 } else if id == "Vec" {
198 if nesting_format.is_some() {
199 *nesting_format = Some(NestingFormat::Section(NestingType::Vec));
200 }
201 if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
202 args, ..
203 }) = arguments
204 {
205 if let Some(GenericArgument::Type(ty)) = args.first() {
206 let mut item_default_value = String::new();
207 r#type = parse_type(ty, &mut item_default_value, &mut false, &mut None);
208 *default = if item_default_value.is_empty() {
209 "[ ]".to_string()
210 } else {
211 format!("[ {item_default_value:}, ]")
212 }
213 }
214 }
215 } else if id == "HashMap" || id == "BTreeMap" {
216 if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
217 args, ..
218 }) = arguments
219 {
220 if let Some(GenericArgument::Type(ty)) = args.last() {
221 let mut item_default_value = String::new();
222 r#type = parse_type(ty, &mut item_default_value, &mut false, &mut None);
223 }
224 }
225 if nesting_format.is_some() {
226 *nesting_format = Some(NestingFormat::Section(NestingType::Dict));
227 }
228 }
229 }
231 }
232 r#type
233}
234
235fn parse_attrs(attrs: &[Attribute]) -> AttrMeta {
236 let mut docs = Vec::new();
237 let mut default_source = None;
238 let mut nesting_format = None;
239 let mut require = false;
240 let mut skip = false;
241 let mut is_enum = false;
242 let mut flatten = false;
243 #[allow(unused_mut)]
245 let mut rename = None;
246 #[allow(unused_mut)]
248 let mut rename_rule = case::RenameRule::None;
249
250 for attr in attrs.iter() {
251 match (attr.style, &attr.meta) {
252 (Outer, NameValue(MetaNameValue { path, value, .. })) => {
253 for seg in path.segments.iter() {
254 if seg.ident == "doc" {
255 if let Lit(ExprLit {
256 lit: Str(lit_str), ..
257 }) = value
258 {
259 docs.push(lit_str.value());
260 }
261 }
262 }
263 }
264 (
265 Outer,
266 List(MetaList {
267 path,
268 tokens: _tokens,
269 ..
270 }),
271 ) if path.segments.last().is_some_and(|s| s.ident == "serde") => {
272 #[cfg(feature = "serde")]
273 {
274 let token_str = _tokens.to_string();
275 for attribute in token_str.split(find_unenclosed_char(',')).map(str::trim) {
276 if attribute.starts_with("default") {
277 if let Some((_, s)) = attribute.split_once('=') {
278 default_source = Some(DefaultSource::SerdeDefaultFn(
279 s.trim().trim_matches('"').into(),
280 ));
281 } else {
282 default_source = Some(DefaultSource::DefaultFn(None));
283 }
284 }
285 if attribute == "skip_deserializing" || attribute == "skip" {
286 skip = true;
287 }
288 if attribute == "flatten" {
289 flatten = true;
290 }
291 if attribute.starts_with("rename") {
292 if attribute.starts_with("rename_all") {
293 if let Some((_, s)) = attribute.split_once('=') {
294 rename_rule = if let Ok(r) =
295 case::RenameRule::from_str(s.trim().trim_matches('"'))
296 {
297 r
298 } else {
299 abort!(&_tokens, "unsupported rename rule")
300 }
301 }
302 } else if let Some((_, s)) = attribute.split_once('=') {
303 rename = Some(s.trim().trim_matches('"').into());
304 }
305 }
306 }
307 }
308 }
309 (Outer, List(MetaList { path, tokens, .. }))
310 if path
311 .segments
312 .last()
313 .map(|s| s.ident == "toml_example")
314 .unwrap_or_default() =>
315 {
316 let token_str = tokens.to_string();
317 for attribute in token_str.split(find_unenclosed_char(',')).map(str::trim) {
318 if attribute.starts_with("default") {
319 if let Some((_, s)) = attribute.split_once('=') {
320 default_source = Some(DefaultSource::DefaultValue(s.trim().into()));
321 } else {
322 default_source = Some(DefaultSource::DefaultFn(None));
323 }
324 } else if attribute.starts_with("nesting") {
325 if let Some((_, s)) = attribute.split_once('=') {
326 nesting_format = match s.trim() {
327 "prefix" => Some(NestingFormat::Prefix),
328 "section" => Some(NestingFormat::Section(NestingType::None)),
329 _ => {
330 abort!(&attr, "please use prefix or section for nesting derive")
331 }
332 }
333 } else {
334 nesting_format = Some(NestingFormat::Section(NestingType::None));
335 }
336 } else if attribute == "require" {
337 require = true;
338 } else if attribute == "skip" {
339 skip = true;
340 } else if attribute == "is_enum" || attribute == "enum" {
341 is_enum = true;
342 } else if attribute == "flatten" {
343 flatten = true;
344 } else {
345 abort!(&attr, format!("{} is not allowed attribute", attribute))
346 }
347 }
348 }
349 _ => (),
350 }
351 }
352
353 AttrMeta {
354 docs,
355 default_source,
356 nesting_format,
357 require,
358 skip,
359 is_enum,
360 flatten,
361 rename,
362 rename_rule,
363 }
364}
365
366fn parse_field(
367 struct_default: Option<&DefaultSource>,
368 field: &Field,
369 rename_rule: case::RenameRule,
370) -> ParsedField {
371 let mut default_value = String::new();
372 let mut optional = false;
373 let AttrMeta {
374 docs,
375 default_source,
376 mut nesting_format,
377 skip,
378 is_enum,
379 flatten,
380 rename,
381 require,
382 ..
383 } = parse_attrs(&field.attrs);
384 let ty = parse_type(
385 &field.ty,
386 &mut default_value,
387 &mut optional,
388 &mut nesting_format,
389 );
390 let default = match default_source {
391 Some(DefaultSource::DefaultFn(_)) => DefaultSource::DefaultFn(ty.clone()),
392 Some(DefaultSource::SerdeDefaultFn(f)) => DefaultSource::SerdeDefaultFn(f),
393 Some(DefaultSource::DefaultValue(v)) => DefaultSource::DefaultValue(v),
394 _ if struct_default.is_some() => DefaultSource::DefaultFn(None),
395 _ => DefaultSource::DefaultValue(default_value),
396 };
397 let name = if let Some(field_name) = field.ident.as_ref().map(|i| i.to_string()) {
398 rename.unwrap_or(rename_rule.apply_to_field(&field_name))
399 } else {
400 abort!(&field, "The field should has name")
401 };
402 ParsedField {
403 docs,
404 default,
405 nesting_format,
406 skip,
407 is_enum,
408 flatten,
409 name,
410 optional: optional && !require,
411 ty,
412 }
413}
414
415fn push_doc_string(example: &mut String, docs: &[String]) {
416 for doc in docs.iter() {
417 example.push('#');
418 example.push_str(doc);
419 example.push('\n');
420 }
421}
422
423#[proc_macro_derive(TomlExample, attributes(toml_example))]
424#[proc_macro_error]
425pub fn derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
426 Intermediate::from_ast(syn::parse_macro_input!(item as syn::DeriveInput))
427 .unwrap()
428 .to_token_stream()
429 .unwrap()
430 .into()
431}
432
433impl Intermediate {
435 pub fn from_ast(
436 DeriveInput {
437 ident, data, attrs, ..
438 }: syn::DeriveInput,
439 ) -> Result<Intermediate> {
440 let struct_name = ident.clone();
441
442 let AttrMeta {
443 docs,
444 default_source,
445 rename_rule,
446 ..
447 } = parse_attrs(&attrs);
448
449 let struct_doc = {
450 let mut doc = String::new();
451 push_doc_string(&mut doc, &docs);
452 doc
453 };
454
455 let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &data {
456 fields
457 } else {
458 abort!(ident, "TomlExample derive only use for struct")
459 };
460
461 let field_example = Self::parse_field_examples(ident, default_source, fields, rename_rule);
462
463 Ok(Intermediate {
464 struct_name,
465 struct_doc,
466 field_example,
467 })
468 }
469
470 pub fn to_token_stream(&self) -> Result<TokenStream> {
471 let Intermediate {
472 struct_name,
473 struct_doc,
474 field_example,
475 } = self;
476
477 let field_example_stream: proc_macro2::TokenStream = field_example.parse()?;
478
479 Ok(quote! {
480 impl toml_example::TomlExample for #struct_name {
481 fn toml_example() -> String {
482 #struct_name::toml_example_with_prefix("", ("", ""), "")
483 }
484 fn toml_example_with_prefix(label: &str, label_format: (&str, &str), prefix: &str)
485 -> String {
486 let wrapped_label = if label_format.0.is_empty() {
487 String::new()
488 } else {
489 label_format.0.to_string() + label + label_format.1
490 };
491 #struct_doc.to_string() + &wrapped_label + &#field_example_stream
492 }
493 }
494 })
495 }
496
497 fn parse_field_examples(
498 struct_ty: Ident,
499 struct_default: Option<DefaultSource>,
500 fields: &Fields,
501 rename_rule: case::RenameRule,
502 ) -> String {
503 let mut field_example = "r##\"".to_string();
504 let mut nesting_field_example = "".to_string();
505
506 if let Named(named_fields) = fields {
507 for f in named_fields.named.iter() {
508 let field = parse_field(struct_default.as_ref(), f, rename_rule);
509 if field.skip {
510 continue;
511 }
512
513 if field.nesting_format.is_some() {
514 let (example, nesting_section_newline) =
518 if field.nesting_format == Some(NestingFormat::Prefix) {
519 (&mut field_example, "")
520 } else if field.flatten {
521 (
522 &mut field_example,
523 if field.nesting_format
524 == Some(NestingFormat::Section(NestingType::None))
525 {
526 ""
527 } else {
528 "\n"
529 },
530 )
531 } else {
532 (&mut nesting_field_example, "\n")
533 };
534
535 field.push_doc_to_string(example);
536 if let Some(ref field_type) = field.ty {
537 example.push_str("\"##.to_string()");
538 let (before, after) = field.label_format();
539 let label_format = format!(
540 "(\"{}{before}\", \"{after}{nesting_section_newline}\")",
541 if field.optional && field.nesting_format != Some(NestingFormat::Prefix)
542 {
543 "# "
544 } else {
545 ""
546 }
547 );
548 example.push_str(&format!(
549 " + &{field_type}::toml_example_with_prefix(\
550 &{}, {}, \"{}\"\
551 )",
552 field.label(),
553 label_format,
554 field.prefix()
555 ));
556 example.push_str(" + &r##\"");
557 } else {
558 abort!(&f.ident, "nesting only work on inner structure")
559 }
560 } else {
561 field.push_doc_to_string(&mut field_example);
563 if field.optional {
564 field_example.push_str("# ");
565 }
566 field_example.push_str("\"##.to_string() + prefix + &r##\"");
567 field_example.push_str(field.name.trim_start_matches("r#"));
568 match field.default {
569 DefaultSource::DefaultValue(default) => {
570 field_example.push_str(" = ");
571 field_example.push_str(&default);
572 field_example.push('\n');
573 }
574 DefaultSource::DefaultFn(None) => match struct_default {
575 Some(DefaultSource::DefaultFn(None)) => {
576 let suffix = format!(
577 ".{}",
578 f.ident
579 .as_ref()
580 .expect_or_abort("Named fields always have and ident")
581 );
582 handle_default_fn_source(
583 &mut field_example,
584 field.is_enum,
585 struct_ty.to_string(),
586 Some(suffix),
587 );
588 }
589 Some(DefaultSource::SerdeDefaultFn(ref fn_str)) => {
590 let suffix = format!(
591 ".{}",
592 f.ident
593 .as_ref()
594 .expect_or_abort("Named fields always have an ident")
595 );
596 handle_serde_default_fn_source(
597 &mut field_example,
598 field.is_enum,
599 fn_str,
600 Some(suffix),
601 );
602 }
603 Some(DefaultSource::DefaultValue(_)) => abort!(
604 f.ident,
605 "Setting a default value on a struct is not supported!"
606 ),
607 _ => field_example.push_str(" = \"\"\n"),
608 },
609 DefaultSource::DefaultFn(Some(ty)) => {
610 handle_default_fn_source(&mut field_example, field.is_enum, ty, None)
611 }
612 DefaultSource::SerdeDefaultFn(ref fn_str) => {
613 handle_serde_default_fn_source(
614 &mut field_example,
615 field.is_enum,
616 fn_str,
617 None,
618 )
619 }
620 }
621 field_example.push('\n');
622 }
623 }
624 }
625 field_example += &nesting_field_example;
626 field_example.push_str("\"##.to_string()");
627
628 field_example
629 }
630}
631
632fn handle_default_fn_source(
633 field_example: &mut String,
634 is_enum: bool,
635 type_ident: String,
636 suffix: Option<String>,
637) {
638 let suffix = suffix.unwrap_or_default();
639 field_example.push_str(" = \"##.to_string()");
640 if is_enum {
641 field_example.push_str(&format!(
642 " + &format!(\"\\\"{{:?}}\\\"\", {type_ident}::default(){suffix})"
643 ));
644 } else {
645 field_example.push_str(&format!(
646 " + &format!(\"{{:?}}\", {type_ident}::default(){suffix})"
647 ));
648 }
649 field_example.push_str(" + &r##\"\n");
650}
651
652fn handle_serde_default_fn_source(
653 field_example: &mut String,
654 is_enum: bool,
655 fn_str: &String,
656 suffix: Option<String>,
657) {
658 let suffix = suffix.unwrap_or_default();
659 field_example.push_str(" = \"##.to_string()");
660 if is_enum {
661 field_example.push_str(&format!(
662 " + &format!(\"\\\"{{:?}}\\\"\", {fn_str}(){suffix})"
663 ));
664 } else {
665 field_example.push_str(&format!(" + &format!(\"{{:?}}\", {fn_str}(){suffix})"));
666 }
667 field_example.push_str("+ &r##\"\n");
668}
669
670fn find_unenclosed_char(pat: char) -> impl FnMut(char) -> bool {
673 let mut quotes = 0;
674 let mut single_quotes = 0;
675 let mut brackets = 0;
676 let mut braces = 0;
677 let mut parenthesis = 0;
678 let mut is_escaped = false;
679 move |char| -> bool {
680 if is_escaped {
681 is_escaped = false;
682 return false;
683 } else if char == '\\' {
684 is_escaped = true;
685 } else if (quotes % 2 == 1 && char != '"') || (single_quotes % 2 == 1 && char != '\'') {
686 return false;
687 } else {
688 match char {
689 '"' => quotes += 1,
690 '\'' => single_quotes += 1,
691 '[' => brackets += 1,
692 ']' => brackets -= 1,
693 '{' => braces += 1,
694 '}' => braces -= 1,
695 '(' => parenthesis += 1,
696 ')' => parenthesis -= 1,
697 _ => {}
698 }
699 }
700 char == pat
701 && quotes % 2 == 0
702 && single_quotes % 2 == 0
703 && brackets == 0
704 && braces == 0
705 && parenthesis == 0
706 }
707}