east_macro/
lib.rs

1mod node;
2
3use crate::node::{Child, Children};
4use proc_macro2::Span;
5use proc_macro_crate::{crate_name, FoundCrate};
6use quote::quote;
7use syn::{
8    braced,
9    parse::{Parse, ParseStream},
10    parse_macro_input, Ident, ItemImpl, Result, Token,
11};
12
13fn east_crate() -> proc_macro2::TokenStream {
14    let found_crate = crate_name("east").expect("east is present in `Cargo.toml`");
15
16    match found_crate {
17        FoundCrate::Itself => quote!(crate),
18        FoundCrate::Name(name) => {
19            let ident = Ident::new(&name, Span::call_site());
20            quote!( #ident )
21        }
22    }
23}
24
25fn generate_children_to_string(
26    component_type: proc_macro2::TokenStream,
27    children: Vec<Child>,
28) -> proc_macro2::TokenStream {
29    let east_crate = east_crate();
30    let output = Ident::new("__east_output", Span::mixed_site());
31
32    let children = children.into_iter().map(|child| {
33        match child {
34            Child::Element(element) => {
35                if let Some(html_tag) = element.html_tag() {
36                    let attributes = element.attributes().into_iter().map(|attribute| {
37                        let raw_name = format!("{}", attribute.name).replace("_", "-");
38
39                        let name = proc_macro2::Literal::string(&raw_name);
40                        let value = attribute.value;
41                        quote! {
42                            format!("{}=\"{}\"", #name, #east_crate::escape(&#value))
43                        }
44                    });
45
46                    if element.children().is_empty() {
47                        quote! {
48                            #output.push_str("<");
49                            #output.push_str(&[#html_tag, #(&#attributes),*].join(" "));
50                            #output.push_str(">");
51
52                            #output.push_str("</");
53                            #output.push_str(&#html_tag);
54                            #output.push_str(">");
55                        }
56                    } else {
57                        let children = generate_children_to_string(component_type.clone(), element.children());
58
59                        quote! {
60                            #output.push_str("<");
61                            #output.push_str(&[#html_tag, #(&#attributes),*].join(" "));
62                            #output.push_str(">");
63
64                            #output.push_str(&#children.0);
65
66                            #output.push_str("</");
67                            #output.push_str(&#html_tag);
68                            #output.push_str(">");
69                        }
70                    }
71                } else {
72                    let tag = element.tag.clone();
73                    let attributes = element.attributes().into_iter().map(|attribute| {
74                        let name = attribute.name;
75                        let value = attribute.value;
76                        quote! { #name: #value }
77                    });
78
79                    if element.children().is_empty() {
80                        quote! {
81                            #output.push_str(&#east_crate::Render::<#component_type>::render(#tag {
82                                #(#attributes),*
83                            }).0);
84                        }
85                    } else {
86                        let children = generate_children_to_string(component_type.clone(), element.children());
87
88                        quote! {
89                            #output.push_str(&#east_crate::RenderMulti::<#component_type>::render_multi(#tag {
90                                #(#attributes),*
91                            }, #children).0);
92                        }
93                    }
94                }
95            },
96            Child::Expr(expr) => {
97                quote! { #output.push_str(&#east_crate::Render::<#component_type>::render(#expr).0); }
98            },
99        }
100    });
101
102    quote! { {
103        let mut #output = String::new();
104
105        #(#children)*
106
107        #east_crate::PreEscaped(#output)
108    } }
109}
110
111fn generate_children_to_view(scope: Ident, children: Vec<Child>) -> proc_macro2::TokenStream {
112    let east_crate = east_crate();
113
114    let children = children.into_iter().map(|child| match child {
115        Child::Element(element) => {
116            if let Some(html_tag) = element.html_tag() {
117                let attributes = element.attributes().into_iter().map(|attribute| {
118                    let raw_name = format!("{}", attribute.name).replace("_", "-");
119
120                    if raw_name.starts_with("on-") {
121                        let mut raw_name = raw_name;
122                        let name = proc_macro2::Literal::string(&raw_name.split_off(3));
123                        let value = attribute.value;
124
125                        quote! {
126                            .on(#name, #value)
127                        }
128                    } else {
129                        let name = proc_macro2::Literal::string(&raw_name);
130                        let value = attribute.value;
131
132                        quote! {
133                            .dyn_attr(#name, { let #scope = #scope.clone(); move || #value })
134                        }
135                    }
136                });
137
138                let children = if element.children().is_empty() {
139                    quote! {}
140                } else {
141                    let children = generate_children_to_view(scope.clone(), element.children());
142                    quote! {
143                        .dyn_c({ let #scope = #scope.clone(); move || #children })
144                    }
145                };
146
147                let html_tag = proc_macro2::Literal::string(&html_tag);
148                quote! {
149                    #east_crate::builder::tag(#html_tag)
150                    #(#attributes)*
151                    #children
152                    .view(#scope)
153                }
154            } else {
155                let tag = element.tag.clone();
156                let attributes = element.attributes().into_iter().map(|attribute| {
157                    let name = attribute.name;
158                    let value = attribute.value;
159                    quote! { #name: #value }
160                });
161
162                if element.children().is_empty() {
163                    quote! {
164                        #east_crate::RenderDyn::render_dyn(#tag {
165                            #(#attributes),*
166                        }, #scope.clone())
167                    }
168                } else {
169                    panic!("Dynamic element tags do not support children.");
170                }
171            }
172        }
173        Child::Expr(expr) => {
174            quote! { #east_crate::RenderDyn::render_dyn(#expr, #scope) }
175        }
176    });
177
178    quote! { {
179        #east_crate::builder::fragment([
180            #(#children),*
181        ])
182    } }
183}
184
185#[derive(Clone, Debug)]
186struct Render {
187    children: Children,
188}
189
190impl Parse for Render {
191    fn parse(input: ParseStream) -> Result<Self> {
192        Ok(Self {
193            children: input.parse()?,
194        })
195    }
196}
197
198/// Render a static markup without any dynamic component.
199#[proc_macro]
200pub fn render(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
201    let east_crate = east_crate();
202    let view = parse_macro_input!(input as Render);
203    let component_type = quote! { #east_crate::NoComponent };
204
205    generate_children_to_string(component_type, view.children.0.into_iter().collect()).into()
206}
207
208#[derive(Clone, Debug)]
209struct RenderWithComponent {
210    component_type: syn::Type,
211    _comma_token: Token![,],
212    _brace_token: syn::token::Brace,
213    children: Children,
214}
215
216impl Parse for RenderWithComponent {
217    fn parse(input: ParseStream) -> Result<Self> {
218        let content;
219        Ok(Self {
220            component_type: input.parse()?,
221            _comma_token: input.parse()?,
222            _brace_token: braced!(content in input),
223            children: content.parse()?,
224        })
225    }
226}
227
228/// Render a static markup with given dynamic component type.
229#[proc_macro]
230pub fn render_with_component(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
231    let view_with_component = parse_macro_input!(input as RenderWithComponent);
232    let component_type = view_with_component.component_type;
233
234    generate_children_to_string(
235        quote! { #component_type },
236        view_with_component.children.0.into_iter().collect(),
237    )
238    .into()
239}
240
241#[derive(Clone, Debug)]
242struct RenderDyn {
243    scope_ident: Ident,
244    _comma_token: Token![,],
245    _brace_token: syn::token::Brace,
246    children: Children,
247}
248
249impl Parse for RenderDyn {
250    fn parse(input: ParseStream) -> Result<Self> {
251        let content;
252        Ok(Self {
253            scope_ident: input.parse()?,
254            _comma_token: input.parse()?,
255            _brace_token: braced!(content in input),
256            children: content.parse()?,
257        })
258    }
259}
260
261/// Render a dynamic component.
262#[proc_macro]
263pub fn render_dyn(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
264    let render_dyn = parse_macro_input!(input as RenderDyn);
265
266    generate_children_to_view(
267        render_dyn.scope_ident,
268        render_dyn.children.0.into_iter().collect(),
269    )
270    .into()
271}
272
273/// Implement `Render` from `RenderMulti`.
274#[proc_macro_attribute]
275pub fn render_from_multi(
276    _args: proc_macro::TokenStream,
277    input: proc_macro::TokenStream,
278) -> proc_macro::TokenStream {
279    let east_crate = east_crate();
280    let input = parse_macro_input!(input as ItemImpl);
281    let original = input.clone();
282
283    let generics = input.generics;
284    let last_trait_seg = input
285        .trait_
286        .expect("trait must exist")
287        .1
288        .segments
289        .last()
290        .expect("component type must exist")
291        .clone();
292    let last_trait_seg_args = last_trait_seg.arguments;
293    assert_eq!(format!("{}", last_trait_seg.ident), "RenderMulti");
294
295    let self_ty = input.self_ty;
296
297    quote! {
298        #original
299
300        impl #generics #east_crate::Render #last_trait_seg_args for #self_ty {
301            fn render(self) -> #east_crate::Markup {
302                #east_crate::RenderMulti::#last_trait_seg_args::render_multi(self, Default::default())
303            }
304        }
305    }.into()
306}
307
308/// Implement `Render` from `RenderDyn`.
309#[proc_macro_attribute]
310pub fn render_from_dyn(
311    _args: proc_macro::TokenStream,
312    input: proc_macro::TokenStream,
313) -> proc_macro::TokenStream {
314    let east_crate = east_crate();
315    let input = parse_macro_input!(input as ItemImpl);
316    let original = input.clone();
317
318    let self_ty = input.self_ty;
319
320    quote! {
321        impl<AnyComponent> #east_crate::Render<AnyComponent> for #self_ty where
322            AnyComponent: #east_crate::serde::Serialize + From<#self_ty>
323        {
324            fn render(self) -> #east_crate::Markup {
325                let any_component = AnyComponent::from(self.clone());
326
327                if let Ok(serialized) = #east_crate::json_to_string(&any_component) {
328                    #east_crate::render_with_component!(AnyComponent, {
329                        div {
330                            data_component: serialized,
331                            #east_crate::PreEscaped(#east_crate::render_to_string(|cx| {
332                                self.render_dyn(cx)
333                            })),
334                        }
335                    })
336                } else {
337                    #east_crate::render_with_component!(AnyComponent, {
338                        div {
339                            #east_crate::PreEscaped(#east_crate::render_to_string(|cx| {
340                                self.render_dyn(cx)
341                            })),
342                        }
343                    })
344                }
345            }
346        }
347
348        #original
349    }
350    .into()
351}