use self::{events::EventsMeta, fields::ComponentField, props::PropsMeta, state::StateMeta};
use crate::suffix::STATUS_SUFFIX;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::mem;
use syn::{
parse::{Error, Result as ParseResult},
Attribute, Ident, ItemStruct, Visibility,
};
mod events;
mod fields;
mod props;
mod state;
pub struct ComponentMeta {
attrs: Vec<Attribute>,
vis: Visibility,
ident: Ident,
props_meta: PropsMeta,
state_meta: StateMeta,
events_meta: EventsMeta,
}
impl ComponentMeta {
pub fn parse(mut item: ItemStruct) -> ParseResult<ComponentMeta> {
Self::filter_out_component_attribute(&mut item);
if item.generics != Default::default() {
return Err(Error::new(
item.ident.span(),
"Generic parameters not allowed on a component.",
));
}
let (props_meta, state_meta) = ComponentField::parse_into_prop_and_state_meta(&mut item)?;
let events_meta = EventsMeta::parse(&mut item)?;
Ok(ComponentMeta {
attrs: item.attrs,
vis: item.vis,
ident: item.ident,
props_meta,
state_meta,
events_meta,
})
}
fn filter_out_component_attribute(item: &mut ItemStruct) {
let mut attrs = vec![];
mem::swap(&mut attrs, &mut item.attrs);
let component = Ident::new("component", Span::call_site()).into();
let attrs: Vec<_> = attrs
.into_iter()
.filter(|attr| attr.path != component)
.collect();
item.attrs = attrs;
}
pub fn expand(&self) -> TokenStream {
let component_struct = self.create_component_struct();
let component_impl = self.impl_component_trait_on_component_struct();
let set_state_impl = self.impl_set_state_trait_on_component_struct();
let state_setter_impl = self.impl_state_setter_trait_on_component_struct();
let state_struct = self.state_meta.create_state_struct();
let props_struct = self.props_meta.create_props_struct_and_macro();
let events_structs = self
.events_meta
.create_events_and_event_props_struct_and_macro();
let status_wrapper_struct = self.create_status_wrapper_struct();
quote! {
#component_struct
#state_struct
#props_struct
#events_structs
#component_impl
#set_state_impl
#status_wrapper_struct
#state_setter_impl
}
}
fn create_component_struct(&self) -> TokenStream {
let attrs = &self.attrs;
let ident = &self.ident;
let vis = &self.vis;
if self.props_meta.fields.is_empty()
&& self.state_meta.fields.is_empty()
&& self.events_meta.events.is_empty()
{
quote! {
#(#attrs)*
#vis struct #ident;
}
} else {
let state_fields = self.state_meta.to_struct_fields();
let props_fields = self.props_meta.to_struct_fields();
let status_field = self.create_status_field();
let events_field = self.create_events_field();
quote! {
#(#attrs)*
#vis struct #ident {
#(#state_fields ,)*
#(#props_fields ,)*
#status_field
#events_field
}
}
}
}
fn create_status_field(&self) -> Option<TokenStream> {
if self.props_meta.fields.is_empty() && self.state_meta.fields.is_empty() {
None
} else {
let status_ty = self.get_status_type();
Some(quote! {
__status__: #status_ty,
})
}
}
fn create_events_field(&self) -> Option<TokenStream> {
if self.events_meta.events.is_empty() {
None
} else {
let ident = &self.events_meta.ident;
Some(quote! {
__events__: #ident,
})
}
}
fn get_props_type(&self) -> TokenStream {
if self.props_meta.fields.is_empty() {
quote!(())
} else {
let ident = &self.props_meta.ident;
quote!(#ident)
}
}
fn get_state_type(&self) -> TokenStream {
if self.state_meta.fields.is_empty() {
quote!(())
} else {
let ident = &self.state_meta.ident;
quote!(#ident)
}
}
fn get_events_type(&self) -> TokenStream {
if self.events_meta.events.is_empty() {
quote!(())
} else {
let ident = &self.events_meta.ident;
quote!(#ident)
}
}
fn get_status_type(&self) -> Ident {
Ident::new(
&format!("{}{}", self.ident, STATUS_SUFFIX),
Span::call_site(),
)
}
fn create_status_wrapper_struct(&self) -> Option<TokenStream> {
if self.props_meta.fields.is_empty() && self.state_meta.fields.is_empty() {
None
} else {
let ident = self.get_status_type();
let state_ty = self.get_state_type();
let status_set_state = self.impl_set_state_trait_for_status_wrapper();
Some(quote! {
#[derive(Clone)]
struct #ident(std::rc::Rc<std::cell::RefCell<ruukh::component::Status<#state_ty>>>);
#status_set_state
})
}
}
fn impl_set_state_trait_for_status_wrapper(&self) -> Option<TokenStream> {
if self.state_meta.fields.is_empty() {
None
} else {
let ident = self.get_status_type();
let state_ty = self.get_state_type();
Some(quote! {
impl SetState for #ident {
type State = #state_ty;
fn set_state(&self, mut mutator: impl FnMut(&mut Self::State)) {
let mut status = self.0.borrow_mut();
mutator(status.state_as_mut());
status.set_state_dirty(true);
status.do_react();
}
}
})
}
}
fn impl_component_trait_on_component_struct(&self) -> TokenStream {
let ident = &self.ident;
let props_type = &self.get_props_type();
let state_type = &self.get_state_type();
let events_type = &self.get_events_type();
let init_body = self.impl_fn_init_body();
let update_body = self.impl_fn_update_body();
let refresh_state_body = self.impl_fn_refresh_state_body();
let status_body = self.impl_fn_status_body();
quote! {
impl Component for #ident {
type Props = #props_type;
type State = #state_type;
type Events = #events_type;
fn init(
__props__: Self::Props,
__events__: Self::Events,
__status__: ruukh::component::Status<Self::State>,
) -> Self {
#init_body
}
fn update(
&mut self,
mut __props__: Self::Props,
__events__: Self::Events,
) -> Option<Self::Props> {
#update_body
}
fn refresh_state(&mut self) -> bool {
#refresh_state_body
}
fn status(&self) -> Option<&std::rc::Rc<
std::cell::RefCell<
ruukh::component::Status<
Self::State>>>>
{
#status_body
}
}
}
}
fn impl_set_state_trait_on_component_struct(&self) -> Option<TokenStream> {
if self.state_meta.fields.is_empty() {
None
} else {
let ident = &self.ident;
let state_ident = &self.state_meta.ident;
let set_state_body = self.impl_fn_set_state_body();
Some(quote! {
impl SetState for #ident {
type State = #state_ident;
fn set_state(&self, mut mutator: impl FnMut(&mut Self::State)) {
#set_state_body
}
}
})
}
}
fn impl_state_setter_trait_on_component_struct(&self) -> Option<TokenStream> {
if self.state_meta.fields.is_empty() {
None
} else {
let ident = &self.ident;
let status_ty = self.get_status_type();
Some(quote! {
impl StateSetter for #ident {
type Setter = #status_ty;
fn state_setter(&self) -> Self::Setter {
self.__status__.clone()
}
}
})
}
}
fn impl_fn_set_state_body(&self) -> Option<TokenStream> {
if self.state_meta.fields.is_empty() {
None
} else {
let idents = &self.state_meta.to_field_idents();
let idents2 = idents;
Some(quote! {
let mut status = self.__status__.0.borrow_mut();
mutator(status.state_as_mut());
let changed = {
let state = status.state_as_ref();
#(
self.#idents != state.#idents2
) ||*
};
if changed {
status.set_state_dirty(true);
status.do_react();
}
})
}
}
fn impl_fn_init_body(&self) -> TokenStream {
let state_clone = if self.state_meta.fields.is_empty() {
None
} else {
let state_field_idents = &self.state_meta.to_field_idents();
let expanded = if state_field_idents.len() == 1 {
quote! {
let #(#state_field_idents)* = {
let state = __status__.state_as_ref();
#(state.#state_field_idents.clone())*
};
}
} else {
quote! {
let (#(#state_field_idents),*) = {
let state = __status__.state_as_ref();
(#(state.#state_field_idents.clone()),*)
};
}
};
Some(expanded)
};
let event_assignment = if self.events_meta.events.is_empty() {
None
} else {
Some(quote! {
__events__,
})
};
let status_assignment =
if self.props_meta.fields.is_empty() && self.state_meta.fields.is_empty() {
None
} else {
let status_ty = self.get_status_type();
Some(quote! {
__status__: #status_ty(
std::rc::Rc::new(
std::cell::RefCell::new(__status__))),
})
};
let ident = &self.ident;
let state_field_idents = &self.state_meta.to_field_idents();
let props_field_idents = &self.props_meta.to_field_idents();
let props_field_idents2 = props_field_idents;
quote! {
#state_clone
#ident {
#(#props_field_idents: __props__.#props_field_idents2 ,)*
#(#state_field_idents ,)*
#event_assignment
#status_assignment
}
}
}
fn impl_fn_status_body(&self) -> TokenStream {
if self.props_meta.fields.is_empty() && self.state_meta.fields.is_empty() {
quote!(None)
} else {
quote! {
Some(&self.__status__.0)
}
}
}
fn impl_fn_refresh_state_body(&self) -> TokenStream {
if self.state_meta.fields.is_empty() {
quote! {
false
}
} else {
let idents = &self.state_meta.to_field_idents();
let idents2 = idents;
let idents3 = idents;
let idents4 = idents;
quote! {
let status = self.__status__.0.borrow();
let state = status.state_as_ref();
let mut changed = false;
#(
if self.#idents != state.#idents2 {
self.#idents3 = state.#idents4.clone();
if !changed {
changed = true;
}
}
)*
changed
}
}
}
fn impl_fn_update_body(&self) -> TokenStream {
let events_assignment = if self.events_meta.events.is_empty() {
None
} else {
Some(quote! {
self.__events__ = __events__;
})
};
if self.props_meta.fields.is_empty() {
quote! {
#events_assignment
None
}
} else {
let idents = &self.props_meta.to_field_idents();
let idents2 = idents;
quote! {
#events_assignment
use std::mem;
#(
mem::swap(&mut self.#idents, &mut __props__.#idents2);
)*
if #(self.#idents != __props__.#idents2) || * {
self.__status__.0.borrow_mut().set_props_dirty(true);
Some(__props__)
} else {
None
}
}
}
}
}