1#![feature(proc_macro_span)]
2use std::sync::LazyLock;
3use std::sync::Mutex;
4use std::collections::HashMap;
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::parse::Parse;
9use syn::parse::ParseStream;
10use syn::parse_macro_input;
11use syn::ItemStruct;
12
13static REGISTRY: LazyLock<Mutex<HashMap<String, Vec<String>>>> = LazyLock::new(|| Mutex::new(HashMap::default()));
14
15struct AutoRegistryArgs {
17 registry: syn::LitStr,
19 path: Option<syn::LitStr>,
22}
23
24impl Parse for AutoRegistryArgs {
26 fn parse(input: ParseStream) -> syn::Result<Self> {
27 let mut registry = None;
28 let mut path = None;
29 loop {
30 let key: syn::Ident = input.parse()?;
31 input.parse::<syn::Token![=]>()?;
32 let value: syn::LitStr = input.parse()?;
33
34 match key.to_string().as_str() {
35 "registry" => registry = Some(value),
36 "path" => path = Some(value),
37 _ => {
38 return Err(syn::Error::new(
39 key.span(),
40 format!(
41 "Unknown attribute `{}`, excepted `registry` or `path`",
42 key.to_string()
43 ),
44 ))
45 }
46 }
47 if input.is_empty() {
48 break;
49 }
50 input.parse::<syn::Token![,]>()?;
51 }
52
53 if registry.is_none() {
54 return Err(syn::Error::new(
55 input.span(),
56 "Missing required attribute `registry`".to_string(),
57 ));
58 }
59
60 Ok(AutoRegistryArgs {
61 registry: registry.unwrap(),
62 path,
63 })
64 }
65}
66
67#[proc_macro_attribute]
88pub fn auto_registry(attr: TokenStream, input: TokenStream) -> TokenStream {
89 let args = parse_macro_input!(attr as AutoRegistryArgs);
90 let input = parse_macro_input!(input as ItemStruct);
91
92 let ident = &input.ident;
93
94 let path = if let Some(path) = args.path {
95 let value = path.value();
96 if value.is_empty() {
97 value
98 } else {
99 format!("{}::{}", value, ident.to_string().as_str())
100 }
101 } else {
102 let path = match std::path::PathBuf::from(input
105 .ident
106 .span()
107 .unwrap()
108 .file())
109 .canonicalize()
110 {
111 Ok(path) => path,
112 Err(e) => {
113 return syn::Error::new(
114 input.ident.span(),
115 format!("Failed to canonicalize path: {}", e),
116 )
117 .to_compile_error()
118 .into();
119 }
120 };
121
122 let crate_path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
123 let relative_path = path.strip_prefix(&crate_path).unwrap();
124 let relative_path_str = relative_path.to_string_lossy();
125 let pos = if let Some(pos) = relative_path_str.find("/") {
127 pos + 1
128 } else {
129 0
130 };
131
132 let module_path = relative_path_str
133 .split_at(pos)
134 .1
135 .strip_suffix(".rs")
136 .unwrap()
137 .replace("/", "::");
138
139 if module_path.is_empty() {
140 format!("crate::{}", ident.to_string())
141 } else {
142 format!("crate::{module_path}::{}", ident.to_string())
143 }
144 };
145
146 let mut reg = REGISTRY.lock().unwrap();
147 if let Some(ref mut vec) = reg.get_mut(args.registry.value().as_str()) {
148 vec.push(path);
149 } else {
150 reg.insert(args.registry.value(), vec![path]);
151 }
152
153 quote! {
154 #input
155 }
156 .into()
157}
158
159struct GenerateRegistryArgs {
161 registry: syn::LitStr,
163 collector: Option<syn::Expr>,
165 mapper: Option<syn::Expr>,
167 output: syn::Ident,
169}
170
171impl Parse for GenerateRegistryArgs {
173 fn parse(input: ParseStream) -> syn::Result<Self> {
174 let mut registry = None;
175 let mut collector = None;
176 let mut mapper = None;
177 let mut output = None;
178 loop {
179 let key: syn::Ident = input.parse()?;
180 input.parse::<syn::Token![=]>()?;
181
182 match key.to_string().as_str() {
183 "registry" => registry = Some(input.parse()?),
184 "collector" => collector = Some(input.parse()?),
185 "mapper" => mapper = Some(input.parse()?),
186 "output" => output = Some(input.parse()?),
187 _ => {
188 return Err(syn::Error::new(
189 key.span(),
190 format!(
191 "Unknown attribute `{}`, excepted `registry`, `collector`, `mapper` or `output`",
192 key.to_string()
193 ),
194 ))
195 }
196 }
197 if input.is_empty() {
198 break;
199 }
200 input.parse::<syn::Token![,]>()?;
201 }
202
203 if registry.is_none() {
204 return Err(syn::Error::new(
205 input.span(),
206 "Missing required attribute `registry`".to_string(),
207 ));
208 } else if output.is_none() {
209 return Err(syn::Error::new(
210 input.span(),
211 "Missing required attribute `output`".to_string(),
212 ));
213 } else if collector.is_none() && mapper.is_none() {
214 return Err(syn::Error::new(
215 input.span(),
216 "Macro requires that either `collector` or `mapper` be set".to_string(),
217 ));
218 }
219
220 Ok(GenerateRegistryArgs {
221 registry: registry.unwrap(),
222 collector,
223 mapper,
224 output: output.unwrap(),
225 })
226 }
227}
228
229#[proc_macro_attribute]
312pub fn generate_registry(attr: TokenStream, input: TokenStream) -> TokenStream {
313 let args = parse_macro_input!(attr as GenerateRegistryArgs);
314 let reg = REGISTRY.lock().unwrap();
315
316 let mut stream = proc_macro2::TokenStream::new();
317 if let Some(names) = reg.get(args.registry.value().as_str()) {
318 for name in names {
319 let struct_name: proc_macro2::TokenStream = name.parse().unwrap();
320 if let Some(ref mapper) = args.mapper
321 {
322 stream.extend(quote::quote_spanned!(proc_macro2::Span::call_site() =>
323 #mapper!(#struct_name);
324 ));
325 }
326 else
327 {
328 stream.extend(quote::quote_spanned!(proc_macro2::Span::call_site() =>
329 #struct_name::default();
330 ));
331 }
332 }
333 } else {
334 panic!(
335 "Unable to find registry item with key=`{}`",
336 args.registry.value()
337 );
338 }
339
340 let rest: proc_macro2::TokenStream = input.into();
341 let output = args.output;
342
343 if let Some(collector) = args.collector
344 {
345 quote! {
346 macro_rules! #output {
347 () => { #collector!(#stream) };
348 }
349 #rest
350 }
351 }
352 else
353 {
354 quote! {
355 macro_rules! #output {
356 () => { #stream };
357 }
358 #rest
359 }
360 }
361 .into()
362}