use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{
Ident, Result, Token,
parse::{Parse, ParseStream},
};
struct InComponentArgs {
component_name: Ident,
role: Option<Ident>,
}
impl Parse for InComponentArgs {
fn parse(input: ParseStream) -> Result<Self> {
let component_name = input.parse()?;
let mut role = None;
if input.peek(Token![,]) {
let _ = input.parse::<Token![,]>()?;
if input.peek(Ident) {
let key: Ident = input.parse()?;
if key == "role" {
let _ = input.parse::<Token![=]>()?;
role = Some(input.parse()?);
if input.peek(Token![,]) {
let _ = input.parse::<Token![,]>()?;
}
}
}
}
Ok(InComponentArgs {
component_name,
role,
})
}
}
pub fn expand(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = match syn::parse::<InComponentArgs>(attr) {
Ok(args) => args,
Err(err) => {
let err_msg = err.to_string();
let item_tokens: proc_macro2::TokenStream = item.into();
return TokenStream::from(quote! {
compile_error!(#err_msg);
#item_tokens
});
}
};
let component_name = args.component_name;
let component_str = component_name.to_string();
let role_enum = if let Some(role_ident) = args.role {
let role_str = role_ident.to_string().to_lowercase();
match role_str.as_str() {
"public" => quote! { Some(::waddling_errors::Role::Public) },
"developer" => quote! { Some(::waddling_errors::Role::Developer) },
"internal" => quote! { Some(::waddling_errors::Role::Internal) },
_ => {
let err_msg = format!(
"Invalid role '{}'. Expected: public, developer, or internal",
role_str
);
let item_tokens: proc_macro2::TokenStream = item.into();
return TokenStream::from(quote! {
compile_error!(#err_msg);
#item_tokens
});
}
}
} else {
quote! { Some(::waddling_errors::Role::Internal) }
};
let item_tokens: proc_macro2::TokenStream = item.into();
let item_mod: Option<syn::ItemMod> = syn::parse2(item_tokens.clone()).ok();
if let Some(mut item_mod) = item_mod {
if let Some((_, ref mut content)) = item_mod.content {
let metadata_items: proc_macro2::TokenStream = quote! {
#[doc(hidden)]
pub const __COMPONENT: &str = #component_str;
#[doc(hidden)]
pub const __COMPONENT_MODULE_PATH: &str = module_path!();
#[doc(hidden)]
pub const __COMPONENT_FILE: &str = file!();
#[doc(hidden)]
pub const __COMPONENT_ROLE: Option<::waddling_errors::Role> = #role_enum;
#[doc(hidden)]
#[cfg(feature = "metadata")]
pub fn __register_component_location(registry: &mut ::waddling_errors::doc_generator::DocRegistry) {
registry.register_component_location_with_role(__COMPONENT, __COMPONENT_FILE, __COMPONENT_ROLE);
}
};
let metadata_parsed: Vec<syn::Item> = match syn::parse2(metadata_items) {
Ok(syn::File { items, .. }) => items,
Err(_) => vec![],
};
for item in metadata_parsed.into_iter().rev() {
content.insert(0, item);
}
}
TokenStream::from(quote! { #item_mod })
} else {
let metadata_mod_name = Ident::new(
&format!("__component_marker_{}", component_str.to_lowercase()),
Span::call_site(),
);
let output = quote! {
#item_tokens
#[doc(hidden)]
#[allow(non_snake_case)]
pub mod #metadata_mod_name {
pub const COMPONENT: &str = #component_str;
pub const MODULE_PATH: &str = module_path!();
pub const FILE: &str = file!();
pub const ROLE: Option<::waddling_errors::Role> = #role_enum;
#[cfg(feature = "metadata")]
pub fn register_location(registry: &mut ::waddling_errors::doc_generator::DocRegistry) {
registry.register_component_location_with_role(COMPONENT, FILE, ROLE);
}
}
};
TokenStream::from(output)
}
}