1mod errors;
6mod option_field;
7mod scalar_field;
8mod util;
9mod vector_field;
10
11use errors::OptStoreErr;
12use util::identify_field_type;
13
14use proc_macro::TokenStream;
15use std::collections::HashMap;
16
17#[proc_macro_derive(OptStore, attributes(opt))]
58pub fn opt_store_derive(input: TokenStream) -> TokenStream {
59 let input = &syn::parse_macro_input!(input as syn::DeriveInput);
60
61 match generate_opt_store_impl(input) {
62 Ok(generated) => generated,
63 Err(err) => err.to_compile_error().into(),
64 }
65}
66
67fn generate_opt_store_impl(input: &syn::DeriveInput) -> Result<TokenStream, syn::Error> {
68 let struct_name = &input.ident;
69 let (impl_generics, _, where_clause) = &input.generics.split_for_impl();
70
71 let struct_data = match &input.data {
72 syn::Data::Struct(data) => data,
73 _ => return Err(OptStoreErr::MustPutOnStruct.at(input.ident.span())),
74 };
75
76 let struct_span = input.ident.span();
77
78 let mut cfg_vec = Vec::<proc_macro2::TokenStream>::new();
79 let mut init_vec = Vec::<proc_macro2::TokenStream>::new();
80 let mut set_vec = Vec::<proc_macro2::TokenStream>::new();
81 for field in &struct_data.fields {
82 collect_impl_for_field(
83 field,
84 &mut cfg_vec,
85 &mut init_vec,
86 &mut set_vec,
87 struct_span,
88 )?;
89 }
90
91 let expanded = quote::quote! {
92 #[automatically_derived]
93 impl #impl_generics #struct_name #where_clause {
94 pub fn with_defaults() -> #struct_name {
95 #struct_name {
96 #(#init_vec),*
97 }
98 }
99 }
100
101 #[automatically_derived]
102 impl #impl_generics cliargs::OptStore for #struct_name #where_clause {
103 fn make_opt_cfgs(&self) -> Vec<cliargs::OptCfg> {
104 vec![
105 #(#cfg_vec),*
106 ]
107 }
108
109 fn set_field_values(&mut self, m: &std::collections::HashMap<&str, Vec<&str>>) -> Result<(), cliargs::errors::InvalidOption> {
110 #(#set_vec)*
111 Ok(())
112 }
113 }
114 };
115
116 Ok(expanded.into())
118}
119
120fn collect_impl_for_field(
121 field: &syn::Field,
122 cfg_vec: &mut Vec<proc_macro2::TokenStream>,
123 init_vec: &mut Vec<proc_macro2::TokenStream>,
124 set_vec: &mut Vec<proc_macro2::TokenStream>,
125 struct_span: proc_macro2::Span,
126) -> Result<(), syn::Error> {
127 let field_name = match field.ident.as_ref() {
128 Some(ident) => ident,
129 None => return Err(OptStoreErr::MustNotPutOnTuple.at(struct_span)),
130 };
131 let field_span = field_name.span();
132
133 let mut attr_map = HashMap::<String, String>::new();
134 let mut attr_span: Option<proc_macro2::Span> = None;
135 for attr in &field.attrs {
136 if attr.path().is_ident("opt") {
137 let span = attr.path().get_ident().unwrap().span();
138 attr_span = Some(span);
139
140 collect_impl_for_field_attr(attr, &mut attr_map, span)?;
141 }
142 }
143
144 if let Some((ty_ident, in_vec, in_opt)) = identify_field_type(&field.ty) {
145 if in_opt {
146 return option_field::collect_impl(
147 field_name, ty_ident, cfg_vec, init_vec, set_vec, &attr_map, attr_span, field_span,
148 );
149 } else if in_vec {
150 return vector_field::collect_impl(
151 field_name, ty_ident, cfg_vec, init_vec, set_vec, &attr_map, attr_span, field_span,
152 );
153 } else {
154 return scalar_field::collect_impl(
155 field_name, ty_ident, cfg_vec, init_vec, set_vec, &attr_map, attr_span, field_span,
156 );
157 }
158 }
159
160 Err(OptStoreErr::BadFieldType.at(field_span))
161}
162
163fn collect_impl_for_field_attr(
164 attr: &syn::Attribute,
165 attr_map: &mut HashMap<String, String>,
166 attr_span: proc_macro2::Span,
167) -> Result<(), syn::Error> {
168 let nested = attr.parse_args_with(
169 syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
170 )?;
171 for meta in nested {
172 match meta {
173 syn::Meta::NameValue(meta) => {
174 if meta.path.is_ident("cfg") {
175 match meta.value {
176 syn::Expr::Lit(lit) => match lit.lit {
177 syn::Lit::Str(s) => {
178 let value = s.value();
179 match &value.split_once("=") {
180 Some((lhs, rhs)) => {
181 attr_map.insert("names".to_string(), lhs.to_string());
182 attr_map.insert("defaults".to_string(), rhs.to_string());
183 }
184 None => {
185 attr_map.insert("names".to_string(), value);
186 }
187 }
188 }
189 _ => return Err(OptStoreErr::BadAttrMetaValueCfg.at(attr_span)),
190 },
191 _ => return Err(OptStoreErr::BadAttrMetaValueCfg.at(attr_span)),
192 }
193 } else if meta.path.is_ident("desc") {
194 match meta.value {
195 syn::Expr::Lit(lit) => match lit.lit {
196 syn::Lit::Str(s) => {
197 attr_map.insert("desc".to_string(), s.value());
198 }
199 _ => return Err(OptStoreErr::BadAttrMetaValueDesc.at(attr_span)),
200 },
201 _ => return Err(OptStoreErr::BadAttrMetaValueDesc.at(attr_span)),
202 }
203 } else if meta.path.is_ident("arg") {
204 match meta.value {
205 syn::Expr::Lit(lit) => match lit.lit {
206 syn::Lit::Str(s) => {
207 attr_map.insert("arg".to_string(), s.value());
208 }
209 _ => return Err(OptStoreErr::BadAttrMetaValueArg.at(attr_span)),
210 },
211 _ => return Err(OptStoreErr::BadAttrMetaValueArg.at(attr_span)),
212 }
213 } else {
214 return Err(OptStoreErr::BadAttrMetaName.at(attr_span));
215 }
216 }
217 _ => return Err(OptStoreErr::BadAttrMetaName.at(attr_span)),
218 }
219 }
220
221 Ok(())
222}