use proc_macro::TokenStream;
use proc_macro2::{TokenStream as TokenStream2, Ident, Span};
use syn::{ItemFn, AttributeArgs, FnArg, Receiver};
use quote::quote;
use darling::FromMeta;
use crate::utils;
#[allow(clippy::cognitive_complexity)]
fn infer_event_type(dispatch_type: &str) -> Option<&'static str> {
match dispatch_type {
t if t.contains("ReadyDispatch") => Some("Ready"),
t if t.contains("ChannelCreateDispatch") => Some("ChannelCreate"),
t if t.contains("ChannelUpdateDispatch") => Some("ChannelUpdate"),
t if t.contains("ChannelDeleteDispatch") => Some("ChannelDelete"),
t if t.contains("ChannelPinsUpdateDispatch") => Some("ChannelPinsUpdate"),
t if t.contains("GuildCreateDispatch") => Some("GuildCreate"),
t if t.contains("GuildUpdateDispatch") => Some("GuildUpdate"),
t if t.contains("GuildDeleteDispatch") => Some("GuildDelete"),
t if t.contains("GuildBanAddDispatch") => Some("GuildBanAdd"),
t if t.contains("GuildBanRemoveDispatch") => Some("GuildBanRemove"),
t if t.contains("GuildEmojisUpdateDispatch") => Some("GuildEmojisUpdate"),
t if t.contains("GuildIntegrationsUpdateDispatch") => Some("GuildIntegrationsUpdate"),
t if t.contains("GuildMemberAddDispatch") => Some("GuildMemberAdd"),
t if t.contains("GuildMemberRemoveDispatch") => Some("GuildMemberRemove"),
t if t.contains("GuildMemberUpdateDispatch") => Some("GuildMemberUpdate"),
t if t.contains("GuildMembersChunkDispatch") => Some("GuildMembersChunk"),
t if t.contains("GuildRoleCreateDispatch") => Some("GuildRoleCreate"),
t if t.contains("GuildRoleUpdateDispatch") => Some("GuildRoleUpdate"),
t if t.contains("GuildRoleDeleteDispatch") => Some("GuildRoleDelete"),
t if t.contains("MessageCreateDispatch") => Some("MessageCreate"),
t if t.contains("MessageUpdateDispatch") => Some("MessageUpdate"),
t if t.contains("MessageDeleteDispatch") => Some("MessageDelete"),
t if t.contains("MessageDeleteBulkDispatch") => Some("MessageDeleteBulk"),
t if t.contains("MessageReactionAddDispatch") => Some("MessageReactionAdd"),
t if t.contains("MessageReactionRemoveDispatch") => Some("MessageReactionRemove"),
t if t.contains("MessageReactionRemoveAllDispatch") => Some("MessageReactionRemoveAll"),
t if t.contains("MessageReactionRemoveEmojiDispatch") => Some("MessageReactionRemoveEmoji"),
t if t.contains("PresenceUpdateDispatch") => Some("PresenceUpdate"),
t if t.contains("TypingStartDispatch") => Some("TypingStart"),
t if t.contains("UserUpdateDispatch") => Some("UserUpdate"),
t if t.contains("VoiceStateUpdateDispatch") => Some("VoiceStateUpdate"),
t if t.contains("VoiceServerUpdateDispatch") => Some("VoiceServerUpdate"),
t if t.contains("WebhooksUpdateDispatch") => Some("WebhooksUpdate"),
_ => None
}
}
fn adapt_method(item: &ItemFn, rcv: &Receiver, arguments: (&Ident, &Ident), event: String) -> TokenStream {
let (ctx_name, data_name) = arguments;
let self_tokens = if rcv.mutability.is_some() {
quote!(&'a mut self)
} else {
quote!(&'a self)
};
let func = &item.sig.ident;
let reg_name = Ident::new(&format!("__register_{}", item.sig.ident), Span::call_site());
let dispatch = Ident::new(&format!("{}Dispatch", event), Span::call_site());
let content = &item.block;
let event = if rcv.mutability.is_some() {
Ident::new(&format!("{}Mut", event), Span::call_site())
} else {
Ident::new(&event, Span::call_site())
};
let quote = quote! {
const #reg_name: ::automate::events::StatefulListener<Self> = ::automate::events::StatefulListener::#event(Self::#func);
fn #func<'a>(#self_tokens, #ctx_name: &'a Context, #data_name: &'a #dispatch) -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = Result<(), Error>> + Send + 'a>> {
Box::pin(async move {
#content
})
}
};
quote.into()
}
fn adapt_function(item: &ItemFn, arguments: (&Ident, &Ident), event: String) -> TokenStream {
let (ctx_name, data_name) = arguments;
let func = &item.sig.ident;
let reg_name = Ident::new(&format!("__register_{}", item.sig.ident), Span::call_site());
let dispatch = Ident::new(&format!("{}Dispatch", event), Span::call_site());
let event = Ident::new(&event, Span::call_site());
let content = &item.block;
let quote = quote! {
const #reg_name: ::automate::events::ListenerType = ::automate::events::ListenerType::#event(#func);
fn #func<'a>(#ctx_name: &'a Context, #data_name: &'a #dispatch) -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = Result<(), Error>> + Send + 'a>> {
Box::pin(async move {
#content
})
}
};
quote.into()
}
#[derive(FromMeta)]
struct Args {
#[darling(default)]
event: Option<String>,
#[darling(default)]
_priority: Option<u8>,
}
pub fn listener(metadata: TokenStream, item: TokenStream) -> TokenStream {
let metadata_error = metadata.clone();
let args: AttributeArgs = parse_macro_input!(metadata);
let args: Args = match Args::from_list(&args) {
Ok(v) => v,
Err(e) => { return e.write_errors().into(); }
};
let mut input: ItemFn = parse_macro_input!(item);
if args.event.is_some() {
compile_error!(TokenStream2::from(metadata_error), "The event type is now automatically inferred for listener functions, please cut down the attribute to `#[listener]`")
}
if input.sig.asyncness.is_none() {
compile_error!(input.sig, "Listener functions must be asynchronous")
} else {
input.sig.asyncness.take();
}
let arguments = utils::read_function_arguments(&input.sig);
if arguments.len() != 2 {
compile_error!(input.sig.inputs, "Listener functions must take 2 arguments: the first argument should be the session and the second the event dispatch object")
}
let (ctx_name, ctx_ty) = &arguments.get(0).unwrap();
let data_name = &arguments.get(1).unwrap().0;
if ctx_ty.to_lowercase().contains("session") {
compile_error!(input.sig.inputs, "&Session argument was replaced by &Context, see README.md for examples")
}
let event = arguments.get(1)
.and_then(|(_, ty)| infer_event_type(ty).map(String::from));
if event.is_none() {
compile_error!(input.sig.inputs, "Could not infer event type: the first argument should be the session and the second the event dispatch object. Make sure you use a correct event dispatch type")
}
if let Some(FnArg::Receiver(rcv)) = input.sig.receiver() {
if rcv.reference.is_none() {
compile_error!(rcv, "Listener methods must take self by reference")
}
adapt_method(&input, rcv, (ctx_name, data_name), event.unwrap())
} else {
adapt_function(&input, (ctx_name, data_name), event.unwrap())
}
}