dragonfly_plugin_macro/
lib.rs

1mod plugin;
2
3use heck::ToPascalCase;
4use proc_macro::TokenStream;
5use quote::{format_ident, quote};
6use syn::{parse_macro_input, Attribute, DeriveInput, ImplItem, ItemImpl};
7
8use crate::plugin::generate_plugin_impl;
9
10#[proc_macro_derive(Plugin, attributes(plugin, events))]
11pub fn handler_derive(input: TokenStream) -> TokenStream {
12    let ast = parse_macro_input!(input as DeriveInput);
13
14    let derive_name = &ast.ident;
15
16    let info_attr = match find_attribute(
17        &ast,
18        "plugin",
19        "Missing `#[plugin(...)]` attribute with metadata.",
20    ) {
21        Ok(attr) => attr,
22        Err(e) => return e.to_compile_error().into(),
23    };
24
25    let plugin_impl = generate_plugin_impl(info_attr, derive_name);
26
27    quote! {
28        #plugin_impl
29    }
30    .into()
31}
32
33fn find_attribute<'a>(
34    ast: &'a syn::DeriveInput,
35    name: &str,
36    error: &str,
37) -> Result<&'a Attribute, syn::Error> {
38    ast.attrs
39        .iter()
40        .find(|a| a.path().is_ident(name))
41        .ok_or_else(|| syn::Error::new(ast.ident.span(), error))
42}
43
44#[proc_macro_attribute]
45pub fn event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
46    let item_clone = item.clone();
47
48    // Try to parse the input tokens as an `impl` block
49    let impl_block = match syn::parse::<ItemImpl>(item) {
50        Ok(block) => block,
51        Err(_) => {
52            // Parse failed, which means the user is probably in the
53            // middle of typing. Return the original, un-parsed tokens
54            // to keep the LSP alive
55            return item_clone;
56        }
57    };
58
59    // ensure its our EventHandler.
60    let is_event_handler_impl = if let Some((_, trait_path, _)) = &impl_block.trait_ {
61        trait_path
62            .segments
63            .last()
64            .is_some_and(|segment| segment.ident == "EventHandler")
65    } else {
66        return item_clone;
67    };
68
69    if !is_event_handler_impl {
70        // This is an `impl` for some *other* trait.
71        // We shouldn't touch it. Return the original tokens.
72        return item_clone;
73    }
74
75    let mut event_variants = Vec::new();
76    for item in &impl_block.items {
77        if let ImplItem::Fn(method) = item {
78            let fn_name = method.sig.ident.to_string();
79
80            if let Some(event_name_snake) = fn_name.strip_prefix("on_") {
81                let event_name_pascal = event_name_snake.to_pascal_case();
82
83                let variant_ident = format_ident!("{}", event_name_pascal);
84                event_variants.push(quote! { types::EventType::#variant_ident });
85            }
86        }
87    }
88
89    let self_ty = &impl_block.self_ty;
90
91    let subscriptions_impl = quote! {
92        impl dragonfly_plugin::EventSubscriptions for #self_ty {
93            fn get_subscriptions(&self) -> Vec<types::EventType> {
94                vec![
95                    #( #event_variants ),*
96                ]
97            }
98        }
99    };
100
101    let original_impl_tokens = quote! { #impl_block };
102
103    let final_output = quote! {
104        #original_impl_tokens
105        #subscriptions_impl
106    };
107
108    final_output.into()
109}