use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields, Type, parse_macro_input};
pub fn derive_props_impl(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let generics = &input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let fields = match &input.data {
Data::Struct(data_struct) => match &data_struct.fields {
Fields::Named(fields_named) => &fields_named.named,
_ => {
return syn::Error::new_spanned(
&input,
"Props can only be derived for structs with named fields",
)
.to_compile_error()
.into();
}
},
_ => {
return syn::Error::new_spanned(&input, "Props can only be derived for structs")
.to_compile_error()
.into();
}
};
let has_children_field = fields
.iter()
.any(|field| field.ident.as_ref().map(|n| n.to_string()) == Some("children".to_string()));
let builder_methods = fields.iter().map(|field| {
let field_name = &field.ident;
let field_type = &field.ty;
if is_option_callback_type(field_type) {
quote! {
pub fn #field_name<T>(mut self, value: T) -> Self
where
T: reratui::hooks::callback::IntoCallbackProp<#field_type>,
{
self.#field_name = value.into_callback_prop();
self
}
}
} else {
quote! {
pub fn #field_name<T: Into<#field_type>>(mut self, value: T) -> Self {
self.#field_name = value.into();
self
}
}
}
});
let with_children_method = if has_children_field {
quote! {
pub fn with_children(mut self, children: Vec<Element>) -> Self {
self.children = children;
self
}
}
} else {
quote! {
pub fn with_children(self, _children: Vec<Element>) -> Self {
self
}
}
};
let default_fields = fields.iter().map(|field| {
let field_name = &field.ident;
let field_type = &field.ty;
let is_children =
field_name.as_ref().map(|n| n.to_string()) == Some("children".to_string());
if is_children {
quote! {
#field_name: Vec::new()
}
} else {
if is_option_type(field_type) {
quote! {
#field_name: None
}
} else {
quote! {
#field_name: Default::default()
}
}
}
});
let component_props_impl = if has_children_field {
quote! {
impl #impl_generics ComponentProps for #name #ty_generics #where_clause {
fn get_children(&self) -> Vec<Element> {
self.children.clone()
}
fn set_children(&mut self, children: Vec<Element>) {
self.children = children;
}
}
}
} else {
quote! {
impl #impl_generics ComponentProps for #name #ty_generics #where_clause {
fn get_children(&self) -> Vec<Element> {
Vec::new()
}
fn set_children(&mut self, _children: Vec<Element>) {
}
}
}
};
let clone_fields = fields.iter().map(|field| {
let field_name = &field.ident;
quote! {
#field_name: self.#field_name.clone()
}
});
let expanded = quote! {
impl #impl_generics Clone for #name #ty_generics #where_clause {
fn clone(&self) -> Self {
Self {
#(#clone_fields,)*
}
}
}
impl #impl_generics Default for #name #ty_generics #where_clause {
fn default() -> Self {
Self {
#(#default_fields,)*
}
}
}
impl #impl_generics #name #ty_generics #where_clause {
#(#builder_methods)*
#with_children_method
}
#component_props_impl
};
expanded.into()
}
fn is_option_type(ty: &Type) -> bool {
if let Type::Path(type_path) = ty
&& let Some(segment) = type_path.path.segments.last()
{
return segment.ident == "Option";
}
false
}
fn is_option_callback_type(ty: &Type) -> bool {
if let Type::Path(type_path) = ty
&& let Some(segment) = type_path.path.segments.last()
&& segment.ident == "Option"
{
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments
&& let Some(syn::GenericArgument::Type(Type::Path(inner_path))) = args.args.first()
&& let Some(inner_segment) = inner_path.path.segments.last()
{
return inner_segment.ident == "Callback";
}
}
false
}