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 if field.optional {
572 field_example.push_str(&default.replace('\n', "\n# "));
573 } else {
574 field_example.push_str(&default);
575 }
576 field_example.push('\n');
577 }
578 DefaultSource::DefaultFn(None) => match struct_default {
579 Some(DefaultSource::DefaultFn(None)) => {
580 let suffix = format!(
581 ".{}",
582 f.ident
583 .as_ref()
584 .expect_or_abort("Named fields always have and ident")
585 );
586 handle_default_fn_source(
587 &mut field_example,
588 field.is_enum,
589 struct_ty.to_string(),
590 Some(suffix),
591 );
592 }
593 Some(DefaultSource::SerdeDefaultFn(ref fn_str)) => {
594 let suffix = format!(
595 ".{}",
596 f.ident
597 .as_ref()
598 .expect_or_abort("Named fields always have an ident")
599 );
600 handle_serde_default_fn_source(
601 &mut field_example,
602 field.is_enum,
603 fn_str,
604 Some(suffix),
605 );
606 }
607 Some(DefaultSource::DefaultValue(_)) => abort!(
608 f.ident,
609 "Setting a default value on a struct is not supported!"
610 ),
611 _ => field_example.push_str(" = \"\"\n"),
612 },
613 DefaultSource::DefaultFn(Some(ty)) => {
614 handle_default_fn_source(&mut field_example, field.is_enum, ty, None)
615 }
616 DefaultSource::SerdeDefaultFn(ref fn_str) => {
617 handle_serde_default_fn_source(
618 &mut field_example,
619 field.is_enum,
620 fn_str,
621 None,
622 )
623 }
624 }
625 field_example.push('\n');
626 }
627 }
628 }
629 field_example += &nesting_field_example;
630 field_example.push_str("\"##.to_string()");
631
632 field_example
633 }
634}
635
636fn handle_default_fn_source(
637 field_example: &mut String,
638 is_enum: bool,
639 type_ident: String,
640 suffix: Option<String>,
641) {
642 let suffix = suffix.unwrap_or_default();
643 field_example.push_str(" = \"##.to_string()");
644 if is_enum {
645 field_example.push_str(&format!(
646 " + &format!(\"\\\"{{:?}}\\\"\", {type_ident}::default(){suffix})"
647 ));
648 } else {
649 field_example.push_str(&format!(
650 " + &format!(\"{{:?}}\", {type_ident}::default(){suffix})"
651 ));
652 }
653 field_example.push_str(" + &r##\"\n");
654}
655
656fn handle_serde_default_fn_source(
657 field_example: &mut String,
658 is_enum: bool,
659 fn_str: &String,
660 suffix: Option<String>,
661) {
662 let suffix = suffix.unwrap_or_default();
663 field_example.push_str(" = \"##.to_string()");
664 if is_enum {
665 field_example.push_str(&format!(
666 " + &format!(\"\\\"{{:?}}\\\"\", {fn_str}(){suffix})"
667 ));
668 } else {
669 field_example.push_str(&format!(" + &format!(\"{{:?}}\", {fn_str}(){suffix})"));
670 }
671 field_example.push_str("+ &r##\"\n");
672}
673
674fn find_unenclosed_char(pat: char) -> impl FnMut(char) -> bool {
677 let mut quotes = 0;
678 let mut single_quotes = 0;
679 let mut brackets = 0;
680 let mut braces = 0;
681 let mut parenthesis = 0;
682 let mut is_escaped = false;
683 move |char| -> bool {
684 if is_escaped {
685 is_escaped = false;
686 return false;
687 } else if char == '\\' {
688 is_escaped = true;
689 } else if (quotes % 2 == 1 && char != '"') || (single_quotes % 2 == 1 && char != '\'') {
690 return false;
691 } else {
692 match char {
693 '"' => quotes += 1,
694 '\'' => single_quotes += 1,
695 '[' => brackets += 1,
696 ']' => brackets -= 1,
697 '{' => braces += 1,
698 '}' => braces -= 1,
699 '(' => parenthesis += 1,
700 ')' => parenthesis -= 1,
701 _ => {}
702 }
703 }
704 char == pat
705 && quotes % 2 == 0
706 && single_quotes % 2 == 0
707 && brackets == 0
708 && braces == 0
709 && parenthesis == 0
710 }
711}