#![cfg_attr(docsrs, feature(doc_cfg))]
use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, LitStr, Type, parse_macro_input};
#[proc_macro_attribute]
pub fn react_message(attr: TokenStream, item: TokenStream) -> TokenStream {
let name_override = match parse_name_only_attr(attr, "react_message") {
Ok(name) => name,
Err(e) => return e.to_compile_error().into(),
};
let input = parse_macro_input!(item as DeriveInput);
let PayloadParts {
ident,
impl_generics,
ty_generics,
where_clause,
name,
} = payload_parts(&input, name_override);
quote! {
#[derive(::serde::Deserialize, ::ts_rs::TS)]
#input
impl #impl_generics ::bevy::ecs::event::Event for #ident #ty_generics #where_clause {
type Trigger<'a> = ::bevy::ecs::event::GlobalTrigger;
}
impl #impl_generics ::bevy_react::ReactPayload for #ident #ty_generics #where_clause {
const NAME: &'static str = #name;
}
}
.into()
}
#[proc_macro_attribute]
pub fn react_request(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut name_override: Option<String> = None;
let mut response: Option<Type> = None;
let arg_parser = syn::meta::parser(|meta| {
if try_parse_name_arg(&meta, &mut name_override)? {
Ok(())
} else if meta.path.is_ident("response") {
response = Some(meta.value()?.parse::<Type>()?);
Ok(())
} else {
Err(meta.error(
"unsupported `react_request` argument; expected `name = \"...\"` or `response = Type`",
))
}
});
parse_macro_input!(attr with arg_parser);
let response = match response {
Some(ty) => ty,
None => {
return syn::Error::new(
proc_macro2::Span::call_site(),
"`react_request` requires a `response = Type` argument",
)
.to_compile_error()
.into();
}
};
let input = parse_macro_input!(item as DeriveInput);
let PayloadParts {
ident,
impl_generics,
ty_generics,
where_clause,
name,
} = payload_parts(&input, name_override);
quote! {
#[derive(::serde::Deserialize, ::ts_rs::TS)]
#input
impl #impl_generics ::bevy_react::ReactRequest for #ident #ty_generics #where_clause {
const NAME: &'static str = #name;
type Response = #response;
}
}
.into()
}
#[proc_macro_attribute]
pub fn react_event(attr: TokenStream, item: TokenStream) -> TokenStream {
let name_override = match parse_name_only_attr(attr, "react_event") {
Ok(name) => name,
Err(e) => return e.to_compile_error().into(),
};
let input = parse_macro_input!(item as DeriveInput);
let PayloadParts {
ident,
impl_generics,
ty_generics,
where_clause,
name,
} = payload_parts(&input, name_override);
quote! {
#[derive(::serde::Serialize, ::ts_rs::TS)]
#input
impl #impl_generics ::bevy_react::ReactEvent for #ident #ty_generics #where_clause {
const NAME: &'static str = #name;
}
}
.into()
}
fn try_parse_name_arg(
meta: &syn::meta::ParseNestedMeta,
out: &mut Option<String>,
) -> syn::Result<bool> {
if meta.path.is_ident("name") {
*out = Some(meta.value()?.parse::<LitStr>()?.value());
Ok(true)
} else {
Ok(false)
}
}
fn parse_name_only_attr(attr: TokenStream, macro_name: &str) -> syn::Result<Option<String>> {
let mut name_override: Option<String> = None;
let parser = syn::meta::parser(|meta| {
if try_parse_name_arg(&meta, &mut name_override)? {
Ok(())
} else {
Err(meta.error(format!(
"unsupported `{macro_name}` argument; expected `name = \"...\"`"
)))
}
});
syn::parse::Parser::parse(parser, attr)?;
Ok(name_override)
}
struct PayloadParts<'a> {
ident: &'a syn::Ident,
impl_generics: syn::ImplGenerics<'a>,
ty_generics: syn::TypeGenerics<'a>,
where_clause: Option<&'a syn::WhereClause>,
name: String,
}
fn payload_parts<'a>(input: &'a DeriveInput, name_override: Option<String>) -> PayloadParts<'a> {
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
PayloadParts {
ident,
impl_generics,
ty_generics,
where_clause,
name: name_override.unwrap_or_else(|| lower_first(&ident.to_string())),
}
}
fn lower_first(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
Some(first) => first.to_lowercase().collect::<String>() + chars.as_str(),
None => String::new(),
}
}