templr_macros/
lib.rs

1use std::{
2    borrow::Cow,
3    fmt::{self, Write},
4    marker::PhantomData,
5    ops,
6};
7
8use askama_escape::Escaper;
9use parser::Element;
10use proc_macro2::{Delimiter, Span, TokenStream, TokenTree};
11use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
12use syn::{parse_quote, punctuated::Punctuated, Ident, Token};
13use templr_parser::{self as parser, Node, TemplBody};
14
15const SELF_CLOSING: &[&str] = &[
16    "arena", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source",
17    "track", "wbr", // html
18];
19
20#[rustfmt::skip]
21fn can_attrs_break(attrs: &[syn::Attribute]) -> bool {
22    !attrs.iter().all(|attr| {
23        attr.path().get_ident().is_some_and(|ident| {
24            [
25                // Conditional compilation
26                "cfg", "cfg_attr",
27                // Testing
28                "test", "ignore", "should_panic",
29                // Derive
30                "derive", "automatically_derived",
31                // Macros
32                "macro_export", "macro_use", "proc_macro", "proc_macro_derive",
33                "proc_macro_attribute",
34                // Diagnostics
35                "allow", "warn", "deny", "forbid", "deprecated", "must_use",
36                // ABI, linking, symbols, and FFI
37                "link", "link_name", "link_ordinal", "no_link", "repr", "crate_type",
38                "no_main", "export_name", "link_section", "no_mangle", "used", "crate_name",
39                // Code generation
40                "inline", "cold", "no_builtins", "target_feature", "track_caller",
41                "instruction_set",
42                // Documentation
43                "doc",
44                // Preludes
45                "no_std", "no_implicit_prelude",
46                // Modules
47                "path",
48                // Limits
49                "recursion_limit", "type_length_limit",
50                // Runtime
51                "panic_handler", "global_allocator", "windows_subsystem",
52                // Features
53                "feature",
54                // Type System
55                "non_exhaustive",
56                // Debugger
57                "debugger_visualizer",
58            ]
59            .iter()
60            .any(|s| ident == s)
61        })
62    })
63}
64
65fn can_macro_break(mac: &syn::Macro) -> bool {
66    !mac.path.get_ident().is_some_and(|ident| {
67        // returns true if can't break;
68
69        if ["cfg", "stringify", "concat", "include_str", "include_bytes"]
70            .iter()
71            .any(|s| ident == s)
72        {
73            true
74        } else if [
75            "todo",
76            "unreachable",
77            "unimplemented",
78            "panic",
79            "assert",
80            "assert_eq",
81            "assert_ne",
82            "debug_assert",
83            "debug_assert_eq",
84            "debug_assert_ne",
85            "dbg",
86            "print",
87            "println",
88            "write",
89            "writeln",
90            "format",
91            "format_args",
92        ]
93        .iter()
94        .any(|s| ident == s)
95        {
96            mac.parse_body_with(Punctuated::<syn::Expr, Token![,]>::parse_terminated)
97                .ok()
98                .map_or(false, |exprs| !exprs.iter().any(can_expr_break))
99        } else {
100            false
101        }
102    })
103}
104
105fn can_expr_break(expr: &syn::Expr) -> bool {
106    match expr {
107        syn::Expr::Array(expr) => {
108            can_attrs_break(&expr.attrs) || expr.elems.iter().any(can_expr_break)
109        }
110        syn::Expr::Assign(expr) => {
111            can_attrs_break(&expr.attrs)
112                || can_expr_break(&expr.left)
113                || can_expr_break(&expr.right)
114        }
115        syn::Expr::Async(_) => false,
116        syn::Expr::Await(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.base),
117        syn::Expr::Binary(expr) => {
118            can_attrs_break(&expr.attrs)
119                || can_expr_break(&expr.left)
120                || can_expr_break(&expr.right)
121        }
122        syn::Expr::Block(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
123        syn::Expr::Break(expr) => {
124            can_attrs_break(&expr.attrs)
125                || expr.label.is_none()
126                || expr.expr.as_ref().is_some_and(|expr| can_expr_break(expr))
127        }
128        syn::Expr::Call(expr) => {
129            can_attrs_break(&expr.attrs)
130                || can_expr_break(&expr.func)
131                || expr.args.iter().any(can_expr_break)
132        }
133        syn::Expr::Cast(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
134        syn::Expr::Closure(expr) => can_attrs_break(&expr.attrs),
135        syn::Expr::Const(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
136        syn::Expr::Continue(expr) => can_attrs_break(&expr.attrs) || expr.label.is_none(),
137        syn::Expr::Field(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.base),
138        syn::Expr::ForLoop(expr) => can_attrs_break(&expr.attrs),
139        syn::Expr::Group(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
140        syn::Expr::If(expr) => {
141            can_attrs_break(&expr.attrs)
142                || can_expr_break(&expr.cond)
143                || can_block_break(&expr.then_branch)
144                || expr
145                    .else_branch
146                    .as_ref()
147                    .is_some_and(|(_, expr)| can_expr_break(expr))
148        }
149        syn::Expr::Index(expr) => {
150            can_attrs_break(&expr.attrs)
151                || can_expr_break(&expr.expr)
152                || can_expr_break(&expr.index)
153        }
154        syn::Expr::Infer(_) => false,
155        syn::Expr::Let(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
156        syn::Expr::Lit(expr) => can_attrs_break(&expr.attrs),
157        syn::Expr::Loop(expr) => can_attrs_break(&expr.attrs),
158        syn::Expr::Macro(syn::ExprMacro { attrs, mac }) => {
159            can_attrs_break(attrs) || can_macro_break(mac)
160        }
161        syn::Expr::Match(expr) => {
162            can_attrs_break(&expr.attrs)
163                || can_expr_break(&expr.expr)
164                || expr.arms.iter().any(|arm| {
165                    !arm.attrs.is_empty()
166                        || arm
167                            .guard
168                            .as_ref()
169                            .is_some_and(|(_, expr)| can_expr_break(expr))
170                        || can_expr_break(&expr.expr)
171                })
172        }
173        syn::Expr::MethodCall(expr) => {
174            can_attrs_break(&expr.attrs)
175                || can_expr_break(&expr.receiver)
176                || expr.args.iter().any(can_expr_break)
177        }
178        syn::Expr::Paren(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
179        syn::Expr::Path(expr) => can_attrs_break(&expr.attrs),
180        syn::Expr::Range(expr) => {
181            can_attrs_break(&expr.attrs)
182                || expr.start.as_ref().is_some_and(|expr| can_expr_break(expr))
183                || expr.end.as_ref().is_some_and(|expr| can_expr_break(expr))
184        }
185        syn::Expr::Reference(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
186        syn::Expr::Repeat(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
187        syn::Expr::Return(expr) => {
188            can_attrs_break(&expr.attrs)
189                || expr.expr.as_ref().is_some_and(|expr| can_expr_break(expr))
190        }
191        syn::Expr::Struct(expr) => {
192            can_attrs_break(&expr.attrs)
193                || expr.fields.iter().any(|expr| can_expr_break(&expr.expr))
194        }
195        syn::Expr::Try(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
196        syn::Expr::TryBlock(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
197        syn::Expr::Tuple(expr) => {
198            can_attrs_break(&expr.attrs) || expr.elems.iter().any(can_expr_break)
199        }
200        syn::Expr::Unary(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
201        syn::Expr::Unsafe(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
202        syn::Expr::Verbatim(_) => true,
203        syn::Expr::While(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.cond),
204        syn::Expr::Yield(expr) => {
205            can_attrs_break(&expr.attrs)
206                || expr.expr.as_ref().is_some_and(|expr| can_expr_break(expr))
207        }
208        _ => true,
209    }
210}
211
212fn can_block_break(block: &syn::Block) -> bool {
213    block.stmts.iter().any(|stmt| match stmt {
214        syn::Stmt::Item(_) => false,
215        syn::Stmt::Local(syn::Local { attrs, init, .. }) => {
216            can_attrs_break(attrs)
217                || init
218                    .as_ref()
219                    .is_some_and(|syn::LocalInit { expr, diverge, .. }| {
220                        can_expr_break(expr)
221                            || diverge
222                                .as_ref()
223                                .is_some_and(|(_, expr)| can_expr_break(expr))
224                    })
225        }
226        syn::Stmt::Macro(syn::StmtMacro { attrs, mac, .. }) => {
227            can_attrs_break(attrs) || can_macro_break(mac)
228        }
229        syn::Stmt::Expr(expr, _) => can_expr_break(expr),
230    })
231}
232
233fn try_stringify_expr(expr: &syn::Expr, can_break: bool) -> Option<(Span, String)> {
234    if can_break && can_expr_break(expr) {
235        return None;
236    }
237    match expr {
238        syn::Expr::Lit(syn::ExprLit { attrs, lit }) if attrs.is_empty() => match lit {
239            syn::Lit::Str(s) => Some((s.span(), s.value())),
240            syn::Lit::Byte(byte) => Some((byte.span(), byte.value().to_string())),
241            syn::Lit::Char(ch) => Some((ch.span(), ch.value().to_string())),
242            syn::Lit::Int(int) => Some((int.span(), int.to_string())),
243            syn::Lit::Float(float) => Some((float.span(), float.to_string())),
244            syn::Lit::Bool(bool) => Some((bool.span(), bool.value().to_string())),
245            _ => None,
246        },
247        syn::Expr::Paren(syn::ExprParen { expr, attrs, .. }) if attrs.is_empty() => {
248            try_stringify_expr(expr, false)
249        }
250        syn::Expr::Block(syn::ExprBlock {
251            block,
252            attrs,
253            label: None,
254            ..
255        }) if attrs.is_empty() => try_stringify_block(block, false),
256        _ => None,
257    }
258}
259
260fn try_stringify_block(block: &syn::Block, can_break: bool) -> Option<(Span, String)> {
261    if can_break && can_block_break(block) {
262        return None;
263    }
264    let Some(syn::Stmt::Expr(expr, None)) = block.stmts.iter().rfind(|stmt| {
265        matches!(
266            stmt,
267            syn::Stmt::Expr(_, _) | syn::Stmt::Macro(_) | syn::Stmt::Local(_),
268        )
269    }) else {
270        return None;
271    };
272    try_stringify_expr(expr, false)
273}
274
275fn writer() -> Ident {
276    Ident::new("__templr_writer", Span::mixed_site())
277}
278
279fn context() -> Ident {
280    Ident::new("__templr_ctx", Span::mixed_site())
281}
282
283fn crate_path(span: Span) -> syn::Path {
284    syn::parse_quote_spanned!(span => ::templr)
285}
286
287/// Try to isolate `tokens` from `break`.
288fn isolate_block(tokens: impl ToTokens) -> TokenStream {
289    let mut iter = tokens.into_token_stream().into_iter();
290    let Some(first) = iter.next() else {
291        if cfg!(debug_assertions) {
292            eprintln!("something weird happened")
293        }
294        return quote! { () };
295    };
296
297    let first_span = first.span();
298    let group = match (first, iter.next()) {
299        (TokenTree::Group(first), None) if first.delimiter() == Delimiter::Brace => {
300            first.into_token_stream()
301        }
302        (first, second) => quote_spanned! { first_span =>  { #first #second #(#iter)* } },
303    };
304
305    quote_spanned! { first_span =>
306        loop {
307            #[allow(unreachable_code)]
308            break {
309                #[warn(unreachable_code)]
310                #group
311            };
312        }
313    }
314}
315
316const EST_EXPR_SIZE: usize = 20;
317
318// TODO: Rename when less tired
319fn call_on_block(
320    tokens: &mut TokenStream,
321    block: &syn::Block,
322    f: impl FnOnce(&mut TokenStream, TokenStream),
323) {
324    match block.stmts.iter().rposition(|stmt| {
325        matches!(
326            stmt,
327            syn::Stmt::Expr(_, _) | syn::Stmt::Macro(_) | syn::Stmt::Local(_),
328        )
329    }) {
330        Some(i)
331            if matches!(
332                block.stmts[i],
333                syn::Stmt::Expr(_, None)
334                    | syn::Stmt::Macro(syn::StmtMacro {
335                        semi_token: None,
336                        ..
337                    })
338            ) && !can_block_break(block) =>
339        {
340            let (before, after) = block.stmts.split_at(i);
341            let (stmt, after) = after.split_first().unwrap();
342
343            let mut inner_tokens = TokenStream::new();
344            inner_tokens.append_all(before);
345            f(
346                &mut inner_tokens,
347                match stmt {
348                    syn::Stmt::Expr(expr, None) => expr.to_token_stream(),
349                    syn::Stmt::Macro(
350                        mac @ syn::StmtMacro {
351                            semi_token: None, ..
352                        },
353                    ) => mac.to_token_stream(),
354                    _ => unreachable!(),
355                },
356            );
357            inner_tokens.append_all(after);
358            surround_with_block(tokens, block.brace_token.span.join(), inner_tokens, false);
359        }
360        _ => f(tokens, isolate_block(block)),
361    }
362}
363
364fn call_on_maybe_block(
365    tokens: &mut TokenStream,
366    block: &parser::Block,
367    f: impl FnOnce(&mut TokenStream, TokenStream),
368) {
369    match block {
370        parser::Block::Valid(block) => call_on_block(tokens, block, f),
371        parser::Block::Invalid { body, .. } => f(tokens, isolate_block(body)),
372    }
373}
374
375fn surround_with_block(
376    tokens: &mut TokenStream,
377    span: Span,
378    inner_tokens: TokenStream,
379    require_brace: bool,
380) {
381    let mut inner_tokens = inner_tokens.into_iter().peekable();
382    match inner_tokens.next() {
383        Some(TokenTree::Group(first))
384            if first.delimiter() == Delimiter::Brace && inner_tokens.peek().is_none() =>
385        {
386            let inner = first.stream();
387            tokens.append_all(quote_spanned!(span => { #inner }));
388        }
389        Some(first) => tokens.append_all(quote_spanned!(span => {
390            #first
391            #(#inner_tokens)*
392        })),
393        None if require_brace => tokens.append_all(quote_spanned!(span => {})),
394        None => {}
395    }
396}
397
398enum BufEntry {
399    Token(TokenTree),
400    LitStr(syn::LitStr),
401    Str(Cow<'static, str>),
402}
403
404impl BufEntry {
405    fn lit_str(span: Span, value: &str) -> Self {
406        Self::LitStr(syn::LitStr::new(value, span))
407    }
408
409    fn str(value: impl Into<Cow<'static, str>>) -> Self {
410        Self::Str(value.into())
411    }
412
413    fn stringify(&self, span: Span) -> TokenStream {
414        match self {
415            BufEntry::Str(s) => quote_spanned! { span => #s },
416            BufEntry::Token(token) => {
417                quote_spanned! { span => stringify!(#token) }
418            }
419            BufEntry::LitStr(lit) => lit.to_token_stream(),
420        }
421    }
422}
423
424struct Buf(Vec<BufEntry>);
425
426impl Buf {
427    fn push_str(&mut self, s: impl Into<Cow<'static, str>>) {
428        self.push(BufEntry::str(s));
429    }
430    fn push_lit_str(&mut self, span: Span, value: &str) {
431        self.push(BufEntry::lit_str(span, value));
432    }
433    fn extend_tokens(&mut self, tokens: impl ToTokens) {
434        for token in tokens.into_token_stream() {
435            self.push(BufEntry::Token(token));
436        }
437    }
438
439    fn name(&mut self, name: &parser::Name) {
440        match name {
441            parser::Name::Str(s) => self.push(BufEntry::LitStr(s.clone())),
442            parser::Name::Parts(parts) => {
443                for part in parts {
444                    self.extend_tokens(part);
445                }
446            }
447        }
448    }
449}
450
451impl ops::Deref for Buf {
452    type Target = Vec<BufEntry>;
453
454    fn deref(&self) -> &Self::Target {
455        &self.0
456    }
457}
458
459impl ops::DerefMut for Buf {
460    fn deref_mut(&mut self) -> &mut Self::Target {
461        &mut self.0
462    }
463}
464
465struct Generator<'a> {
466    buf: Buf,
467    sizes: Vec<usize>,
468    space: bool,
469    _phantom: PhantomData<&'a TemplBody>,
470}
471
472impl<'a> Generator<'a> {
473    fn new() -> Self {
474        Self {
475            buf: Buf(vec![]),
476            sizes: vec![],
477            space: false,
478            _phantom: PhantomData,
479        }
480    }
481
482    fn write_escaped(&mut self, span: Span, value: impl fmt::Display) {
483        pub struct EscapeWriter<'a, T>(&'a mut T);
484
485        impl<T: Write> Write for EscapeWriter<'_, T> {
486            #[inline]
487            fn write_str(&mut self, s: &str) -> fmt::Result {
488                askama_escape::Html.write_escaped(&mut *self.0, s)
489            }
490        }
491
492        let mut s = String::new();
493        write!(EscapeWriter(&mut s), "{value}").unwrap();
494        self.buf.push_lit_str(span, &s);
495    }
496
497    fn top_size(&mut self) -> &mut usize {
498        self.sizes.last_mut().unwrap()
499    }
500
501    #[inline]
502    fn flush_buffer(&mut self, tokens: &mut TokenStream, span: Span) {
503        *self.top_size() += self.buf.len();
504        let buf = &mut self.buf;
505        if !buf.is_empty() {
506            let writer = writer();
507
508            let entries = buf.drain(..).map(|e| e.stringify(span));
509
510            tokens.append_all(quote_spanned! { span =>
511                ::core::fmt::Write::write_str(#writer, concat!(#(#entries),*))?;
512            });
513        }
514    }
515
516    fn write_maybe_block(&mut self, tokens: &mut TokenStream, block: &parser::Block) {
517        match block {
518            parser::Block::Valid(block) => self.write_block(tokens, block),
519            parser::Block::Invalid { brace, body } => {
520                let block_span = brace.span.join();
521
522                let crate_path = crate_path(block_span);
523                let writer = writer();
524
525                self.flush_buffer(tokens, brace.span.open());
526                *self.top_size() += EST_EXPR_SIZE;
527
528                let body = isolate_block(body);
529                tokens.append_all(quote_spanned! { block_span =>
530                    #crate_path::write_escaped(#writer, &#body)?;
531                });
532            }
533        }
534    }
535
536    fn write_block(&mut self, tokens: &mut TokenStream, block: &syn::Block) {
537        match try_stringify_block(block, true) {
538            Some((span, s)) => {
539                tokens.append_all(quote_spanned! { block.brace_token.span.open() => _ = });
540                match can_block_break(block) {
541                    true => tokens.append_all(isolate_block(block)),
542                    false => {
543                        block.brace_token.surround(tokens, |tokens| {
544                            tokens.append_all(quote_spanned! { block.brace_token.span.open() =>
545                                _ = 0;
546                            });
547                            tokens.append_all(&block.stmts);
548                        });
549                    }
550                }
551                tokens.append_all(quote_spanned! { block.brace_token.span.close() => ; });
552                self.write_escaped(span, &s);
553            }
554            None => {
555                self.flush_buffer(tokens, block.brace_token.span.open());
556                call_on_block(tokens, block, |tokens, expr| {
557                    let block_span = block.brace_token.span.join();
558                    let crate_path = crate_path(block_span);
559                    let writer = writer();
560
561                    *self.top_size() += EST_EXPR_SIZE;
562                    tokens.append_all(quote_spanned! { block_span =>
563                        #crate_path::write_escaped(#writer, &(#expr))?;
564                    });
565                });
566            }
567        }
568    }
569
570    fn write_templ(&mut self, tokens: &mut TokenStream, span: Span, body: &'a TemplBody) {
571        let crate_path = crate_path(Span::mixed_site());
572        let context = context();
573        let writer = writer();
574
575        self.sizes.push(0);
576        let mut inner_tokens = TokenStream::new();
577        for node in &body.nodes {
578            self.write_node(&mut inner_tokens, node);
579        }
580        self.flush_buffer(&mut inner_tokens, span);
581        let size = self.sizes.pop().unwrap();
582
583        let context_ty = body
584            .use_context()
585            .and_then(|u| u.colon_ty.as_ref())
586            .map(|(colon, ty)| quote! { #colon #ty });
587        let context_pat_owned;
588        let context_pat: &dyn ToTokens = match body.use_context() {
589            Some(u) => match &u.as_pat {
590                Some((_, pat)) => pat,
591                None => &u.context,
592            },
593            None => {
594                context_pat_owned = quote_spanned! { span => _ };
595                &context_pat_owned
596            }
597        };
598        let children_owned;
599        let children: &dyn ToTokens = match body.use_children() {
600            Some(u) => match &u.as_pat {
601                Some((_, pat)) => pat,
602                None => &u.children,
603            },
604            None => {
605                children_owned = quote_spanned! { span => _ };
606                &children_owned
607            }
608        };
609        tokens.append_all(quote_spanned! { span =>
610            #crate_path::FnTemplate::new_sized(
611                #size,
612                move |#writer, #context @ #context_pat #context_ty, #children| {
613                    #inner_tokens
614                    #crate_path::Result::Ok(())
615                },
616            )
617        })
618    }
619
620    fn write_match<T>(
621        &mut self,
622        tokens: &mut TokenStream,
623        stmt: &'a parser::Match<T>,
624        mut write_item: impl FnMut(&mut Self, &mut TokenStream, &'a T),
625    ) {
626        let parser::Match {
627            pound,
628            match_token,
629            expr,
630            brace,
631            arms,
632        } = stmt;
633
634        self.flush_buffer(tokens, pound.span);
635
636        let num_arms = arms.len();
637        self.sizes.push(0);
638        let init_space = self.space;
639
640        let arms = arms.iter().map(
641            |parser::match_stmt::Arm {
642                 pat,
643                 guard,
644                 fat_arrow,
645                 brace,
646                 body,
647             }| {
648                let mut inner_tokens = TokenStream::new();
649                self.space = init_space;
650                for item in body {
651                    write_item(self, &mut inner_tokens, item);
652                }
653                self.flush_buffer(&mut inner_tokens, brace.span.close());
654                self.space = true;
655
656                let guard = guard
657                    .as_ref()
658                    .map(|(if_token, cond)| quote! { #if_token #cond });
659
660                let mut tokens = quote! { #pat #guard #fat_arrow };
661                surround_with_block(&mut tokens, brace.span.join(), inner_tokens, true);
662                tokens
663            },
664        );
665
666        tokens.append_all(quote_spanned! { brace.span.join() =>
667            #match_token #expr { #(#arms)* }
668        });
669
670        let avg_size = self.sizes.pop().unwrap() / num_arms;
671        *self.top_size() += avg_size;
672    }
673
674    fn write_if<T>(
675        &mut self,
676        tokens: &mut TokenStream,
677        stmt: &'a parser::If<T>,
678        mut write_item: impl FnMut(&mut Self, &mut TokenStream, &'a T),
679    ) {
680        let parser::If {
681            pound,
682            if_token,
683            cond,
684            brace,
685            body,
686            else_if_branches,
687            else_branch,
688        } = stmt;
689
690        self.flush_buffer(tokens, pound.span);
691
692        let num_branches = 1 + else_if_branches.len() + else_branch.is_some() as usize;
693        self.sizes.push(0);
694
695        let init_space = self.space;
696        let mut write_nodes = |tokens: &mut _, span, body| {
697            self.space = init_space;
698            for item in body {
699                write_item(self, tokens, item);
700            }
701            self.flush_buffer(tokens, span);
702            self.space = true;
703        };
704
705        let mut then_tokens = TokenStream::new();
706        write_nodes(&mut then_tokens, brace.span.open(), body);
707
708        tokens.append_all(quote! { #if_token #cond });
709        surround_with_block(tokens, brace.span.join(), then_tokens, true);
710        for branch in else_if_branches {
711            let parser::if_stmt::ElseIfBranch {
712                else_token,
713                if_token,
714                cond,
715                brace,
716                body,
717            } = branch;
718            let mut then_tokens = TokenStream::new();
719            write_nodes(&mut then_tokens, brace.span.open(), body);
720
721            tokens.append_all(quote! { #else_token #if_token #cond });
722            surround_with_block(tokens, brace.span.join(), then_tokens, true);
723        }
724        if let Some(parser::if_stmt::ElseBranch {
725            else_token,
726            brace,
727            body,
728        }) = else_branch
729        {
730            let mut then_tokens = TokenStream::new();
731            write_nodes(&mut then_tokens, brace.span.open(), body);
732
733            tokens.append_all(quote! { #else_token });
734            surround_with_block(tokens, brace.span.join(), then_tokens, true);
735        }
736
737        let avg_size = self.sizes.pop().unwrap() / num_branches;
738        *self.top_size() += avg_size;
739    }
740
741    fn write_for<T>(
742        &mut self,
743        tokens: &mut TokenStream,
744        stmt: &'a parser::For<T>,
745        mut write_item: impl FnMut(&mut Self, &mut TokenStream, &'a T),
746    ) {
747        let parser::For {
748            pound,
749            for_token,
750            pat,
751            in_token,
752            expr,
753            brace,
754            body,
755        } = stmt;
756        self.flush_buffer(tokens, pound.span);
757
758        let mut inner_tokens = TokenStream::new();
759        self.space = true;
760        for item in body {
761            write_item(self, &mut inner_tokens, item);
762        }
763        self.flush_buffer(&mut inner_tokens, brace.span.close());
764
765        tokens.append_all(quote! { #for_token #pat #in_token #expr });
766        surround_with_block(tokens, brace.span.join(), inner_tokens, true);
767    }
768
769    fn write_attr(&mut self, tokens: &mut TokenStream, attr: &'a parser::Attr) {
770        match attr {
771            parser::Attr::Html(parser::attrs::HtmlAttr { name, value }) => match value {
772                parser::attrs::HtmlAttrValue::None => {
773                    self.buf.push_str(" ");
774                    self.buf.name(name);
775                }
776                parser::attrs::HtmlAttrValue::Ident(eq, value) => {
777                    self.buf.push_str(" ");
778                    self.buf.name(name);
779                    self.buf.extend_tokens(quote! { #eq #value });
780                }
781                parser::attrs::HtmlAttrValue::Str(eq, value) => {
782                    self.buf.push_str(" ");
783                    self.buf.name(name);
784                    self.buf.extend_tokens(eq);
785                    self.buf.push_str("\"");
786                    self.write_escaped(value.span(), value.value());
787                    self.buf.push_str("\"");
788                }
789                parser::attrs::HtmlAttrValue::Int(eq, value) => {
790                    self.buf.push_str(" ");
791                    self.buf.name(name);
792                    self.buf.extend_tokens(eq);
793                    self.buf.push_str("\"");
794                    self.write_escaped(value.span(), value);
795                    self.buf.push_str("\"");
796                }
797                parser::attrs::HtmlAttrValue::Float(eq, value) => {
798                    self.buf.push_str(" ");
799                    self.buf.name(name);
800                    self.buf.extend_tokens(eq);
801                    self.buf.push_str("\"");
802                    self.write_escaped(value.span(), value);
803                    self.buf.push_str("\"");
804                }
805                parser::attrs::HtmlAttrValue::Block(toggle, eq, cond) => match toggle {
806                    Some(toggle) => {
807                        self.flush_buffer(tokens, toggle.span);
808
809                        call_on_maybe_block(tokens, cond, |tokens, expr| {
810                            let crate_path = crate_path(toggle.span);
811                            let writer = writer();
812
813                            let mut inner_tokens = TokenStream::new();
814
815                            self.buf.push_str(" ");
816                            self.buf.name(name);
817                            self.flush_buffer(&mut inner_tokens, cond.brace_span().open());
818
819                            *self.top_size() += EST_EXPR_SIZE;
820                            tokens.append_all(quote_spanned! { toggle.span =>
821                                _ = 0;
822                                if let Some(view) = #crate_path::OptAttrValue::to_opt_attr_value(#expr) {
823                                    #inner_tokens
824                                    #crate_path::write_escaped(#writer, &view)?;
825                                }
826                            });
827                        });
828                    }
829                    None => {
830                        self.buf.push_str(" ");
831                        self.buf.name(name);
832                        self.buf.extend_tokens(eq);
833                        self.buf.push_str("\"");
834                        self.write_maybe_block(tokens, cond);
835                        self.buf.push_str("\"");
836                    }
837                },
838            },
839            parser::Attr::If(stmt) => self.write_if(tokens, stmt, Self::write_attr),
840            parser::Attr::Match(stmt) => self.write_match(tokens, stmt, Self::write_attr),
841            parser::Attr::Scope(parser::Scope { brace, body, .. }) => {
842                brace.surround(tokens, |tokens| {
843                    for attr in body {
844                        self.write_attr(tokens, attr);
845                    }
846                })
847            }
848            parser::Attr::Let(stmt) => {
849                stmt.let_token.to_tokens(tokens);
850                stmt.pat.to_tokens(tokens);
851                stmt.init.to_tokens(tokens);
852                stmt.semi.to_tokens(tokens);
853            }
854            parser::Attr::Spread(block) => call_on_maybe_block(tokens, block, |tokens, expr| {
855                self.flush_buffer(tokens, block.brace_span().open());
856
857                let block_span = block.brace_span().join();
858                let crate_path = crate_path(block_span);
859                let writer = writer();
860
861                *self.top_size() += EST_EXPR_SIZE;
862                tokens.append_all(quote_spanned! { block_span =>
863                    #crate_path::Attributes::render_into(#expr, #writer)?;
864                });
865            }),
866        }
867    }
868
869    fn write_element(&mut self, tokens: &mut TokenStream, element: &'a Element) {
870        if self.space {
871            self.buf.push_str(" ");
872        }
873        self.space = false;
874
875        let parser::element::OpenTag {
876            lt,
877            name,
878            attrs,
879            slash,
880            gt,
881        } = &element.open;
882
883        self.buf.extend_tokens(lt);
884        self.buf.name(name);
885
886        let mut inner_tokens = TokenStream::new();
887        for attr in attrs {
888            self.write_attr(&mut inner_tokens, attr);
889        }
890        surround_with_block(tokens, lt.span, inner_tokens, false);
891        self.buf.extend_tokens(gt);
892
893        let mut inner_tokens = TokenStream::new();
894        for node in &element.nodes {
895            self.write_node(&mut inner_tokens, node);
896        }
897        surround_with_block(tokens, gt.span, inner_tokens, false);
898
899        if let Some(slash) = slash {
900            if !SELF_CLOSING.contains(&&*name.to_string()) {
901                self.buf.extend_tokens(quote! { #lt #slash });
902                self.buf.name(name);
903                self.buf.extend_tokens(gt);
904            }
905        } else if let Some(parser::element::CloseTag {
906            lt,
907            slash,
908            name,
909            gt,
910        }) = &element.close
911        {
912            self.buf.extend_tokens(quote! { #lt #slash });
913            self.buf.name(name);
914            self.buf.extend_tokens(gt);
915        }
916        self.space = true;
917    }
918
919    fn write_call(&mut self, tokens: &mut TokenStream, call: &'a parser::Call) {
920        let parser::Call { pound, expr, end } = call;
921
922        self.flush_buffer(tokens, pound.span);
923
924        let expr = match can_expr_break(expr) {
925            true => isolate_block(expr),
926            false => expr.to_token_stream(),
927        };
928
929        let crate_path = crate_path(pound.span);
930        let context = context();
931        let writer = writer();
932
933        match end {
934            parser::call::End::Semi(semi) => {
935                tokens.append_all(quote_spanned! { pound.span =>
936                    #crate_path::Template::render_into(
937                        &(#expr),
938                        #writer,
939                        #context
940                    )? #semi
941                });
942            }
943            parser::call::End::Children(brace, body) => {
944                let mut inner_tokens = TokenStream::new();
945
946                self.write_templ(&mut inner_tokens, brace.span.join(), body);
947
948                tokens.append_all(quote_spanned! { pound.span =>
949                    #crate_path::Template::render_with_children_into(
950                        &(#expr),
951                        #writer,
952                        #context,
953                        &#inner_tokens,
954                    )?;
955                });
956            }
957        }
958    }
959
960    fn write_node(&mut self, tokens: &mut TokenStream, node: &'a Node) {
961        match node {
962            Node::Entity(entity) => self.buf.extend_tokens(entity),
963            Node::Doctype(parser::Doctype {
964                lt,
965                bang,
966                doctype,
967                name,
968                gt,
969            }) => {
970                self.buf.extend_tokens(quote! { #lt #bang #doctype });
971                self.buf.push_str(" ");
972                self.buf.name(name);
973                self.buf.extend_tokens(gt);
974            }
975            Node::Element(element) => self.write_element(tokens, element),
976            Node::RawText(text) => {
977                for token in text.tokens.clone() {
978                    match token {
979                        TokenTree::Punct(punct)
980                            if matches!(
981                                punct.as_char(),
982                                '.' | ',' | ':' | ';' | '!' | '?' | '%'
983                            ) =>
984                        {
985                            self.space = true;
986                            self.buf.extend_tokens(punct)
987                        }
988                        TokenTree::Punct(punct)
989                            if matches!(punct.as_char(), '$' | '#' | '¿' | '¡') =>
990                        {
991                            if self.space {
992                                self.space = false;
993                                self.buf.push_str(" ");
994                            }
995                            self.buf.extend_tokens(punct)
996                        }
997                        _ => {
998                            if self.space {
999                                self.buf.push_str(" ");
1000                            }
1001                            self.space = true;
1002                            self.write_escaped(token.span(), token);
1003                        }
1004                    }
1005                }
1006            }
1007            Node::Paren(paren, nodes) => {
1008                if self.space {
1009                    self.buf.push_str(" ");
1010                }
1011                self.buf.push_lit_str(paren.span.open(), "(");
1012                self.space = false;
1013                for node in nodes {
1014                    self.write_node(tokens, node);
1015                }
1016                self.buf.push_lit_str(paren.span.close(), ")");
1017            }
1018            Node::Bracket(bracket, nodes) => {
1019                if self.space {
1020                    self.buf.push_str(" ");
1021                }
1022                self.buf.push_lit_str(bracket.span.open(), "[");
1023                self.space = false;
1024                for node in nodes {
1025                    self.write_node(tokens, node);
1026                }
1027                self.buf.push_lit_str(bracket.span.close(), "]");
1028            }
1029            Node::Expr(block) => {
1030                if self.space {
1031                    self.buf.push_str(" ");
1032                }
1033                self.space = true;
1034                self.write_maybe_block(tokens, block);
1035            }
1036            Node::If(stmt) => self.write_if(tokens, stmt, Self::write_node),
1037            Node::Match(stmt) => self.write_match(tokens, stmt, Self::write_node),
1038            Node::For(stmt) => self.write_for(tokens, stmt, Self::write_node),
1039            Node::With(parser::With {
1040                with,
1041                ty,
1042                eq,
1043                expr,
1044                brace,
1045                nodes,
1046                ..
1047            }) => {
1048                brace.surround(tokens, |tokens| {
1049                    let context = context();
1050                    let ty = ty.as_ref().map(|(col, ty)| quote! { #col #ty });
1051                    tokens.append_all(quote_spanned! { with.span =>
1052                        let #context #ty #eq #expr;
1053                    });
1054                    for node in nodes {
1055                        self.write_node(tokens, node);
1056                    }
1057                });
1058            }
1059            Node::Scope(parser::Scope { brace, body, .. }) => brace.surround(tokens, |tokens| {
1060                for node in body {
1061                    self.write_node(tokens, node);
1062                }
1063            }),
1064            Node::Let(stmt) => {
1065                stmt.let_token.to_tokens(tokens);
1066                stmt.pat.to_tokens(tokens);
1067                stmt.init.to_tokens(tokens);
1068                stmt.semi.to_tokens(tokens);
1069            }
1070            Node::Call(call) => self.write_call(tokens, call),
1071        }
1072    }
1073}
1074
1075/// The [`templ!`] macro compiles down your template to an `FnTemplate` closure.
1076/// It uses HTML like syntax to write templates, except:
1077///
1078/// 1. All tags must close with a slash (e.g. `<img src="..." />`).
1079///    The slash will be removed for self closing tags during compilation,
1080///    or expanded into a closing tag for all other elements (e.g. `<script src="..." />`)
1081/// 2. Element and attribute names aren't as flexible as HTML,
1082///    to use names that aren't supported or to prevent "name merging" you can use strings.
1083///    (e.g. `<"input" "type"="hidden" "value"="..." />`)
1084/// 3. Unquoted attribute names cannot end with a `?`.
1085/// 4. All parenthesis and brackets must have a matching one inside the current element.
1086/// 6. Raw text cannot contain certain characters (`#`, `<`, `&`, etc).
1087///
1088/// ```rust
1089/// # use templr::{templ, Template};
1090/// let t = templ! {
1091///     <div class="hello world" hidden "@click"="doSomething()">
1092///         Lorem ipsum dolor sit amet.
1093///     </div>
1094/// };
1095/// let html = t.render(&()).unwrap();
1096/// assert_eq!(
1097///     html,
1098///     r#"<div class="hello world" hidden @click="doSomething()">Lorem ipsum dolor sit amet.</div>"#,
1099/// );
1100/// ```
1101///
1102/// # Interpolation
1103/// You can interpolate values using curly braces `{...}`. These can be used inside text.
1104/// Interpolating string literals is done at compile time, so feel free to do that when raw text
1105/// isn't sufficient: (e.g. `{"#1 winner"}`).
1106///
1107/// You may also use the question mark operator (`?`) inside the templates as long as they're
1108/// compatible with [`anyhow`].
1109///
1110/// ```rust
1111/// # use templr::{templ, Template};
1112/// let name = "Zecver";
1113/// let t = templ! {
1114///     Hello, {name}!
1115/// };
1116/// let html = t.render(&()).unwrap();
1117/// assert_eq!(html, format!("Hello, {name}!"));
1118/// ```
1119///
1120/// # Attributes
1121/// Attributes may have a constant value like normal html, (e.g. `attr="..."`).
1122/// Boolean attributes are likewise supported (e.g. `disabled`).
1123/// You can event have unquoted identifiers and number literals as values (e.g. `value=true`).
1124///
1125/// Other than static attributes you can assign interpolated values using `attr={...}`.
1126/// This will quote and escape the given value, so no worries.
1127/// You can use optional attributes (e.g. `attr?={true}`).
1128///
1129/// Finally, you can omit the attribute name and use the `Attributes` trait instead.
1130///
1131/// ```rust
1132/// # use templr::{templ, Template};
1133/// let name = Some("Zecver");
1134/// let value = "false";
1135/// let t = templ! {
1136///     <span
1137///         hidden
1138///         hello="world"
1139///         name?={name}
1140///         value={value}
1141///         {..[("hey", "no")]}
1142///     >
1143///         Hello
1144///     </span>
1145/// };
1146/// let html = t.render(&()).unwrap();
1147/// assert_eq!(
1148///     html,
1149///     r#"<span hidden hello="world" name="Zecver" value="false" hey="no">Hello</span>"#,
1150/// );
1151/// ```
1152/// # Statements
1153/// templr supports `#if`, `#match`, `#for`, `#let`, and `#{}`. These can be nodes or attributes
1154/// (except `#for`).
1155/// These statements function like their rust counterparts but they must start with a `#`.
1156///
1157/// ```rust
1158/// # use templr::{templ, Template};
1159/// let hide_list = false;
1160/// let list_type = None::<&str>;
1161/// let n = 2;
1162/// let error = Some("oops");
1163/// let t = templ! {
1164///     #match error {
1165///         Some(error) => {
1166///             <div class="err">{error}</div>
1167///         }
1168///         None => {
1169///             {"All's well."}
1170///         }
1171///     }
1172///     <ul
1173///         #if hide_list {
1174///             style="list-style: none; margin-left: 0;"
1175///         } else if let Some(list_type) = list_type {
1176///             style={format_args!("list-style: {list_type};")}
1177///         }
1178///     >
1179///         #{
1180///             #let n = n + 1;
1181///             #let range = 1..=n;
1182///             #for i in range {
1183///                 <li>No {i}</li>
1184///             }
1185///         }
1186///     </ul>
1187///     {n}
1188/// };
1189/// let html = t.render(&()).unwrap();
1190/// assert_eq!(
1191///     html,
1192///     r#"<div class="err">oops</div> <ul> <li>No 1</li> <li>No 2</li> <li>No 3</li></ul> 2"#
1193/// );
1194/// ```
1195///
1196/// # Template Composition
1197/// You can use template composition by putting a `#`, then a template expression,
1198/// and finally a closing semicolon or pass children with curly braces.
1199///
1200/// A template can catch children passed to it using the `#use children as pattern;` command at the top.
1201/// You can omit the `as pattern` part and the pattern will be `children`.
1202///
1203/// ```rust
1204/// # use templr::{templ, Template};
1205/// let hello = templ! {
1206///     #use children;
1207///
1208///     {"Hello, "}
1209///     #children;
1210///     !
1211/// };
1212///
1213/// let t = templ! {
1214///     #hello {
1215///         world
1216///     }
1217/// };
1218/// let html = t.render(&()).unwrap();
1219/// assert_eq!(html, "Hello, world!");
1220/// ```
1221///
1222/// # Context
1223/// Templates pass a context implicitly when instantiating. This is useful to avoid constantly
1224/// passing around arguments. The context defaults to `()`.
1225///
1226/// You can use it using `#use context as pattern: type`. When `as pattern` is omitted, `context`
1227/// is the pattern. While when `: type` is omitted, the type is infered.
1228///
1229/// To use templates with a different context type, you can use the `with` statement to change the
1230/// context for a specific block. You may supply an optional type after the `context` keyword
1231/// (e.g. `with context [: type] = value { ... }`).
1232///
1233/// ```rust
1234/// # use templr::{templ, Template};
1235/// let hello = templ! {
1236///     #use children;
1237///
1238///     {"Hello, "}
1239///     #children;
1240///     !
1241/// };
1242///
1243/// let t = templ! {
1244///     #hello {
1245///         #use context as name;
1246///         {name}
1247///     }
1248/// };
1249/// assert_eq!(t.render("Zaknar").unwrap(), r"Hello, Zaknar!");
1250///
1251/// let name = "Mannier";
1252/// let t2 = templ! {
1253///     #with context = name {
1254///         #t;
1255///     }
1256/// };
1257/// assert_eq!(t2.render(&()).unwrap(), r"Hello, Mannier!");
1258/// ```
1259///
1260/// [`anyhow`]: https://docs.rs/anyhow/
1261#[proc_macro]
1262pub fn templ(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
1263    let body = syn::parse_macro_input!(tokens as TemplBody);
1264
1265    let mut tokens = TokenStream::new();
1266    let mut generator = Generator::new();
1267    generator.write_templ(&mut tokens, Span::call_site(), &body);
1268    tokens.into()
1269}
1270
1271struct AttrsBody(Vec<parser::Attr>);
1272
1273impl syn::parse::Parse for AttrsBody {
1274    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1275        let mut attrs = vec![];
1276        while !input.is_empty() {
1277            attrs.push(input.parse()?);
1278        }
1279        Ok(Self(attrs))
1280    }
1281}
1282
1283/// This renders the attributes into a `PrerenderedAttrs` struct. This can later be used for
1284/// attributes instantiation.
1285///
1286/// ```rust
1287/// # use templr::{templ, Template, attrs};
1288///
1289/// let attrs = &attrs! { style="color: red;" class="hello world" }.unwrap();
1290///
1291/// let t = templ! {
1292///     <ul>
1293///         <li {attrs}>Item 1</li>
1294///         <li {attrs}>Item 2</li>
1295///         <li {attrs}>Item 3</li>
1296///         <li {attrs}>Item 4</li>
1297///     </ul>
1298/// };
1299/// assert_eq!(
1300///     t.render(&()).unwrap(),
1301///     r#"<ul><li style="color: red;" class="hello world">Item 1</li> <li style="color: red;" class="hello world">Item 2</li> <li style="color: red;" class="hello world">Item 3</li> <li style="color: red;" class="hello world">Item 4</li></ul>"#,
1302/// );
1303/// ```
1304#[proc_macro]
1305pub fn attrs(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
1306    let body = syn::parse_macro_input!(tokens as AttrsBody);
1307
1308    let mut inner_tokens = TokenStream::new();
1309    let mut generator = Generator::new();
1310    generator.sizes.push(0);
1311    for attr in &body.0 {
1312        generator.write_attr(&mut inner_tokens, attr);
1313    }
1314    generator.flush_buffer(&mut inner_tokens, Span::call_site());
1315
1316    let writer = writer();
1317    let crate_path = crate_path(Span::call_site());
1318    From::from(quote! {
1319        (move || -> #crate_path::Result<_> {
1320            let mut #writer = ::std::string::String::new();
1321            {
1322                let #writer = &mut #writer;
1323                #inner_tokens
1324            }
1325            #crate_path::Result::Ok(
1326                #crate_path::attrs::PrerenderedAttrs::from_raw_unchecked(#writer),
1327            )
1328        })()
1329    })
1330}
1331
1332/// This derives the `Template` trait using all `ToTemplate` implementations.
1333/// This implementation runs `ToTemplate::to_template` for every method of the `Template` trait,
1334/// so that method should be very lightweight (like using [`templ!`]).
1335///
1336/// ```rust
1337/// # use templr::{templ, ToTemplate, Template};
1338/// #[derive(Template)]
1339/// struct Greet<'a> {
1340///     name: &'a str,
1341/// }
1342///
1343/// impl ToTemplate for Greet<'_> {
1344///     fn to_template(&self) -> impl Template + '_ {
1345///         templ! {
1346///             Hello, {self.name}!
1347///         }
1348///     }
1349/// }
1350///
1351/// let t = templ! {
1352///     #(Greet { name: "baba" });
1353/// };
1354/// let html = t.render(&()).unwrap();
1355/// assert_eq!(html, "Hello, baba!");
1356/// ```
1357#[proc_macro_derive(Template)]
1358pub fn derive_template(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
1359    let input = syn::parse_macro_input!(tokens as syn::DeriveInput);
1360
1361    let name = input.ident;
1362
1363    let crate_path = crate_path(Span::call_site());
1364
1365    let ctx_ty = Ident::new("__TemplrCtx", Span::mixed_site());
1366    let (_, ty_generics, _) = input.generics.split_for_impl();
1367
1368    let mut modified_generics = input.generics.clone();
1369    modified_generics.params.push(parse_quote! {
1370        #ctx_ty: ?Sized
1371    });
1372    modified_generics
1373        .make_where_clause()
1374        .predicates
1375        .push(parse_quote! {
1376            Self: #crate_path::ToTemplate<#ctx_ty>
1377        });
1378
1379    let (impl_generics, _, where_clause) = modified_generics.split_for_impl();
1380
1381    From::from(quote! {
1382        impl #impl_generics #crate_path::Template<#ctx_ty> for #name #ty_generics
1383        #where_clause
1384        {
1385            fn size_hint(&self) -> usize {
1386                #crate_path::ToTemplate::<#ctx_ty>::to_template(self)
1387                    .size_hint()
1388            }
1389            fn render_with_children_into(
1390                &self,
1391                writer: &mut dyn ::std::fmt::Write,
1392                ctx: &#ctx_ty,
1393                children: &dyn #crate_path::Template<#ctx_ty>,
1394            ) -> #crate_path::Result<()> {
1395                #crate_path::ToTemplate::<#ctx_ty>::to_template(self)
1396                    .render_with_children_into(writer, ctx, children)
1397            }
1398            fn render_into(&self, writer: &mut dyn ::std::fmt::Write, ctx: &#ctx_ty) -> #crate_path::Result<()> {
1399                #crate_path::ToTemplate::<#ctx_ty>::to_template(self)
1400                    .render_into(writer, ctx)
1401            }
1402            fn write_into(&self, writer: &mut dyn ::std::io::Write, ctx: &#ctx_ty) -> ::std::io::Result<()> {
1403                #crate_path::ToTemplate::<#ctx_ty>::to_template(self)
1404                    .write_into(writer, ctx)
1405            }
1406            fn render(&self, ctx: &#ctx_ty) -> #crate_path::Result<String> {
1407                #crate_path::ToTemplate::<#ctx_ty>::to_template(self)
1408                    .render(ctx)
1409            }
1410        }
1411    })
1412}