config_manager_proc/
lib.rs1mod 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#[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#[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}