use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use syn::{Ident, parse_macro_input}; use crate::form::{ParsedChild, ParsedInput}; pub fn form_impl(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ParsedInput); let node_refs = input.children.iter().map(node_ref); let signals = input.children.iter().map(signal); let type_verifications = input.children.iter().map(type_verification); let output_struct_definition = output_struct_definition(&input.children); let output_struct_construction = output_struct_construction(&input.children); let fetch_values = input.children.iter().map(fetch_value); let bounds = input.on_submit.inputs; let on_submit_block = input.on_submit.block; let input_htmls = input.children.iter().map(input_html); let output = quote!({ use crate::components::{ input_placeholder::InputPlaceholder, select_placeholder::SelectPlaceholder }; use leptos::{ view, prelude::{ Get, NodeRef, ElementChild, ClassAttribute, OnAttribute, signal, Set, Show, }, html::{Input, Select}, web_sys::SubmitEvent }; use log::{info, error}; #( #node_refs )* #( #signals )* #( #type_verifications )* let on_submit = move |ev: SubmitEvent| { #output_struct_definition // stop the page from reloading! ev.prevent_default(); #( #fetch_values )* let real_on_submit = |Inputs {#(#bounds),*}| #on_submit_block; real_on_submit( #output_struct_construction ) }; view! {
} }); // { // match syn::parse_file(quote! {fn main() { #output }}.to_string().as_str()) { // Ok(tree) => { // let formatted = prettyplease::unparse(&tree); // eprint!("{}", formatted); // } // Err(err) => { // eprintln!("Error: {err}\n{output}"); // } // }; // } output.into() } fn node_ref_name(name: &Ident) -> Ident { format_ident!("{name}_node_ref") } fn node_ref(child: &ParsedChild) -> TokenStream2 { let (name, ty) = match child { ParsedChild::Input { name, .. } => (name, quote! {Input}), ParsedChild::Select { name, .. } => (name, quote! {Select}), }; let node_ref_name = node_ref_name(name); quote! { let #node_ref_name: NodeRef<#ty> = NodeRef::new(); } } fn signal_names(name: &Ident) -> (Ident, Ident) { (format_ident!("{name}_get"), format_ident!("{name}_set")) } fn signal(child: &ParsedChild) -> TokenStream2 { match child { ParsedChild::Input { name, .. } => { let (signal_name_get, signal_name_set) = signal_names(name); quote! { let (#signal_name_get, #signal_name_set) = signal(None); } } ParsedChild::Select { .. } => quote! {}, } } fn type_verification(child: &ParsedChild) -> TokenStream2 { match child { ParsedChild::Input { .. } => quote! {}, ParsedChild::Select { rust_type, options, .. } => { let names: Vec<_> = options .0 .iter() .enumerate() .map(|(index, _)| format_ident!("name_{index}")) .collect(); let values: Vec<_> = options.0.iter().map(|option| option.1.clone()).collect(); quote! { { #( let #names: #rust_type = #values; )* } } } } } fn output_struct_definition(children: &[ParsedChild]) -> TokenStream2 { let names = children.iter().map(|child| match child { ParsedChild::Input { name, .. } => name, ParsedChild::Select { name, .. } => name, }); let rust_types = children.iter().map(|child| match child { ParsedChild::Input { rust_type, .. } => rust_type, ParsedChild::Select { rust_type, .. } => rust_type, }); quote! { struct Inputs { #( #names: #rust_types ),* } } } fn output_struct_construction(children: &[ParsedChild]) -> TokenStream2 { let names = children.iter().map(|child| match child { ParsedChild::Input { name, .. } => name, ParsedChild::Select { name, .. } => name, }); quote! { Inputs { #( #names ),* } } } fn fetch_value(child: &ParsedChild) -> TokenStream2 { match child { ParsedChild::Input { name, rust_type, .. } => { let node_ref_name = node_ref_name(name); let (_, signal_name_set) = signal_names(name); quote! { let #name: #rust_type = { let value = { let output = #node_ref_name .get() // event handlers can only fire after the view // is mounted to the DOM, so the `NodeRef` will be `Some` .expect(" to exist") .value(); let fin: Result<#rust_type, leptos::error::Error> = output .parse() .map_err(Into::{move || format!( "Input is invalid for type {}: {}", stringify!(#rust_type), #signal_name_get.get().expect("Was `is_some`") ) }