config_manager_proc/
lib.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2022 JSRPC “Kryptonite”
3
4mod generator;
5mod utils;
6
7use proc_macro::TokenStream as TokenStream0;
8use proc_macro2::{Span, TokenStream};
9use quote::{quote, quote_spanned, ToTokens};
10use syn::{parse::Parser, punctuated::Punctuated, spanned::Spanned, *};
11
12use generator::*;
13use utils::{
14    attributes::{extract_docs, ALLOWED_FLATTEN_ATTRS},
15    config::*,
16    field::*,
17    panic_site, panic_span,
18    parser::*,
19    str_to_tokens,
20    top_level::*,
21    PanicOnNone,
22};
23
24pub(crate) use syn::Error;
25pub(crate) type Result<T> = std::result::Result<T, Error>;
26
27/// Macro generating an implementation of the `ConfigInit` trait
28/// or constructing global variable. \
29///
30/// For more info see crate level documentation
31#[proc_macro_attribute]
32pub fn config(attrs: TokenStream0, input: TokenStream0) -> TokenStream0 {
33    let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
34    let attrs = match parser.parse(attrs) {
35        Ok(attrs) => attrs,
36        Err(err) => return err.into_compile_error().into(),
37    }
38    .into_iter()
39    .collect::<Vec<_>>();
40
41    let mut annotations: TokenStream0 =
42        quote!(#[derive(::config_manager::__private::__Config__)]).into();
43    annotations.extend(input.clone());
44
45    let input = parse_macro_input!(input as DeriveInput);
46
47    let new_code: TokenStream0 = match generate_config_inner(input, &attrs) {
48        Ok(res) => res.into(),
49        Err(err) => return err.into_compile_error().into(),
50    };
51    annotations.extend(new_code);
52    annotations
53}
54
55#[proc_macro_derive(
56    __Config__,
57    attributes(
58        source,
59        flatten,
60        subcommand,
61        config,
62        env_prefix,
63        clap,
64        global_name,
65        file,
66        table,
67        default_order,
68        __debug_cmd_input__
69    )
70)]
71pub fn generate_config(_input: TokenStream0) -> TokenStream0 {
72    TokenStream0::new()
73}
74
75fn generate_config_inner(input: DeriveInput, crate_attrs: &[Meta]) -> Result<TokenStream> {
76    let class_ident = input.ident;
77    let docs = extract_docs(&input.attrs);
78
79    let AppTopLevelInfo {
80        env_prefix,
81        clap_app_info,
82        configs,
83        debug_cmd_input,
84        table_name,
85        default_order,
86    } = AppTopLevelInfo::extract(crate_attrs, docs)?;
87
88    let class: DataStruct = match input.data {
89        Data::Struct(s) => s,
90        _ => panic_site!("config macro input should be a Struct"),
91    };
92
93    let mut fields_json_definition = Vec::new();
94    let mut clap_fields = Vec::new();
95
96    for field in class.fields {
97        check_field_attributes(&field)?;
98
99        let res = if field_is_flatten(&field) {
100            process_flatten_field(field)?
101        } else if field_is_subcommand(&field).is_some() {
102            process_subcommand_field(field, &debug_cmd_input)?
103        } else {
104            process_field(field, &table_name, &default_order)?
105        };
106
107        fields_json_definition.push((res.name, res.initialization));
108        clap_fields.push(res.clap_field);
109    }
110
111    generate_final_struct_and_supporting_code(InitializationInfo {
112        env_prefix,
113        class_ident,
114        clap_app_info,
115        configs,
116        clap_fields,
117        fields_json_definition,
118        debug_cmd_input,
119    })
120}
121
122/// Annotated with this macro structure can be used
123/// as a flatten argument in the [config](attr.config.html) macro.
124#[proc_macro_derive(Flatten, attributes(source, flatten, subcommand, table, default_order))]
125pub fn generate_flatten(input: TokenStream0) -> TokenStream0 {
126    let input = parse_macro_input!(input as DeriveInput);
127
128    match generate_flatten_inner(input) {
129        Ok(res) => res.into(),
130        Err(err) => err.into_compile_error().into(),
131    }
132}
133
134fn generate_flatten_inner(input: DeriveInput) -> Result<TokenStream> {
135    let class_attrs = input
136        .attrs
137        .into_iter()
138        .map(|attr| attr.meta)
139        .collect::<Vec<_>>();
140    check_unfamilliar_attrs(&class_attrs, ALLOWED_FLATTEN_ATTRS)?;
141    let table_name = extract_table_name(&class_attrs)?;
142    let default_order = extract_source_order(&class_attrs)?;
143
144    let class_ident = input.ident;
145    let class: DataStruct = match input.data {
146        Data::Struct(s) => s,
147        _ => panic_site!("config macro input should be a Struct"),
148    };
149
150    let mut fields_json_definition = Vec::new();
151    let mut clap_fields = Punctuated::<ClapInitialization, Token![.]>::new();
152
153    for field in class.fields {
154        check_field_attributes(&field)?;
155
156        let res = if field_is_flatten(&field) {
157            process_flatten_field(field)
158        } else if let Some(attr) = field_is_subcommand(&field) {
159            Err(Error::new(
160                attr.meta.span(),
161                "subcommands are forbidden in the nested structures",
162            ))
163        } else {
164            process_field(field, &table_name, &default_order)
165        }?;
166
167        fields_json_definition.push((res.name, res.initialization));
168        clap_fields.push(res.clap_field);
169    }
170
171    generate_flatten_implementation(class_ident, clap_fields, fields_json_definition)
172}