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#[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#[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#[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#[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#[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}