derive_component.rs

 1use convert_case::{Case, Casing};
 2use proc_macro::TokenStream;
 3use quote::quote;
 4use syn::{parse_macro_input, DeriveInput, Lit, Meta, MetaList, MetaNameValue, NestedMeta};
 5
 6pub fn derive_into_component(input: TokenStream) -> TokenStream {
 7    let input = parse_macro_input!(input as DeriveInput);
 8    let mut scope_val = None;
 9    let mut description_val = None;
10
11    for attr in &input.attrs {
12        if attr.path.is_ident("component") {
13            if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() {
14                for item in nested {
15                    if let NestedMeta::Meta(Meta::NameValue(MetaNameValue {
16                        path,
17                        lit: Lit::Str(s),
18                        ..
19                    })) = item
20                    {
21                        let ident = path.get_ident().map(|i| i.to_string()).unwrap_or_default();
22                        if ident == "scope" {
23                            scope_val = Some(s.value());
24                        } else if ident == "description" {
25                            description_val = Some(s.value());
26                        }
27                    }
28                }
29            }
30        }
31    }
32
33    let name = &input.ident;
34
35    let scope_impl = if let Some(s) = scope_val {
36        quote! {
37            fn scope() -> Option<&'static str> {
38                Some(#s)
39            }
40        }
41    } else {
42        quote! {
43            fn scope() -> Option<&'static str> {
44                None
45            }
46        }
47    };
48
49    let description_impl = if let Some(desc) = description_val {
50        quote! {
51            fn description() -> Option<&'static str> {
52                Some(#desc)
53            }
54        }
55    } else {
56        quote! {}
57    };
58
59    let register_component_name = syn::Ident::new(
60        &format!(
61            "__register_component_{}",
62            Casing::to_case(&name.to_string(), Case::Snake)
63        ),
64        name.span(),
65    );
66    let register_preview_name = syn::Ident::new(
67        &format!(
68            "__register_preview_{}",
69            Casing::to_case(&name.to_string(), Case::Snake)
70        ),
71        name.span(),
72    );
73
74    let expanded = quote! {
75        impl component::Component for #name {
76            #scope_impl
77
78            fn name() -> &'static str {
79                stringify!(#name)
80            }
81
82            #description_impl
83        }
84
85        #[linkme::distributed_slice(component::__ALL_COMPONENTS)]
86        fn #register_component_name() {
87            component::register_component::<#name>();
88        }
89
90        #[linkme::distributed_slice(component::__ALL_PREVIEWS)]
91        fn #register_preview_name() {
92            component::register_preview::<#name>();
93        }
94    };
95
96    expanded.into()
97}