Skip to main content

espforge_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{DeriveInput, LitStr, parse_macro_input};
4
5#[proc_macro_derive(ComponentPlugin, attributes(plugin))]
6pub fn derive_component_plugin(input: TokenStream) -> TokenStream {
7    derive_plugin_internal(
8        input,
9        quote!(::espforge_configuration::plugin::PluginKind::Component),
10    )
11}
12
13#[proc_macro_derive(DevicePlugin, attributes(plugin))]
14pub fn derive_device_plugin(input: TokenStream) -> TokenStream {
15    derive_plugin_internal(
16        input,
17        quote!(::espforge_configuration::plugin::PluginKind::Device),
18    )
19}
20
21fn derive_plugin_internal(input: TokenStream, kind_path: proc_macro2::TokenStream) -> TokenStream {
22    let input = parse_macro_input!(input as DeriveInput);
23    let name = &input.ident;
24    let mut required_features = quote!(vec![]);
25
26    // Extract name from #[plugin(name = "...")]
27    let plugin_name = input
28        .attrs
29        .iter()
30        .filter(|a| a.path().is_ident("plugin"))
31        .find_map(|a| {
32            let mut name_val = None;
33            let _ = a.parse_nested_meta(|meta| {
34                if meta.path.is_ident("name") {
35                    let value = meta.value()?;
36                    let lit: LitStr = value.parse()?;
37                    name_val = Some(lit.value());
38                }
39
40                if meta.path.is_ident("features") {
41                    let value = meta.value()?;
42                    let lit: LitStr = value.parse()?;
43                    let feats: Vec<String> = lit
44                        .value()
45                        .split(',')
46                        .map(|s| s.trim().to_string())
47                        .filter(|s| !s.is_empty())
48                        .collect();
49                    required_features = quote!(vec![#(#feats.to_string()),*]);
50                }
51
52                Ok(())
53            });
54            name_val
55        })
56        .unwrap_or_else(|| name.to_string().trim_end_matches("Plugin").to_string());
57
58    let expanded = quote! {
59        impl ::espforge_configuration::plugin::Plugin for #name {
60            fn name(&self) -> &'static str {
61                #plugin_name
62            }
63
64            fn kind(&self) -> ::espforge_configuration::plugin::PluginKind {
65                #kind_path
66            }
67
68            fn validate(&self, properties: &::serde_yaml_ng::Value) -> ::anyhow::Result<()> {
69                self.validate_properties(properties)
70            }
71
72            fn dependencies(&self, properties: &::serde_yaml_ng::Value)
73                -> ::anyhow::Result<::std::vec::Vec<::espforge_configuration::plugin::Dependency>> {
74                self.resolve_dependencies(properties)
75            }
76
77            fn generate(&self, ctx: &::espforge_configuration::plugin::GenerationContext)
78                -> ::anyhow::Result<::espforge_configuration::plugin::GeneratedCode> {
79                self.generate_code(ctx)
80            }
81
82            fn required_features(&self) -> Vec<String> { #required_features }
83        }
84
85        ::inventory::submit! {
86            ::espforge_configuration::plugin::PluginRegistration(&#name)
87        }
88    };
89
90    TokenStream::from(expanded)
91}