use proc_macro::TokenStream;
use quote::quote;
use unsynn::{LiteralString, Parse, ToTokens, TokenIter};
#[proc_macro_attribute]
pub fn migration(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr2: proc_macro2::TokenStream = attr.into();
let mut tokens = TokenIter::new(attr2);
let version = match LiteralString::parse(&mut tokens) {
Ok(v) => v,
Err(e) => {
let msg = format!("expected string literal for migration version: {e}");
return quote! { compile_error!(#msg); }.into();
}
};
let version_lit = version.to_token_stream();
let item: proc_macro2::TokenStream = item.into();
let item_str = item.to_string();
let fn_name = match extract_fn_name(&item_str) {
Some(name) => name,
None => {
return quote! { compile_error!("expected function"); }.into();
}
};
let fn_ident = quote::format_ident!("{}", fn_name);
let registration_ident = quote::format_ident!(
"__DIBS_MIGRATION_{}",
fn_name.to_uppercase().replace('-', "_")
);
quote! {
#item
#[allow(non_upper_case_globals)]
#[::dibs::inventory::collect]
static #registration_ident: ::dibs::Migration = ::dibs::Migration {
version: #version_lit,
name: stringify!(#fn_ident),
run: |ctx| Box::pin(#fn_ident(ctx)),
};
}
.into()
}
fn extract_fn_name(s: &str) -> Option<&str> {
let idx = s.find("fn ")?;
let rest = &s[idx + 3..].trim_start();
let end = rest.find(|c: char| !c.is_alphanumeric() && c != '_')?;
Some(&rest[..end])
}