lan-party-backend/macros/src/lib.rs

173 lines
5.6 KiB
Rust

mod edit;
use convert_case::{Case, Casing};
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::Fields;
#[proc_macro_derive(WebEdit)]
pub fn web_edit(tokens: TokenStream) -> TokenStream {
let input: syn::DeriveInput = syn::parse(tokens).unwrap();
let name = input.ident;
let edit_ident = format_ident!("{}Edit", name);
let editable = quote! {
pub struct #edit_ident;
impl<'a, G: Html> Editable<'a, G> for #name {
type Editor = #edit_ident;
}
};
let derived = match input.data {
syn::Data::Struct(s) => {
let fields = s.fields.iter().map(|f| {
let name = f
.ident
.as_ref()
.expect("each struct field must be named")
.clone();
let name_str = name.to_string();
(name_str, name)
});
let fields_view = fields.clone().map(|(name_str, name)| {
let title = name_str.to_case(Case::Title);
quote! {
p {
label { (#title) }
(#name.edit(cx))
}
}
});
let signals = fields.clone().map(|(_name_str, name)| {
quote! {
let #name = create_signal(cx, state.get_untracked().#name.clone());
}
});
let effect_fields = fields.clone().map(|(_name_str, name)| {
quote! {
#name: #name.get().as_ref().clone()
}
});
quote! {
impl<'a, G: Html> Editor<'a, G, #name> for #edit_ident {
fn edit(cx: Scope<'a>, props: EditProps<'a, #name>) -> View<G> {
let state = props.state;
#(#signals)*
create_effect(cx, || {
state.set(#name {
#(#effect_fields,)*
..Default::default()
});
});
view! { cx,
Block(title=stringify!(#name).to_string()) {
#(#fields_view)*
}
}
}
}
}
}
syn::Data::Enum(e) => {
//dbg!(&e);
let variants = e.variants.iter().map(|v| {
let variant = v.ident.clone();
let inner = match &v.fields {
Fields::Unnamed(u) => {
u.unnamed.first().expect("the should be a field").ty.clone()
}
_ => unimplemented!(),
};
let variant_lower = format_ident!("{}", variant.to_string().to_case(Case::Snake));
(variant_lower, variant, inner)
});
let first = variants.clone().next().unwrap().0;
let options = variants.clone().map(|(lower, variant, _inner)| {
let selected = first == lower;
quote! { option(value={stringify!(#lower)}, selected=#selected) { (stringify!(#variant)) } }
});
let view_match = variants.clone().map(|(lower, _variant, _inner)| {
let lower_str = format!("{}", lower);
quote! {
#lower_str => #lower.edit(cx)
}
});
let signals = variants.clone().map(|(lower, variant, inner)| {
quote! {
let #lower = if let #name::#variant(v) = state.get_untracked().as_ref().clone() {
create_signal(cx, v.clone())
} else {
create_signal(cx, <#inner>::default())
};
}
});
let effect_match = variants.clone().map(|(lower, variant, _inner)| {
let lower_str = format!("{}", lower);
quote! {
#lower_str => state.set(#name::#variant(#lower.get().as_ref().clone()))
}
});
quote! {
impl<'a, G: Html> Editor<'a, G, #name> for #edit_ident {
fn edit(cx: Scope<'a>, props: EditProps<'a, #name>) -> View<G> {
let state = props.state;
let selected = create_signal(cx, String::from("0"));
#(#signals)*
create_effect(cx, || {
match selected.get().as_str() {
#(#effect_match,)*
_ => {}
//_ => unreachable!()
}
});
view! { cx,
Block(title=stringify!(#name).to_string()) {
select(bind:value=selected) {
#(#options)*
}
(match selected.get().as_str() {
#(#view_match,)*
_ => view! { cx, }
//_ => unreachable!()
})
}
}
}
}
}
}
_ => unimplemented!(),
};
println!("{}", &derived.to_string());
TokenStream::from(quote! {
#derived
#editable
})
}