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