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