mithril_doc_derive/
lib.rs

1extern 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                    // Not implemented!
74                    vec![]
75                }
76            }
77        }
78        Data::Union(_) => {
79            // Not implemented!
80            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/// To extract doc from a struct.
139#[proc_macro_derive(Documenter, attributes(example))]
140pub fn doc_extractor(input: TokenStream) -> TokenStream {
141    // Parse the input tokens into a syntax tree
142    let ast = parse_macro_input!(input as DeriveInput);
143    extract_struct_info(&ast, "Documenter", false)
144}
145
146/// To extract doc from a struct with Default trait.
147#[proc_macro_derive(DocumenterDefault, attributes(example))]
148pub fn doc_extractor_default(input: TokenStream) -> TokenStream {
149    // Parse the input tokens into a syntax tree
150    let ast = parse_macro_input!(input as DeriveInput);
151    extract_struct_info(&ast, "DocumenterDefault", true)
152}