mithril_doc_derive/
lib.rs1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use syn::{Data, DeriveInput, Fields, Type, parse_macro_input};
6
7mod doc;
8
9fn extract_struct_info(
10 ast: &DeriveInput,
11 trait_name: &str,
12 implement_default: bool,
13) -> TokenStream {
14 let name_ident = &ast.ident;
15 let trait_ident = format_ident!("{trait_name}");
16 let default_values_variable_ident = format_ident!("entries");
17
18 let init_default_values_code_quote = if implement_default {
19 quote! {
20 let #default_values_variable_ident = <#name_ident>::default().collect().unwrap();
21 }
22 } else {
23 quote! {
24 let #default_values_variable_ident = Map::<String,Value>::new();
25 }
26 };
27
28 let fields_info = extract_fields_info(ast).unwrap();
29 let data_code_quote = fields_info.iter().map(|field_info| {
30 let name = &field_info.name;
31 let doc = &field_info.doc;
32 let example = match &field_info.example {
33 Some(value) => quote! {
34 Some(#value.to_string())
35 },
36 None => quote! {
37 None
38 },
39 };
40 let is_mandatory = &field_info.is_mandatory;
41
42 quote! {
43 struct_data.add_param(#name, #doc, Some(#name.to_uppercase().to_string()), #default_values_variable_ident.get(#name).map(|v| v.to_string()), #example, #is_mandatory);
44 }
45 }).collect::<Vec<_>>();
46
47 let output_quote = quote! {
48 impl #trait_ident for #name_ident {
49 fn extract() -> StructDoc {
50 #init_default_values_code_quote
51 let mut struct_data = StructDoc::default();
52 #(#data_code_quote)*
53 struct_data
54 }
55 }
56 };
57
58 output_quote.into()
59}
60
61fn extract_fields_info(ast: &DeriveInput) -> Result<Vec<FieldInfo>, String> {
62 let data = &ast.data;
63 let data_code = match data {
64 Data::Enum(_) => {
65 return Err("compile_error!(\"Enum types are not supported\")".parse().unwrap());
66 }
67 Data::Struct(data_struct) => {
68 match &data_struct.fields {
69 Fields::Named(fields) => fields.named.iter().map(format_field).collect(),
70 _ => {
71 vec![]
73 }
74 }
75 }
76 Data::Union(_) => {
77 vec![]
79 }
80 };
81 Ok(data_code)
82}
83
84struct FieldInfo {
85 name: String,
86 doc: String,
87 example: Option<String>,
88 is_mandatory: bool,
89}
90
91fn format_field(field: &syn::Field) -> FieldInfo {
92 let _vis_str = match field.vis {
93 syn::Visibility::Public(_) => "public",
94 syn::Visibility::Restricted(_) => "restricted",
95 syn::Visibility::Inherited => "inherited",
96 };
97
98 let doc = doc::extract_doc_comment(&field.attrs[..]).join("\n");
99
100 let example = field
101 .attrs
102 .iter()
103 .find(|attr| attr.path().is_ident("example"))
104 .and_then(|attr| match &attr.meta {
105 syn::Meta::NameValue(syn::MetaNameValue {
106 value:
107 syn::Expr::Lit(syn::ExprLit {
108 lit: syn::Lit::Str(s),
109 ..
110 }),
111 ..
112 }) => Some(s.value()),
113 _ => None,
114 });
115
116 let is_mandatory = match field.ty.clone() {
117 Type::Path(type_path) => type_path
118 .path
119 .segments
120 .first()
121 .map(|segment| segment.ident != "Option")
122 .unwrap_or(true),
123 _ => true,
124 };
125
126 FieldInfo {
127 name: field.ident.as_ref().unwrap().to_string(),
128 doc,
129 example,
130 is_mandatory,
131 }
132}
133
134#[proc_macro_derive(Documenter, attributes(example))]
136pub fn doc_extractor(input: TokenStream) -> TokenStream {
137 let ast = parse_macro_input!(input as DeriveInput);
139 extract_struct_info(&ast, "Documenter", false)
140}
141
142#[proc_macro_derive(DocumenterDefault, attributes(example))]
144pub fn doc_extractor_default(input: TokenStream) -> TokenStream {
145 let ast = parse_macro_input!(input as DeriveInput);
147 extract_struct_info(&ast, "DocumenterDefault", true)
148}