use proc_macro::TokenStream;
use quote::quote;
use syn::spanned::Spanned as _;
use syn::{Item, Type};
#[proc_macro_attribute]
pub fn ui(attr: TokenStream, item: TokenStream) -> TokenStream {
export(attr, item, World::Ui)
}
#[proc_macro_attribute]
pub fn daemon(attr: TokenStream, item: TokenStream) -> TokenStream {
export(attr, item, World::Daemon)
}
enum World {
Ui,
Daemon,
}
fn export(attr: TokenStream, item: TokenStream, world: World) -> TokenStream {
if !attr.is_empty() {
let attr = proc_macro2::TokenStream::from(attr);
return syn::Error::new(attr.span(), "this attribute takes no arguments")
.to_compile_error()
.into();
}
let parsed = syn::parse_macro_input!(item as Item);
let ty: Type = match &parsed {
Item::Struct(item) => {
if !item.generics.params.is_empty() {
return generic_error(item.generics.span());
}
let ident = &item.ident;
syn::parse_quote!(#ident)
}
Item::Enum(item) => {
if !item.generics.params.is_empty() {
return generic_error(item.generics.span());
}
let ident = &item.ident;
syn::parse_quote!(#ident)
}
Item::Impl(item) if item.trait_.is_some() => {
if !item.generics.params.is_empty() {
return generic_error(item.generics.span());
}
(*item.self_ty).clone()
}
other => {
return syn::Error::new(
other.span(),
"put this attribute on the plugin's struct or enum, or on its plugin trait impl",
)
.to_compile_error()
.into();
}
};
let export = match world {
World::Ui => quote!(::standard_plugin::__export_ui!(#ty);),
World::Daemon => quote!(::standard_plugin::__export_daemon!(#ty);),
};
quote!(#parsed #export).into()
}
fn generic_error(span: proc_macro2::Span) -> TokenStream {
syn::Error::new(
span,
"a plugin type cannot be generic: the component exports one type",
)
.to_compile_error()
.into()
}