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 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
124fn 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 }
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
365impl 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 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 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}