use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse::Parser, parse_macro_input, DeriveInput, Fields, GenericParam, Lifetime, LifetimeParam,
};
#[proc_macro_attribute]
pub fn element(_args: TokenStream, input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let struct_name = &ast.ident;
let has_generics = !ast.generics.params.is_empty();
if ast.generics.lifetimes().count() == 0 && !has_generics {
ast.generics
.params
.push(GenericParam::Lifetime(LifetimeParam::new(Lifetime::new(
"'a",
proc_macro2::Span::call_site(),
))));
}
match &mut ast.data {
syn::Data::Struct(ref mut struct_data) => {
if let Fields::Named(fields) = &mut struct_data.fields {
fields.named.push(
syn::Field::parse_named
.parse2(quote! { pub children: Children })
.unwrap(),
);
fields.named.push(
syn::Field::parse_named
.parse2(quote! { pub style: Style })
.unwrap(),
);
fields.named.push(
syn::Field::parse_named
.parse2(quote! { pub id: &'a str })
.unwrap(),
);
fields.named.push(
syn::Field::parse_named
.parse2(quote! { pub class: &'a str })
.unwrap(),
);
}
}
_ => panic!("`element` can only be used with structs"),
}
let impl_block = if has_generics {
quote! {
impl<'a, T> ElementCore for #struct_name<'a, T> {
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element> {
if let Children::Children(children, _) = &mut self.children {
for elem in children {
if elem.get_id() == id {
return Some(elem);
} else if let Some(e) = elem.get_element_by_id(id) {
return Some(e);
}
}
}
None
}
fn set_styling(&mut self, styling: &std::collections::HashMap<StyleName, Style>) {
if let Some(style) = styling.get(&StyleName::Class(self.class.to_string())) {
self.style = style.clone();
} else if let Some(style) = styling.get(&StyleName::Id(self.id.to_string())) {
self.style = style.clone();
} else if let Some(style) =
styling.get(&StyleName::Component(stringify!(#struct_name).to_string()))
{
self.style = style.clone();
}
if let Children::Children(children, _) = &mut self.children {
for child in children {
child.set_styling(styling);
}
}
}
fn get_id(&self) -> String {
self.id.to_string()
}
}
}
} else {
quote! {
impl<'a> ElementCore for #struct_name<'a> {
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element> {
if let Children::Children(children, _) = &mut self.children {
for elem in children {
if elem.get_id() == id {
return Some(elem);
} else if let Some(e) = elem.get_element_by_id(id) {
return Some(e);
}
}
}
None
}
fn set_styling(&mut self, styling: &std::collections::HashMap<StyleName, Style>) {
if let Some(style) = styling.get(&StyleName::Class(self.class.to_string())) {
self.style = style.clone();
} else if let Some(style) = styling.get(&StyleName::Id(self.id.to_string())) {
self.style = style.clone();
} else if let Some(style) =
styling.get(&StyleName::Component(stringify!(#struct_name).to_string()))
{
self.style = style.clone();
}
if let Children::Children(children, _) = &mut self.children {
for child in children {
child.set_styling(styling);
}
}
}
fn get_id(&self) -> String {
self.id.to_string()
}
}
}
};
let expanded = quote! {
#ast
#impl_block
};
expanded.into()
}
#[proc_macro_attribute]
pub fn elem_fn(_args: TokenStream, input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let struct_name = &ast.ident;
let func_name = syn::Ident::new(
&struct_name.to_string().to_lowercase(),
proc_macro2::Span::call_site(),
);
let elem_fn = if ast.generics.params.len() == 1 {
quote! {
pub fn #func_name<'a>() -> Box<#struct_name<'a>> {
Box::new(#struct_name::default())
}
}
} else {
quote! {
pub fn #func_name<'a, T>() -> Box<#struct_name<'a, T>> {
Box::new(#struct_name::default())
}
}
};
let expanded = quote! {
#ast
#elem_fn
};
expanded.into()
}