Skip to main content

congen_derive/
lib.rs

1use quote::{ToTokens, format_ident, quote};
2use syn::{
3    Attribute, Ident, Item, Meta, Token, parse::Parse, parse_macro_input, parse2,
4    punctuated::Punctuated,
5};
6
7use crate::{enum_config::enum_configuration, struct_config::struct_configuration};
8
9mod enum_config;
10mod struct_config;
11
12/// See documentation in the congen crate
13#[proc_macro_derive(Configuration, attributes(congen))]
14pub fn configuration(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
15    let input = parse_macro_input!(input as Item);
16
17    match input {
18        Item::Struct(input) => struct_configuration(input),
19        Item::Enum(input) => enum_configuration(input),
20        _ => quote! { compile_error!("Configuration only supports structs and enums") },
21    }
22    .into()
23}
24
25enum AttributeParam {
26    Flag(Ident),
27    NameValue {
28        ident: Ident,
29        value: syn::Expr,
30        eq: Token![=],
31    },
32}
33
34impl AttributeParam {
35    pub fn ident(&self) -> &Ident {
36        match self {
37            AttributeParam::Flag(ident) => ident,
38            AttributeParam::NameValue { ident, .. } => ident,
39        }
40    }
41}
42
43impl Parse for AttributeParam {
44    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
45        let ident: Ident = input.parse()?;
46
47        if input.peek(Token![=]) {
48            let eq = input.parse()?;
49            let value = input.parse()?;
50
51            Ok(AttributeParam::NameValue { ident, value, eq })
52        } else {
53            Ok(AttributeParam::Flag(ident))
54        }
55    }
56}
57
58impl ToTokens for AttributeParam {
59    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
60        match self {
61            AttributeParam::Flag(ident) => ident.to_tokens(tokens),
62            AttributeParam::NameValue { ident, value, eq } => {
63                ident.to_tokens(tokens);
64                eq.to_tokens(tokens);
65                value.to_tokens(tokens);
66            }
67        }
68    }
69}
70
71#[derive(Default)]
72struct CongenItemAttribute {
73    debug: bool,
74    default: bool,
75    clone: bool,
76}
77
78impl CongenItemAttribute {
79    fn combine(&self, other: &Self) -> Self {
80        Self {
81            debug: self.debug || other.debug,
82            default: self.default || other.default,
83            clone: self.clone || other.clone,
84        }
85    }
86
87    fn from_attrs<'a, I: IntoIterator<Item = &'a Attribute>>(
88        iter: I,
89        errors: &mut Vec<syn::Error>,
90    ) -> Self {
91        iter.into_iter()
92            .filter_map(|attr| match &attr.meta {
93                Meta::List(meta_list) if meta_list.path.is_ident(&format_ident!("congen")) => {
94                    match parse2(meta_list.tokens.clone()) {
95                        Ok(attr) => Some(attr),
96                        Err(err) => {
97                            errors.push(err);
98                            None
99                        }
100                    }
101                }
102                _ => None,
103            })
104            .fold(CongenItemAttribute::default(), |a, b| a.combine(&b))
105    }
106}
107
108impl Parse for CongenItemAttribute {
109    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
110        let args = Punctuated::<AttributeParam, Token![,]>::parse_terminated(input)?;
111
112        let mut impl_debug = false;
113        let mut gen_default = false;
114        let mut clone = false;
115
116        for arg in args {
117            match arg.ident().to_string().as_str() {
118                "default" => {
119                    if gen_default {
120                        return Err(syn::Error::new_spanned(
121                            arg,
122                            "\"default\" should only be specified once in `congen` attribute",
123                        ));
124                    }
125                    if !matches!(arg, AttributeParam::Flag(_)) {
126                        return Err(syn::Error::new_spanned(
127                            arg,
128                            "\"default\" is a flag and takes no arguments",
129                        ));
130                    }
131                    gen_default = true;
132                }
133                "debug" => {
134                    if impl_debug {
135                        return Err(syn::Error::new_spanned(
136                            arg,
137                            "\"debug\" should only be specified once in `congen` attribute",
138                        ));
139                    }
140                    if !matches!(arg, AttributeParam::Flag(_)) {
141                        return Err(syn::Error::new_spanned(
142                            arg,
143                            "\"debug\" is a flag and takes no arguments",
144                        ));
145                    }
146                    impl_debug = true;
147                }
148                "clone" => {
149                    if clone {
150                        return Err(syn::Error::new_spanned(
151                            arg,
152                            "\"clone\" should only be specified once in `congen` attribute",
153                        ));
154                    }
155                    if !matches!(arg, AttributeParam::Flag(_)) {
156                        return Err(syn::Error::new_spanned(
157                            arg,
158                            "\"clone\" is a flag and takes no arguments",
159                        ));
160                    }
161                    clone = true;
162                }
163                _ => {
164                    return Err(syn::Error::new_spanned(
165                        arg,
166                        "unknown argument to `congen` attribute",
167                    ));
168                }
169            }
170        }
171
172        Ok(CongenItemAttribute {
173            debug: impl_debug,
174            default: gen_default,
175            clone,
176        })
177    }
178}