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