mithril_doc_derive/
lib.rs1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use syn::{parse_macro_input, Data, DeriveInput, Fields, Type};
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\")"
66 .parse()
67 .unwrap())
68 }
69 Data::Struct(data_struct) => {
70 match &data_struct.fields {
71 Fields::Named(fields) => fields.named.iter().map(format_field).collect(),
72 _ => {
73 vec![]
75 }
76 }
77 }
78 Data::Union(_) => {
79 vec![]
81 }
82 };
83 Ok(data_code)
84}
85
86struct FieldInfo {
87 name: String,
88 doc: String,
89 example: Option<String>,
90 is_mandatory: bool,
91}
92
93fn format_field(field: &syn::Field) -> FieldInfo {
94 let _vis_str = match field.vis {
95 syn::Visibility::Public(_) => "public",
96 syn::Visibility::Restricted(_) => "restricted",
97 syn::Visibility::Inherited => "inherited",
98 };
99
100 let doc = doc::extract_doc_comment(&field.attrs[..]).join("\n");
101
102 let example = field
103 .attrs
104 .iter()
105 .find(|attr| attr.path().is_ident("example"))
106 .and_then(|attr| match &attr.meta {
107 syn::Meta::NameValue(syn::MetaNameValue {
108 value:
109 syn::Expr::Lit(syn::ExprLit {
110 lit: syn::Lit::Str(s),
111 ..
112 }),
113 ..
114 }) => Some(s.value()),
115 _ => None,
116 });
117
118 let is_mandatory = match field.ty.clone() {
119 Type::Path(type_path) => type_path
120 .path
121 .segments
122 .first()
123 .map(|segment| segment.ident != "Option")
124 .unwrap_or(true),
125 _ => true,
126 };
127
128 let field_info = FieldInfo {
129 name: field.ident.as_ref().unwrap().to_string(),
130 doc,
131 example,
132 is_mandatory,
133 };
134
135 field_info
136}
137
138#[proc_macro_derive(Documenter, attributes(example))]
140pub fn doc_extractor(input: TokenStream) -> TokenStream {
141 let ast = parse_macro_input!(input as DeriveInput);
143 extract_struct_info(&ast, "Documenter", false)
144}
145
146#[proc_macro_derive(DocumenterDefault, attributes(example))]
148pub fn doc_extractor_default(input: TokenStream) -> TokenStream {
149 let ast = parse_macro_input!(input as DeriveInput);
151 extract_struct_info(&ast, "DocumenterDefault", true)
152}