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#[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}