#![feature(proc_macro_span)]
use std::sync::LazyLock;
use std::sync::Mutex;
use std::collections::HashMap;
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::Parse;
use syn::parse::ParseStream;
use syn::parse_macro_input;
use syn::ItemStruct;
static REGISTRY: LazyLock<Mutex<HashMap<String, Vec<String>>>> = LazyLock::new(|| Mutex::new(HashMap::default()));
struct AutoRegistryArgs {
registry: syn::LitStr,
path: Option<syn::LitStr>,
}
impl Parse for AutoRegistryArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut registry = None;
let mut path = None;
loop {
let key: syn::Ident = input.parse()?;
input.parse::<syn::Token![=]>()?;
let value: syn::LitStr = input.parse()?;
match key.to_string().as_str() {
"registry" => registry = Some(value),
"path" => path = Some(value),
_ => {
return Err(syn::Error::new(
key.span(),
format!(
"Unknown attribute `{}`, excepted `registry` or `path`",
key.to_string()
),
))
}
}
if input.is_empty() {
break;
}
input.parse::<syn::Token![,]>()?;
}
if registry.is_none() {
return Err(syn::Error::new(
input.span(),
"Missing required attribute `registry`".to_string(),
));
}
Ok(AutoRegistryArgs {
registry: registry.unwrap(),
path,
})
}
}
#[proc_macro_attribute]
pub fn auto_registry(attr: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as AutoRegistryArgs);
let input = parse_macro_input!(input as ItemStruct);
let ident = &input.ident;
let path = if let Some(path) = args.path {
let value = path.value();
if value.is_empty() {
value
} else {
format!("{}::{}", value, ident.to_string().as_str())
}
} else {
let path = match std::path::PathBuf::from(input
.ident
.span()
.unwrap()
.file())
.canonicalize()
{
Ok(path) => path,
Err(e) => {
return syn::Error::new(
input.ident.span(),
format!("Failed to canonicalize path: {}", e),
)
.to_compile_error()
.into();
}
};
let crate_path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let relative_path = path.strip_prefix(&crate_path).unwrap();
let relative_path_str = relative_path.to_string_lossy();
let pos = if let Some(pos) = relative_path_str.find("/") {
pos + 1
} else {
0
};
let module_path = relative_path_str
.split_at(pos)
.1
.strip_suffix(".rs")
.unwrap()
.replace("/", "::");
if module_path.is_empty() {
format!("crate::{}", ident.to_string())
} else {
format!("crate::{module_path}::{}", ident.to_string())
}
};
let mut reg = REGISTRY.lock().unwrap();
if let Some(ref mut vec) = reg.get_mut(args.registry.value().as_str()) {
vec.push(path);
} else {
reg.insert(args.registry.value(), vec![path]);
}
quote! {
#input
}
.into()
}
struct GenerateRegistryArgs {
registry: syn::LitStr,
collector: Option<syn::Expr>,
mapper: Option<syn::Expr>,
output: syn::Ident,
}
impl Parse for GenerateRegistryArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut registry = None;
let mut collector = None;
let mut mapper = None;
let mut output = None;
loop {
let key: syn::Ident = input.parse()?;
input.parse::<syn::Token![=]>()?;
match key.to_string().as_str() {
"registry" => registry = Some(input.parse()?),
"collector" => collector = Some(input.parse()?),
"mapper" => mapper = Some(input.parse()?),
"output" => output = Some(input.parse()?),
_ => {
return Err(syn::Error::new(
key.span(),
format!(
"Unknown attribute `{}`, excepted `registry`, `collector`, `mapper` or `output`",
key.to_string()
),
))
}
}
if input.is_empty() {
break;
}
input.parse::<syn::Token![,]>()?;
}
if registry.is_none() {
return Err(syn::Error::new(
input.span(),
"Missing required attribute `registry`".to_string(),
));
} else if output.is_none() {
return Err(syn::Error::new(
input.span(),
"Missing required attribute `output`".to_string(),
));
} else if collector.is_none() && mapper.is_none() {
return Err(syn::Error::new(
input.span(),
"Macro requires that either `collector` or `mapper` be set".to_string(),
));
}
Ok(GenerateRegistryArgs {
registry: registry.unwrap(),
collector,
mapper,
output: output.unwrap(),
})
}
}
#[proc_macro_attribute]
pub fn generate_registry(attr: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as GenerateRegistryArgs);
let reg = REGISTRY.lock().unwrap();
let mut stream = proc_macro2::TokenStream::new();
if let Some(names) = reg.get(args.registry.value().as_str()) {
for name in names {
let struct_name: proc_macro2::TokenStream = name.parse().unwrap();
if let Some(ref mapper) = args.mapper
{
stream.extend(quote::quote_spanned!(proc_macro2::Span::call_site() =>
#mapper!(#struct_name);
));
}
else
{
stream.extend(quote::quote_spanned!(proc_macro2::Span::call_site() =>
#struct_name::default();
));
}
}
} else {
panic!(
"Unable to find registry item with key=`{}`",
args.registry.value()
);
}
let rest: proc_macro2::TokenStream = input.into();
let output = args.output;
if let Some(collector) = args.collector
{
quote! {
macro_rules! #output {
() => { #collector!(#stream) };
}
#rest
}
}
else
{
quote! {
macro_rules! #output {
() => { #stream };
}
#rest
}
}
.into()
}