form_yew/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::{format_ident, quote};
4use syn::{parse_macro_input, DataStruct, DeriveInput, Field, FieldsNamed};
5use syn::{Data, Fields};
6mod helpers;
7use helpers::parse_struct_body;
8
9
10
11
12// 11-12-22 where i stopped
13// was trying to make the adverstise form work for checkboxes, but I need the value
14// to be passed as a String and not a boolean
15
16
17/*
18 TODOs:
19 - error handling. If something is done wrong the error handling should
20 actually tell the user, and give a decent error message for a way to fix
21 it.
22 */
23
24#[proc_macro_derive(YewForm, attributes(ele))]
25pub fn yew_form_derive(token_stream: TokenStream) -> TokenStream {
26    /*
27    <input .. /> types this macro doesn't handle
28    - checkbox (would map nice to bool)
29    - file - Custom type I export?
30    - email - Custom type I export?
31    - number
32     */
33
34    let der_input = parse_macro_input!(token_stream as DeriveInput);
35
36    let struct_name = der_input.ident;
37
38    let enum_name = format_ident!("Update{}", struct_name);
39    let provider_name = format_ident!("{}Provider", struct_name);
40    let props_name = format_ident!("{}Props", struct_name);
41    let (messages_enum,
42        struct_field_names,
43        (html_tag_type, cast_type),
44        struct_field_types) = parse_struct_body(der_input.data, &enum_name);
45    // update_#struct_field_names
46    let struct_field_names_for_update = struct_field_names
47        .iter()
48        .map(|v| format_ident!("update_{}", v))
49        .collect::<Vec<proc_macro2::Ident>>();
50    
51    // NOTE: This is only here to get rid of compiler warnings for non-capitalized variant names
52    // also, need to come up with a better way to do this than what I'm doing right now
53    let enum_variants = struct_field_names.iter().map(|v| format_ident!("{}", v.to_string().to_uppercase())).collect::<Vec<proc_macro2::Ident>>();
54
55    let function_component_function_name = struct_field_names.iter().map(|v| format_ident!("{}_fn",v.to_string().to_uppercase())).collect::<Vec<proc_macro2::Ident>>();
56    let function_component_names = struct_field_names.iter().map(|v| {
57        let chars = v.to_string().chars().enumerate().map(|(ind, chr)|{
58            if ind == 0 {
59                chr.to_ascii_uppercase()
60            } else {
61                chr
62            }
63        }).collect::<String>();
64        format_ident!("{}", chars)
65    }).collect::<Vec<proc_macro2::Ident>>();
66    let html_tag_type = html_tag_type.iter().map(|v| format_ident!("{}", v)).collect::<Vec<proc_macro2::Ident>>();
67
68    let update_funcs = struct_field_names.iter().map(|v| format_ident!("update_{}", v)).collect::<Vec<proc_macro2::Ident>>();
69
70    let struct_field_names_as_strings = struct_field_names.iter().map(|v| v.to_string()).collect::<Vec<String>>();
71
72    let function_component_props = function_component_names.iter().map(|v| format_ident!("{}_Props",v)).collect::<Vec<proc_macro2::Ident>>();
73
74    // eprintln!("html tag {:#?} - rust type {:#?}", html_tag_type, cast_type);
75    
76
77    let generated_html = helpers::generate_html_inputs(html_tag_type.clone(), struct_field_names_as_strings.clone(), struct_field_types.clone());
78
79    let generated_callbacks = helpers::update_callbacks(struct_field_types.clone(), enum_variants.clone(), cast_type.clone(), struct_field_names.clone());
80
81    let final_output = quote! {
82
83        /*
84        this generates an enum that looks like approx. like this
85        pub enum Update#NameOfStructThisIsDerivedFrom {
86            struct_field_name_1(struct_field_1_type),
87            struct_field_name_2(struct_field_2_type),
88            ...
89        }
90         */
91        #messages_enum
92
93
94        #[derive(PartialEq, Properties)]
95        pub struct #props_name {
96            pub children: yew::Children
97        }
98
99
100        /*  looks like
101        pub enum Update#original_struct_name {
102            #struct_field_one(#struct_field_type_one),
103            #struct_field_two(#struct_field_type_two),
104            ...
105        }
106
107         */
108        #[derive(Clone, PartialEq, Debug)]
109        pub struct #provider_name {
110            pub form: #struct_name,
111            #( pub #struct_field_names_for_update : yew::Callback<yew::html::onchange::Event>,)*
112        }
113
114        impl Component for #provider_name {
115            type Message = #enum_name;
116            type Properties = #props_name;
117
118            fn create(ctx: &Context<Self>) -> Self {
119
120                // #(
121                //     let #struct_field_names_for_update = |e: yew::events::Event| -> Self::Message {
122                //     let input = e.target_dyn_into::<#cast_type>().unwrap();
123                //     let parse_type: #struct_field_types = input.value().parse().unwrap();
124                //     Self::Message::#enum_variants(parse_type)
125                //     }; 
126                //     let #struct_field_names_for_update = ctx.link().callback(#struct_field_names_for_update);
127                // )*
128
129
130                #(
131                    let #struct_field_names_for_update = #generated_callbacks; 
132                    let #struct_field_names_for_update = ctx.link().callback(#struct_field_names_for_update);
133                )*
134
135
136
137
138                Self {
139                    form: #struct_name {
140                        #( #struct_field_names: Default::default(), )*
141                    },
142                    #( #struct_field_names_for_update, )*
143                }
144            }
145
146            fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
147                match msg {
148                     #(Self::Message::#enum_variants(inside_value) => {
149                         self.form.#struct_field_names = inside_value;
150                     }
151                    ),*
152                }
153                true
154            }
155
156            fn view(&self, ctx: &Context<Self>) -> Html {
157                html! {
158                    <ContextProvider<#provider_name> context={self.clone()}>
159                        {ctx.props().children.clone()}
160                    </ContextProvider<#provider_name>>
161                }
162            }
163        }
164
165
166        #(#[derive(Properties, PartialEq)]
167        pub struct #function_component_props {
168            pub class: std::option::Option<String>
169        }
170        )*
171
172        #(#[function_component(#function_component_names)]
173        pub fn #function_component_function_name(p: &#function_component_props) -> yew::Html {
174
175            let #provider_name {
176                form: #struct_name {
177                    #struct_field_names: struct_field_name,
178                    ..
179                },
180                #update_funcs: update_func,
181                ..
182            } = use_context::<#provider_name>().expect("context for this field");
183
184
185
186            /*
187            - if this thing is a boolean then I need to have a
188            <#html_tag_type type="checkbox" class={&p.class} onchange={update_func} value={struct_field_name} placeholder={#struct_field_names_as_strings} /> 
189             */
190
191             html! {
192                #generated_html
193             }
194            //html! {
195                //// <#html_tag_type class={&p.class} onchange={update_func} value={struct_field_name.to_string()} placeholder={#struct_field_names_as_strings} /> 
196            //
197         })*
198
199    }
200    .into();
201
202    // eprintln!("output tokens: {}",final_output);
203
204    final_output
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn it_works() {
213        assert!(true);
214    }
215}