osui_element/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4    parse::Parser, parse_macro_input, DeriveInput, FnArg, GenericParam, ItemFn, Lifetime,
5    LifetimeParam, PatIdent, PatType,
6};
7
8#[proc_macro_attribute]
9pub fn element(_args: TokenStream, input: TokenStream) -> TokenStream {
10    let mut ast = parse_macro_input!(input as DeriveInput);
11    let struct_name = &ast.ident;
12
13    // Check if the struct has generics
14    let has_generics = !ast.generics.params.is_empty();
15
16    // If there are no generics, add a lifetime (if necessary)
17    if ast.generics.lifetimes().count() == 0 && !has_generics {
18        ast.generics
19            .params
20            .push(syn::GenericParam::Lifetime(LifetimeParam::new(
21                Lifetime::new("'a", proc_macro2::Span::call_site()),
22            )));
23    }
24
25    // Modify the struct fields to include additional fields
26    match &mut ast.data {
27        syn::Data::Struct(ref mut struct_data) => {
28            if let syn::Fields::Named(fields) = &mut struct_data.fields {
29                fields.named.push(
30                    syn::Field::parse_named
31                        .parse2(quote! { pub children: Children })
32                        .unwrap(),
33                );
34                fields.named.push(
35                    syn::Field::parse_named
36                        .parse2(quote! { pub style: Style })
37                        .unwrap(),
38                );
39                fields.named.push(
40                    syn::Field::parse_named
41                        .parse2(quote! { pub id: &'a str })
42                        .unwrap(),
43                );
44                fields.named.push(
45                    syn::Field::parse_named
46                        .parse2(quote! { pub class: &'a str })
47                        .unwrap(),
48                );
49            }
50        }
51        _ => panic!("`element` can only be used with structs"),
52    }
53
54    // Generate the impl block with generics if needed
55    let impl_block = quote! {
56        impl<'a> ElementCore for #struct_name<'a> {
57            fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element> {
58                if let Children::Children(children, _) = &mut self.children {
59                    for elem in children {
60                        if elem.get_id() == id {
61                            return Some(elem);
62                        } else if let Some(e) = elem.get_element_by_id(id) {
63                            return Some(e);
64                        }
65                    }
66                }
67                None
68            }
69
70            fn get_id(&self) -> String {
71                self.id.to_string()
72            }
73
74            fn get_class(&self) -> String {
75                self.class.to_string()
76            }
77
78            fn get_style(&self) -> &Style {
79                &self.style
80            }
81        }
82    };
83
84    let expanded = quote! {
85        #ast
86        #impl_block
87    };
88
89    expanded.into()
90}
91
92#[proc_macro_attribute]
93pub fn component(_: TokenStream, input: TokenStream) -> TokenStream {
94    let input_fn = parse_macro_input!(input as ItemFn);
95
96    let fn_name = input_fn.sig.ident.clone();
97    let code = input_fn.block;
98    let visibility = input_fn.vis;
99    let return_type = match input_fn.sig.output {
100        syn::ReturnType::Default => syn::parse_quote! { Element },
101        syn::ReturnType::Type(_, t) => t,
102    };
103
104    let lifetimes: Vec<_> = input_fn
105        .sig
106        .generics
107        .params
108        .iter()
109        .filter_map(|param| {
110            if let GenericParam::Lifetime(lifetime) = param {
111                Some(&lifetime.lifetime)
112            } else {
113                None
114            }
115        })
116        .collect();
117
118    let mut struct_fields = Vec::new();
119
120    for arg in input_fn.sig.inputs.iter() {
121        if let FnArg::Typed(PatType { pat, ty, .. }) = arg {
122            if let syn::Pat::Ident(PatIdent { ident, .. }) = &**pat {
123                let field_name = ident.clone();
124                let field_type = ty.clone();
125                struct_fields.push(quote! { pub #field_name: #field_type });
126            }
127        }
128    }
129
130    let expanded = quote! {
131        #[derive(Debug, Default)]
132        #[allow(non_camel_case_types)]
133        #visibility struct #fn_name<#(#lifetimes),*> {
134            #(#struct_fields),*
135        }
136
137        impl <#(#lifetimes),*> Component for #fn_name <#(#lifetimes),*> {
138            type Element = #return_type;
139            fn create_element(self) -> Self::Element {
140                #code
141            }
142        }
143    };
144
145    TokenStream::from(expanded)
146}