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