Skip to main content

standard_plugin_macros/
lib.rs

1//! `#[ui]` and `#[daemon]`: export a plugin type as the `ui-plugin` or
2//! `daemon-plugin` world of `standard:plugin@2.0.0`.
3//!
4//! Use them through `standard-plugin-sdk` as `#[standard_plugin::ui]` and
5//! `#[standard_plugin::daemon]`. Put the attribute on the plugin type's
6//! definition (a struct or enum) or on its `impl UiPlugin for ...` /
7//! `impl DaemonPlugin for ...` block, once per plugin crate. The attribute
8//! leaves the item unchanged and adds the component exports, which the SDK's
9//! runtime forwards to the trait methods.
10
11use proc_macro::TokenStream;
12use quote::quote;
13use syn::spanned::Spanned as _;
14use syn::{Item, Type};
15
16/// Exports the annotated type, which implements
17/// `standard_plugin::UiPlugin`, as the plugin's `ui-plugin` world.
18#[proc_macro_attribute]
19pub fn ui(attr: TokenStream, item: TokenStream) -> TokenStream {
20    export(attr, item, World::Ui)
21}
22
23/// Exports the annotated type, which implements
24/// `standard_plugin::daemon::DaemonPlugin`, as the plugin's
25/// `daemon-plugin` world.
26#[proc_macro_attribute]
27pub fn daemon(attr: TokenStream, item: TokenStream) -> TokenStream {
28    export(attr, item, World::Daemon)
29}
30
31enum World {
32    Ui,
33    Daemon,
34}
35
36fn export(attr: TokenStream, item: TokenStream, world: World) -> TokenStream {
37    if !attr.is_empty() {
38        let attr = proc_macro2::TokenStream::from(attr);
39        return syn::Error::new(attr.span(), "this attribute takes no arguments")
40            .to_compile_error()
41            .into();
42    }
43    let parsed = syn::parse_macro_input!(item as Item);
44    let ty: Type = match &parsed {
45        Item::Struct(item) => {
46            if !item.generics.params.is_empty() {
47                return generic_error(item.generics.span());
48            }
49            let ident = &item.ident;
50            syn::parse_quote!(#ident)
51        }
52        Item::Enum(item) => {
53            if !item.generics.params.is_empty() {
54                return generic_error(item.generics.span());
55            }
56            let ident = &item.ident;
57            syn::parse_quote!(#ident)
58        }
59        Item::Impl(item) if item.trait_.is_some() => {
60            if !item.generics.params.is_empty() {
61                return generic_error(item.generics.span());
62            }
63            (*item.self_ty).clone()
64        }
65        other => {
66            return syn::Error::new(
67                other.span(),
68                "put this attribute on the plugin's struct or enum, or on its plugin trait impl",
69            )
70            .to_compile_error()
71            .into();
72        }
73    };
74    let export = match world {
75        World::Ui => quote!(::standard_plugin::__export_ui!(#ty);),
76        World::Daemon => quote!(::standard_plugin::__export_daemon!(#ty);),
77    };
78    quote!(#parsed #export).into()
79}
80
81fn generic_error(span: proc_macro2::Span) -> TokenStream {
82    syn::Error::new(
83        span,
84        "a plugin type cannot be generic: the component exports one type",
85    )
86    .to_compile_error()
87    .into()
88}