tonysd_config_manager_proc/
lib.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2022 JSRPC “Kryptonite”
3
4mod generator;
5mod utils;
6
7use std::{fmt::Write, str::FromStr};
8
9use proc_macro::TokenStream as TokenStream0;
10use proc_macro2::TokenStream;
11use quote::{quote, ToTokens};
12use syn::{parse::Parser, punctuated::Punctuated, *};
13
14use generator::*;
15use utils::{config::*, field::*, parser::*, top_level::*};
16
17/// Macro generating an implementation of the `ConfigInit` trait
18/// or constructing global variable. \
19///
20/// For more info see crate level documentation
21#[proc_macro_attribute]
22pub fn config(attrs: TokenStream0, input: TokenStream0) -> TokenStream0 {
23    let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
24    let attrs = parser.parse(attrs).unwrap();
25    let mut annotations = String::from("#[derive(::config_manager::__private::__Config__)]");
26    attrs.iter().for_each(|attr| {
27        std::write!(&mut annotations, "\n{}", (quote! { #[#attr]})).unwrap();
28    });
29    let mut annotations =
30        TokenStream0::from_str(&annotations).expect("can't parse annotations as tokenstream");
31    annotations.extend(input.into_iter());
32    annotations
33}
34
35#[proc_macro_derive(
36    __Config__,
37    attributes(
38        source,
39        flatten,
40        subcommand,
41        config,
42        env_prefix,
43        clap,
44        global_name,
45        file,
46        table,
47        default_order,
48        __debug_cmd_input__
49    )
50)]
51pub fn generate_config(input: TokenStream0) -> TokenStream0 {
52    let input = parse_macro_input!(input as DeriveInput);
53
54    let class_ident = input.ident;
55
56    let AppTopLevelInfo {
57        env_prefix,
58        clap_app_info,
59        configs,
60        debug_cmd_input,
61        table_name,
62        default_order,
63    } = AppTopLevelInfo::extract(&input.attrs);
64
65    let class: DataStruct = match input.data {
66        Data::Struct(s) => s,
67        _ => panic!("config macro input should be a Struct"),
68    };
69
70    unzip_n::unzip_n!(2);
71    let (fields_json_definition, clap_fields): (
72        Vec<(proc_macro2::Ident, TokenStream)>,
73        Vec<ClapInitialization>,
74    ) = class
75        .fields
76        .into_iter()
77        .map(|field| {
78            let ProcessFieldResult {
79                name,
80                clap_field,
81                initialization,
82            } = if field_is_flatten(&field) {
83                process_flatten_field(field)
84            } else if field_is_subcommand(&field) {
85                process_subcommand_field(field, &debug_cmd_input)
86            } else {
87                process_field(field, &table_name, &default_order)
88            };
89            ((name, initialization), clap_field)
90        })
91        .unzip_n();
92
93    generate_final_struct_and_supporting_code(InitializationInfo {
94        env_prefix,
95        class_ident,
96        clap_app_info,
97        configs,
98        clap_fields,
99        fields_json_definition,
100        debug_cmd_input,
101    })
102    .into()
103}
104
105/// Annotated with this macro structure can be used
106/// as a flatten argument in the [config](attr.config.html) macro.
107#[proc_macro_derive(Flatten, attributes(source, flatten, subcommand, table, default_order))]
108pub fn generate_flatten(input: TokenStream0) -> TokenStream0 {
109    let input = parse_macro_input!(input as DeriveInput);
110
111    let table_name = extract_table_name(&input.attrs);
112    let default_order = extract_source_order(&input.attrs);
113
114    let class_ident = input.ident;
115    let class: DataStruct = match input.data {
116        Data::Struct(s) => s,
117        _ => panic!("config macro input should be a Struct"),
118    };
119
120    unzip_n::unzip_n!(2);
121    let (fields_json_definition, clap_fields): (
122        Vec<(proc_macro2::Ident, TokenStream)>,
123        Punctuated<ClapInitialization, Token![.]>,
124    ) = class
125        .fields
126        .into_iter()
127        .map(|field| {
128            let ProcessFieldResult {
129                name,
130                clap_field,
131                initialization,
132            } = if field_is_flatten(&field) {
133                process_flatten_field(field)
134            } else if field_is_subcommand(&field) {
135                panic!("subcommands are forbidden in the nested structures")
136            } else {
137                process_field(field, &table_name, &default_order)
138            };
139            ((name, initialization), clap_field)
140        })
141        .unzip_n();
142
143    generate_flatten_implementation(class_ident, clap_fields, fields_json_definition).into()
144}