handlebars/
template.rs

1use std::collections::{HashMap, VecDeque};
2use std::iter::Peekable;
3use std::str::FromStr;
4
5use pest::error::LineColLocation;
6use pest::iterators::Pair;
7use pest::{Parser, Position, Span};
8use serde_json::value::Value as Json;
9
10use crate::error::{TemplateError, TemplateErrorReason};
11use crate::grammar::{HandlebarsParser, Rule};
12use crate::json::path::{parse_json_path_from_iter, Path};
13use crate::support;
14
15use derive_builder::Builder;
16
17use self::TemplateElement::{
18    Comment, DecoratorBlock, DecoratorExpression, Expression, HelperBlock, HtmlExpression,
19    PartialBlock, PartialExpression, RawString,
20};
21
22#[non_exhaustive]
23#[derive(PartialEq, Eq, Clone, Debug)]
24pub struct TemplateMapping(pub usize, pub usize);
25
26/// A handlebars template
27#[non_exhaustive]
28#[derive(Builder, PartialEq, Eq, Clone, Debug, Default)]
29pub struct Template {
30    #[builder(setter(into, strip_option), default)]
31    pub name: Option<String>,
32    pub elements: Vec<TemplateElement>,
33    pub mapping: Vec<TemplateMapping>,
34}
35
36#[derive(Default)]
37pub(crate) struct TemplateOptions {
38    pub(crate) prevent_indent: bool,
39    pub(crate) is_partial: bool,
40    pub(crate) name: Option<String>,
41}
42
43impl TemplateOptions {
44    fn name(&self) -> String {
45        self.name.clone().unwrap_or_else(|| "Unnamed".to_owned())
46    }
47}
48
49#[non_exhaustive]
50#[derive(Builder, PartialEq, Eq, Clone, Debug)]
51pub struct Subexpression {
52    // we use box here avoid resursive struct definition
53    pub element: Box<TemplateElement>,
54}
55
56impl Subexpression {
57    pub fn new(
58        name: Parameter,
59        params: Vec<Parameter>,
60        hash: HashMap<String, Parameter>,
61    ) -> Subexpression {
62        Subexpression {
63            element: Box::new(Expression(Box::new(HelperTemplate {
64                name,
65                params,
66                hash,
67                template: None,
68                inverse: None,
69                block_param: None,
70                block: false,
71                chain: false,
72                indent_before_write: false,
73            }))),
74        }
75    }
76
77    pub fn is_helper(&self) -> bool {
78        match *self.as_element() {
79            TemplateElement::Expression(ref ht) => !ht.is_name_only(),
80            _ => false,
81        }
82    }
83
84    pub fn as_element(&self) -> &TemplateElement {
85        self.element.as_ref()
86    }
87
88    pub fn name(&self) -> &str {
89        match *self.as_element() {
90            // FIXME: avoid unwrap here
91            Expression(ref ht) => ht.name.as_name().unwrap(),
92            _ => unreachable!(),
93        }
94    }
95
96    pub fn params(&self) -> Option<&Vec<Parameter>> {
97        match *self.as_element() {
98            Expression(ref ht) => Some(&ht.params),
99            _ => None,
100        }
101    }
102
103    pub fn hash(&self) -> Option<&HashMap<String, Parameter>> {
104        match *self.as_element() {
105            Expression(ref ht) => Some(&ht.hash),
106            _ => None,
107        }
108    }
109}
110
111#[non_exhaustive]
112#[derive(PartialEq, Eq, Clone, Debug)]
113pub enum BlockParam {
114    Single(Parameter),
115    Pair((Parameter, Parameter)),
116}
117
118#[non_exhaustive]
119#[derive(Builder, PartialEq, Eq, Clone, Debug)]
120pub struct ExpressionSpec {
121    pub name: Parameter,
122    pub params: Vec<Parameter>,
123    pub hash: HashMap<String, Parameter>,
124    #[builder(setter(strip_option), default)]
125    pub block_param: Option<BlockParam>,
126    pub omit_pre_ws: bool,
127    pub omit_pro_ws: bool,
128}
129
130#[non_exhaustive]
131#[derive(PartialEq, Eq, Clone, Debug)]
132pub enum Parameter {
133    // for helper name only
134    Name(String),
135    // for expression, helper param and hash
136    Path(Path),
137    Literal(Json),
138    Subexpression(Subexpression),
139}
140
141#[non_exhaustive]
142#[derive(Builder, PartialEq, Eq, Clone, Debug)]
143pub struct HelperTemplate {
144    pub name: Parameter,
145    pub params: Vec<Parameter>,
146    pub hash: HashMap<String, Parameter>,
147    #[builder(setter(strip_option), default)]
148    pub block_param: Option<BlockParam>,
149    #[builder(setter(strip_option), default)]
150    pub template: Option<Template>,
151    #[builder(setter(strip_option), default)]
152    pub inverse: Option<Template>,
153    pub block: bool,
154    pub chain: bool,
155    pub(crate) indent_before_write: bool,
156}
157
158impl HelperTemplate {
159    pub fn new(exp: ExpressionSpec, block: bool, indent_before_write: bool) -> HelperTemplate {
160        HelperTemplate {
161            name: exp.name,
162            params: exp.params,
163            hash: exp.hash,
164            block_param: exp.block_param,
165            block,
166            template: None,
167            inverse: None,
168            chain: false,
169            indent_before_write,
170        }
171    }
172
173    pub fn new_chain(
174        exp: ExpressionSpec,
175        block: bool,
176        indent_before_write: bool,
177    ) -> HelperTemplate {
178        HelperTemplate {
179            name: exp.name,
180            params: exp.params,
181            hash: exp.hash,
182            block_param: exp.block_param,
183            block,
184            template: None,
185            inverse: None,
186            chain: true,
187            indent_before_write,
188        }
189    }
190
191    // test only
192    pub(crate) fn with_path(path: Path) -> HelperTemplate {
193        HelperTemplate {
194            name: Parameter::Path(path),
195            params: Vec::with_capacity(5),
196            hash: HashMap::new(),
197            block_param: None,
198            template: None,
199            inverse: None,
200            block: false,
201            chain: false,
202            indent_before_write: false,
203        }
204    }
205
206    pub(crate) fn is_name_only(&self) -> bool {
207        !self.block && self.params.is_empty() && self.hash.is_empty()
208    }
209
210    fn insert_inverse_node(&mut self, mut node: Box<HelperTemplate>) {
211        // Create a list in "inverse" member to hold the else-chain.
212        // Here we create the new template to save the else-chain node.
213        // The template render could render it successfully without any code add.
214        let mut new_chain_template = Template::new();
215        node.inverse = self.inverse.take();
216        new_chain_template.elements.push(HelperBlock(node));
217        self.inverse = Some(new_chain_template);
218    }
219
220    fn ref_chain_head_mut(&mut self) -> Option<&mut Box<HelperTemplate>> {
221        if self.chain {
222            if let Some(inverse_tmpl) = &mut self.inverse {
223                assert_eq!(inverse_tmpl.elements.len(), 1);
224                if let HelperBlock(helper) = &mut inverse_tmpl.elements[0] {
225                    return Some(helper);
226                }
227            }
228        }
229        None
230    }
231
232    fn set_chain_template(&mut self, tmpl: Option<Template>) {
233        if let Some(hepler) = self.ref_chain_head_mut() {
234            hepler.template = tmpl;
235        } else {
236            self.template = tmpl;
237        }
238    }
239
240    fn revert_chain_and_set(&mut self, inverse: Option<Template>) {
241        if self.chain {
242            let mut prev = None;
243
244            if let Some(head) = self.ref_chain_head_mut() {
245                if head.template.is_some() {
246                    // Here the prev will hold the head inverse template.
247                    // It will be set when reverse the chain.
248                    prev = inverse;
249                } else {
250                    // If the head already has template. set the inverse template.
251                    head.template = inverse;
252                }
253            }
254
255            // Reverse the else chain, to the normal list order.
256            while let Some(mut node) = self.inverse.take() {
257                assert_eq!(node.elements.len(), 1);
258                if let HelperBlock(c) = &mut node.elements[0] {
259                    self.inverse = c.inverse.take();
260                    c.inverse = prev;
261                    prev = Some(node);
262                }
263            }
264
265            self.inverse = prev;
266        } else {
267            // If the helper has no else chain.
268            // set the template to self.
269            if self.template.is_some() {
270                self.inverse = inverse;
271            } else {
272                self.template = inverse;
273            }
274        }
275    }
276
277    fn set_chained(&mut self) {
278        self.chain = true;
279    }
280
281    pub fn is_chained(&self) -> bool {
282        self.chain
283    }
284}
285
286#[non_exhaustive]
287#[derive(Builder, PartialEq, Eq, Clone, Debug)]
288pub struct DecoratorTemplate {
289    pub name: Parameter,
290    pub params: Vec<Parameter>,
291    pub hash: HashMap<String, Parameter>,
292    #[builder(setter(strip_option), default)]
293    pub template: Option<Template>,
294    // for partial indent
295    #[builder(setter(into, strip_option), default)]
296    pub indent: Option<String>,
297    pub(crate) indent_before_write: bool,
298}
299
300impl DecoratorTemplate {
301    pub fn new(exp: ExpressionSpec, indent_before_write: bool) -> DecoratorTemplate {
302        DecoratorTemplate {
303            name: exp.name,
304            params: exp.params,
305            hash: exp.hash,
306            template: None,
307            indent: None,
308            indent_before_write,
309        }
310    }
311}
312
313impl Parameter {
314    pub fn as_name(&self) -> Option<&str> {
315        match self {
316            Parameter::Name(ref n) => Some(n),
317            Parameter::Path(ref p) => Some(p.raw()),
318            _ => None,
319        }
320    }
321
322    pub fn parse(s: &str) -> Result<Parameter, TemplateError> {
323        let parser = HandlebarsParser::parse(Rule::parameter, s)
324            .map_err(|_| TemplateError::of(TemplateErrorReason::InvalidParam(s.to_owned())))?;
325
326        let mut it = parser.flatten().peekable();
327        Template::parse_param(s, &mut it, s.len() - 1)
328    }
329
330    fn debug_name(&self) -> String {
331        if let Some(name) = self.as_name() {
332            name.to_owned()
333        } else {
334            format!("{self:?}")
335        }
336    }
337}
338
339impl Template {
340    pub fn new() -> Template {
341        Template::default()
342    }
343
344    fn push_element(&mut self, e: TemplateElement, line: usize, col: usize) {
345        self.elements.push(e);
346        self.mapping.push(TemplateMapping(line, col));
347    }
348
349    fn parse_subexpression<'a, I>(
350        source: &'a str,
351        it: &mut Peekable<I>,
352        limit: usize,
353    ) -> Result<Parameter, TemplateError>
354    where
355        I: Iterator<Item = Pair<'a, Rule>>,
356    {
357        let espec = Template::parse_expression(source, it.by_ref(), limit)?;
358        Ok(Parameter::Subexpression(Subexpression::new(
359            espec.name,
360            espec.params,
361            espec.hash,
362        )))
363    }
364
365    fn parse_name<'a, I>(
366        source: &'a str,
367        it: &mut Peekable<I>,
368        _: usize,
369    ) -> Result<Parameter, TemplateError>
370    where
371        I: Iterator<Item = Pair<'a, Rule>>,
372    {
373        let name_node = it.next().unwrap();
374        let rule = name_node.as_rule();
375        let name_span = name_node.as_span();
376        match rule {
377            Rule::identifier | Rule::partial_identifier | Rule::invert_tag_item => {
378                Ok(Parameter::Name(name_span.as_str().to_owned()))
379            }
380            Rule::reference => {
381                let paths = parse_json_path_from_iter(it, name_span.end());
382                Ok(Parameter::Path(Path::new(name_span.as_str(), paths)))
383            }
384            Rule::subexpression => {
385                Template::parse_subexpression(source, it.by_ref(), name_span.end())
386            }
387            _ => unreachable!(),
388        }
389    }
390
391    fn parse_param<'a, I>(
392        source: &'a str,
393        it: &mut Peekable<I>,
394        _: usize,
395    ) -> Result<Parameter, TemplateError>
396    where
397        I: Iterator<Item = Pair<'a, Rule>>,
398    {
399        let mut param = it.next().unwrap();
400        if param.as_rule() == Rule::helper_parameter {
401            param = it.next().unwrap();
402        }
403        let param_rule = param.as_rule();
404        let param_span = param.as_span();
405        let result = match param_rule {
406            Rule::reference => {
407                let path_segs = parse_json_path_from_iter(it, param_span.end());
408                Parameter::Path(Path::new(param_span.as_str(), path_segs))
409            }
410            Rule::literal => {
411                // Parse the parameter as a JSON literal
412                let param_literal = it.next().unwrap();
413                let json_result = match param_literal.as_rule() {
414                    Rule::string_literal
415                        if it.peek().unwrap().as_rule() == Rule::string_inner_single_quote =>
416                    {
417                        // ...unless the parameter is a single-quoted string.
418                        // In that case, transform it to a double-quoted string
419                        // and then parse it as a JSON literal.
420                        let string_inner_single_quote = it.next().unwrap();
421                        let double_quoted = format!(
422                            "\"{}\"",
423                            string_inner_single_quote
424                                .as_str()
425                                .replace("\\'", "'")
426                                .replace('"', "\\\"")
427                        );
428                        Json::from_str(&double_quoted)
429                    }
430                    _ => Json::from_str(param_span.as_str()),
431                };
432                if let Ok(json) = json_result {
433                    Parameter::Literal(json)
434                } else {
435                    return Err(TemplateError::of(TemplateErrorReason::InvalidParam(
436                        param_span.as_str().to_owned(),
437                    )));
438                }
439            }
440            Rule::subexpression => {
441                Template::parse_subexpression(source, it.by_ref(), param_span.end())?
442            }
443            _ => unreachable!(),
444        };
445
446        while let Some(n) = it.peek() {
447            let n_span = n.as_span();
448            if n_span.end() > param_span.end() {
449                break;
450            }
451            it.next();
452        }
453
454        Ok(result)
455    }
456
457    fn parse_hash<'a, I>(
458        source: &'a str,
459        it: &mut Peekable<I>,
460        limit: usize,
461    ) -> Result<(String, Parameter), TemplateError>
462    where
463        I: Iterator<Item = Pair<'a, Rule>>,
464    {
465        let name = it.next().unwrap();
466        let name_node = name.as_span();
467        // identifier
468        let key = name_node.as_str().to_owned();
469
470        let value = Template::parse_param(source, it.by_ref(), limit)?;
471        Ok((key, value))
472    }
473
474    fn parse_block_param<'a, I>(_: &'a str, it: &mut Peekable<I>, limit: usize) -> BlockParam
475    where
476        I: Iterator<Item = Pair<'a, Rule>>,
477    {
478        let p1_name = it.next().unwrap();
479        let p1_name_span = p1_name.as_span();
480        // identifier
481        let p1 = p1_name_span.as_str().to_owned();
482
483        let p2 = it.peek().and_then(|p2_name| {
484            let p2_name_span = p2_name.as_span();
485            if p2_name_span.end() <= limit {
486                Some(p2_name_span.as_str().to_owned())
487            } else {
488                None
489            }
490        });
491
492        if let Some(p2) = p2 {
493            it.next();
494            BlockParam::Pair((Parameter::Name(p1), Parameter::Name(p2)))
495        } else {
496            BlockParam::Single(Parameter::Name(p1))
497        }
498    }
499
500    fn parse_expression<'a, I>(
501        source: &'a str,
502        it: &mut Peekable<I>,
503        limit: usize,
504    ) -> Result<ExpressionSpec, TemplateError>
505    where
506        I: Iterator<Item = Pair<'a, Rule>>,
507    {
508        let mut params: Vec<Parameter> = Vec::new();
509        let mut hashes: HashMap<String, Parameter> = HashMap::new();
510        let mut omit_pre_ws = false;
511        let mut omit_pro_ws = false;
512        let mut block_param = None;
513
514        if it.peek().unwrap().as_rule() == Rule::leading_tilde_to_omit_whitespace {
515            omit_pre_ws = true;
516            it.next();
517        }
518
519        let name = Template::parse_name(source, it.by_ref(), limit)?;
520
521        loop {
522            let rule;
523            let end;
524            if let Some(pair) = it.peek() {
525                let pair_span = pair.as_span();
526                if pair_span.end() < limit {
527                    rule = pair.as_rule();
528                    end = pair_span.end();
529                } else {
530                    break;
531                }
532            } else {
533                break;
534            }
535
536            it.next();
537
538            match rule {
539                Rule::helper_parameter => {
540                    params.push(Template::parse_param(source, it.by_ref(), end)?);
541                }
542                Rule::hash => {
543                    let (key, value) = Template::parse_hash(source, it.by_ref(), end)?;
544                    hashes.insert(key, value);
545                }
546                Rule::block_param => {
547                    block_param = Some(Template::parse_block_param(source, it.by_ref(), end));
548                }
549                Rule::trailing_tilde_to_omit_whitespace => {
550                    omit_pro_ws = true;
551                }
552                _ => {}
553            }
554        }
555        Ok(ExpressionSpec {
556            name,
557            params,
558            hash: hashes,
559            block_param,
560            omit_pre_ws,
561            omit_pro_ws,
562        })
563    }
564
565    fn remove_previous_whitespace(template_stack: &mut VecDeque<Template>) {
566        let t = template_stack.front_mut().unwrap();
567        if let Some(RawString(ref mut text)) = t.elements.last_mut() {
568            text.trim_end().to_owned().clone_into(text);
569        }
570    }
571
572    // in handlebars, the whitespaces around statement are
573    // automatically trimed.
574    // this function checks if current span has both leading and
575    // trailing whitespaces, which we treat as a standalone statement.
576    //
577    //
578    fn process_standalone_statement(
579        template_stack: &mut VecDeque<Template>,
580        source: &str,
581        current_span: &Span<'_>,
582        prevent_indent: bool,
583        is_partial: bool,
584    ) -> bool {
585        let continuation = &source[current_span.end()..];
586
587        let mut with_trailing_newline = support::str::starts_with_empty_line(continuation);
588
589        // For full templates, we behave as if there was a trailing newline if we encounter
590        // the end of input. See #611.
591        with_trailing_newline |= !is_partial && continuation.is_empty();
592
593        if with_trailing_newline {
594            let with_leading_newline =
595                support::str::ends_with_empty_line(&source[..current_span.start()]);
596
597            // prevent_indent: a special toggle for partial expression
598            // (>) that leading whitespaces are kept
599            if prevent_indent && with_leading_newline {
600                let t = template_stack.front_mut().unwrap();
601                // check the last element before current
602                if let Some(RawString(ref mut text)) = t.elements.last_mut() {
603                    // trim leading space for standalone statement
604                    text.trim_end_matches(support::str::whitespace_matcher)
605                        .to_owned()
606                        .clone_into(text);
607                }
608            }
609
610            // return true when the item is the first element in root template
611            current_span.start() == 0 || with_leading_newline
612        } else {
613            false
614        }
615    }
616
617    fn raw_string<'a>(
618        source: &'a str,
619        pair: Option<Pair<'a, Rule>>,
620        trim_start: bool,
621        trim_start_line: bool,
622    ) -> TemplateElement {
623        let mut s = String::from(source);
624
625        if let Some(pair) = pair {
626            // the source may contains leading space because of pest's limitation
627            // we calculate none space start here in order to correct the offset
628            let pair_span = pair.as_span();
629
630            let current_start = pair_span.start();
631            let span_length = pair_span.end() - current_start;
632            let leading_space_offset = s.len() - span_length;
633
634            // we would like to iterate pair reversely in order to remove certain
635            // index from our string buffer so here we convert the inner pairs to
636            // a vector.
637            for sub_pair in pair.into_inner().rev() {
638                // remove escaped backslash
639                if sub_pair.as_rule() == Rule::escape {
640                    let escape_span = sub_pair.as_span();
641
642                    let backslash_pos = escape_span.start();
643                    let backslash_rel_pos = leading_space_offset + backslash_pos - current_start;
644                    s.remove(backslash_rel_pos);
645                }
646            }
647        }
648
649        if trim_start {
650            RawString(s.trim_start().to_owned())
651        } else if trim_start_line {
652            let s = s.trim_start_matches(support::str::whitespace_matcher);
653            RawString(support::str::strip_first_newline(s).to_owned())
654        } else {
655            RawString(s)
656        }
657    }
658
659    pub(crate) fn compile2(
660        source: &str,
661        options: TemplateOptions,
662    ) -> Result<Template, TemplateError> {
663        let mut helper_stack: VecDeque<HelperTemplate> = VecDeque::new();
664        let mut decorator_stack: VecDeque<DecoratorTemplate> = VecDeque::new();
665        let mut template_stack: VecDeque<Template> = VecDeque::new();
666
667        let mut omit_pro_ws = false;
668        // flag for newline removal of standalone statements
669        // this option is marked as true when standalone statement is detected
670        // then the leading whitespaces and newline of next rawstring will be trimed
671        let mut trim_line_required = false;
672
673        let parser_queue = HandlebarsParser::parse(Rule::handlebars, source).map_err(|e| {
674            let (line_no, col_no) = match e.line_col {
675                LineColLocation::Pos(line_col) => line_col,
676                LineColLocation::Span(line_col, _) => line_col,
677            };
678            TemplateError::of(TemplateErrorReason::InvalidSyntax(
679                e.variant.message().to_string(),
680            ))
681            .at(source, line_no, col_no)
682            .in_template(options.name())
683        })?;
684
685        // dbg!(parser_queue.clone().flatten());
686
687        // remove escape from our pair queue
688        let mut it = parser_queue
689            .flatten()
690            .filter(|p| {
691                // remove rules that should be silent but not for now due to pest limitation
692                !matches!(p.as_rule(), Rule::escape)
693            })
694            .peekable();
695        let mut end_pos: Option<Position<'_>> = None;
696        loop {
697            if let Some(pair) = it.next() {
698                let prev_end = end_pos.as_ref().map_or(0, pest::Position::pos);
699                let rule = pair.as_rule();
700                let span = pair.as_span();
701
702                let is_trailing_string = rule != Rule::template
703                    && span.start() != prev_end
704                    && !omit_pro_ws
705                    && rule != Rule::raw_text
706                    && rule != Rule::raw_block_text;
707
708                if is_trailing_string {
709                    // trailing string check
710                    let (line_no, col_no) = span.start_pos().line_col();
711                    if rule == Rule::raw_block_end {
712                        let mut t = Template::new();
713                        t.push_element(
714                            Template::raw_string(
715                                &source[prev_end..span.start()],
716                                None,
717                                false,
718                                trim_line_required,
719                            ),
720                            line_no,
721                            col_no,
722                        );
723                        template_stack.push_front(t);
724                    } else {
725                        let t = template_stack.front_mut().unwrap();
726                        t.push_element(
727                            Template::raw_string(
728                                &source[prev_end..span.start()],
729                                None,
730                                false,
731                                trim_line_required,
732                            ),
733                            line_no,
734                            col_no,
735                        );
736                    }
737
738                    // reset standalone statement marker
739                    trim_line_required = false;
740                }
741
742                let (line_no, col_no) = span.start_pos().line_col();
743                match rule {
744                    Rule::template => {
745                        template_stack.push_front(Template::new());
746                    }
747                    Rule::raw_text => {
748                        // leading space fix
749                        let start = if span.start() != prev_end {
750                            prev_end
751                        } else {
752                            span.start()
753                        };
754
755                        let t = template_stack.front_mut().unwrap();
756
757                        t.push_element(
758                            Template::raw_string(
759                                &source[start..span.end()],
760                                Some(pair.clone()),
761                                omit_pro_ws,
762                                trim_line_required,
763                            ),
764                            line_no,
765                            col_no,
766                        );
767
768                        // reset standalone statement marker
769                        trim_line_required = false;
770                    }
771                    Rule::helper_block_start
772                    | Rule::raw_block_start
773                    | Rule::decorator_block_start
774                    | Rule::partial_block_start => {
775                        let exp = Template::parse_expression(source, it.by_ref(), span.end())?;
776
777                        if exp.omit_pre_ws {
778                            Template::remove_previous_whitespace(&mut template_stack);
779                        }
780                        omit_pro_ws = exp.omit_pro_ws;
781
782                        // standalone statement check, it also removes leading whitespaces of
783                        // previous rawstring when standalone statement detected
784                        trim_line_required = Template::process_standalone_statement(
785                            &mut template_stack,
786                            source,
787                            &span,
788                            true,
789                            options.is_partial,
790                        );
791
792                        let indent_before_write = trim_line_required && !exp.omit_pre_ws;
793
794                        match rule {
795                            Rule::helper_block_start | Rule::raw_block_start => {
796                                let helper_template =
797                                    HelperTemplate::new(exp.clone(), true, indent_before_write);
798                                helper_stack.push_front(helper_template);
799                            }
800                            Rule::decorator_block_start | Rule::partial_block_start => {
801                                let decorator =
802                                    DecoratorTemplate::new(exp.clone(), indent_before_write);
803                                decorator_stack.push_front(decorator);
804                            }
805                            _ => unreachable!(),
806                        }
807
808                        let t = template_stack.front_mut().unwrap();
809                        t.mapping.push(TemplateMapping(line_no, col_no));
810                    }
811                    Rule::invert_tag | Rule::invert_chain_tag => {
812                        // hack: invert_tag structure is similar to ExpressionSpec, so I
813                        // use it here to represent the data
814
815                        if rule == Rule::invert_chain_tag {
816                            let _ = Template::parse_name(source, &mut it, span.end())?;
817                        }
818                        let exp = Template::parse_expression(source, it.by_ref(), span.end())?;
819
820                        if exp.omit_pre_ws {
821                            Template::remove_previous_whitespace(&mut template_stack);
822                        }
823                        omit_pro_ws = exp.omit_pro_ws;
824
825                        // standalone statement check, it also removes leading whitespaces of
826                        // previous rawstring when standalone statement detected
827                        trim_line_required = Template::process_standalone_statement(
828                            &mut template_stack,
829                            source,
830                            &span,
831                            true,
832                            options.is_partial,
833                        );
834
835                        let indent_before_write = trim_line_required && !exp.omit_pre_ws;
836
837                        let t = template_stack.pop_front().unwrap();
838                        let h = helper_stack.front_mut().unwrap();
839
840                        if rule == Rule::invert_chain_tag {
841                            h.set_chained();
842                        }
843
844                        h.set_chain_template(Some(t));
845                        if rule == Rule::invert_chain_tag {
846                            h.insert_inverse_node(Box::new(HelperTemplate::new_chain(
847                                exp,
848                                true,
849                                indent_before_write,
850                            )));
851                        }
852                    }
853
854                    Rule::raw_block_text => {
855                        let mut t = Template::new();
856                        t.push_element(
857                            Template::raw_string(
858                                span.as_str(),
859                                Some(pair.clone()),
860                                omit_pro_ws,
861                                trim_line_required,
862                            ),
863                            line_no,
864                            col_no,
865                        );
866                        template_stack.push_front(t);
867                    }
868                    Rule::expression
869                    | Rule::html_expression
870                    | Rule::decorator_expression
871                    | Rule::partial_expression
872                    | Rule::helper_block_end
873                    | Rule::raw_block_end
874                    | Rule::decorator_block_end
875                    | Rule::partial_block_end => {
876                        let exp = Template::parse_expression(source, it.by_ref(), span.end())?;
877
878                        if exp.omit_pre_ws {
879                            Template::remove_previous_whitespace(&mut template_stack);
880                        }
881                        omit_pro_ws = exp.omit_pro_ws;
882
883                        match rule {
884                            Rule::expression | Rule::html_expression => {
885                                let helper_template =
886                                    HelperTemplate::new(exp.clone(), false, false);
887                                let el = if rule == Rule::expression {
888                                    Expression(Box::new(helper_template))
889                                } else {
890                                    HtmlExpression(Box::new(helper_template))
891                                };
892                                let t = template_stack.front_mut().unwrap();
893                                t.push_element(el, line_no, col_no);
894                            }
895                            Rule::decorator_expression | Rule::partial_expression => {
896                                // do not auto trim ident spaces for
897                                // partial_expression(>)
898                                let prevent_indent =
899                                    !(rule == Rule::partial_expression && options.prevent_indent);
900                                trim_line_required = Template::process_standalone_statement(
901                                    &mut template_stack,
902                                    source,
903                                    &span,
904                                    prevent_indent,
905                                    options.is_partial,
906                                );
907
908                                // indent for partial expression >
909                                let mut indent = None;
910                                if rule == Rule::partial_expression
911                                    && !options.prevent_indent
912                                    && !exp.omit_pre_ws
913                                {
914                                    indent = support::str::find_trailing_whitespace_chars(
915                                        &source[..span.start()],
916                                    );
917                                }
918
919                                let mut decorator = DecoratorTemplate::new(
920                                    exp.clone(),
921                                    trim_line_required && !exp.omit_pre_ws,
922                                );
923                                decorator.indent = indent.map(std::borrow::ToOwned::to_owned);
924
925                                let el = if rule == Rule::decorator_expression {
926                                    DecoratorExpression(Box::new(decorator))
927                                } else {
928                                    PartialExpression(Box::new(decorator))
929                                };
930                                let t = template_stack.front_mut().unwrap();
931                                t.push_element(el, line_no, col_no);
932                            }
933                            Rule::helper_block_end | Rule::raw_block_end => {
934                                // standalone statement check, it also removes leading whitespaces of
935                                // previous rawstring when standalone statement detected
936                                trim_line_required = Template::process_standalone_statement(
937                                    &mut template_stack,
938                                    source,
939                                    &span,
940                                    true,
941                                    options.is_partial,
942                                );
943
944                                let mut h = helper_stack.pop_front().unwrap();
945                                let close_tag_name = exp.name.as_name();
946                                if h.name.as_name() == close_tag_name {
947                                    let prev_t = template_stack.pop_front().unwrap();
948                                    h.revert_chain_and_set(Some(prev_t));
949
950                                    let t = template_stack.front_mut().unwrap();
951                                    t.elements.push(HelperBlock(Box::new(h)));
952                                } else {
953                                    return Err(TemplateError::of(
954                                        TemplateErrorReason::MismatchingClosedHelper(
955                                            h.name.debug_name(),
956                                            exp.name.debug_name(),
957                                        ),
958                                    )
959                                    .at(source, line_no, col_no)
960                                    .in_template(options.name()));
961                                }
962                            }
963                            Rule::decorator_block_end | Rule::partial_block_end => {
964                                // standalone statement check, it also removes leading whitespaces of
965                                // previous rawstring when standalone statement detected
966                                trim_line_required = Template::process_standalone_statement(
967                                    &mut template_stack,
968                                    source,
969                                    &span,
970                                    true,
971                                    options.is_partial,
972                                );
973
974                                let mut d = decorator_stack.pop_front().unwrap();
975                                let close_tag_name = exp.name.as_name();
976                                if d.name.as_name() == close_tag_name {
977                                    let prev_t = template_stack.pop_front().unwrap();
978                                    d.template = Some(prev_t);
979                                    let t = template_stack.front_mut().unwrap();
980                                    if rule == Rule::decorator_block_end {
981                                        t.elements.push(DecoratorBlock(Box::new(d)));
982                                    } else {
983                                        t.elements.push(PartialBlock(Box::new(d)));
984                                    }
985                                } else {
986                                    return Err(TemplateError::of(
987                                        TemplateErrorReason::MismatchingClosedDecorator(
988                                            d.name.debug_name(),
989                                            exp.name.debug_name(),
990                                        ),
991                                    )
992                                    .at(source, line_no, col_no)
993                                    .in_template(options.name()));
994                                }
995                            }
996                            _ => unreachable!(),
997                        }
998                    }
999                    Rule::hbs_comment_compact => {
1000                        trim_line_required = Template::process_standalone_statement(
1001                            &mut template_stack,
1002                            source,
1003                            &span,
1004                            true,
1005                            options.is_partial,
1006                        );
1007
1008                        let text = span
1009                            .as_str()
1010                            .trim_start_matches("{{!")
1011                            .trim_end_matches("}}");
1012                        let t = template_stack.front_mut().unwrap();
1013                        t.push_element(Comment(text.to_owned()), line_no, col_no);
1014                    }
1015                    Rule::hbs_comment => {
1016                        trim_line_required = Template::process_standalone_statement(
1017                            &mut template_stack,
1018                            source,
1019                            &span,
1020                            true,
1021                            options.is_partial,
1022                        );
1023
1024                        let text = span
1025                            .as_str()
1026                            .trim_start_matches("{{!--")
1027                            .trim_end_matches("--}}");
1028                        let t = template_stack.front_mut().unwrap();
1029                        t.push_element(Comment(text.to_owned()), line_no, col_no);
1030                    }
1031                    _ => {}
1032                }
1033
1034                if rule != Rule::template {
1035                    end_pos = Some(span.end_pos());
1036                }
1037            } else {
1038                let prev_end = end_pos.as_ref().map_or(0, pest::Position::pos);
1039                if prev_end < source.len() {
1040                    let text = &source[prev_end..source.len()];
1041                    // is some called in if check
1042                    let (line_no, col_no) = end_pos.unwrap().line_col();
1043                    let t = template_stack.front_mut().unwrap();
1044                    t.push_element(RawString(text.to_owned()), line_no, col_no);
1045                }
1046                let mut root_template = template_stack.pop_front().unwrap();
1047                root_template.name = options.name;
1048                return Ok(root_template);
1049            }
1050        }
1051    }
1052
1053    // These two compile functions are kept for compatibility with 4.x
1054    // Template APIs in case that some developers are using them
1055    // without registry.
1056
1057    pub fn compile(source: &str) -> Result<Template, TemplateError> {
1058        Self::compile2(source, TemplateOptions::default())
1059    }
1060
1061    pub fn compile_with_name<S: AsRef<str>>(
1062        source: S,
1063        name: String,
1064    ) -> Result<Template, TemplateError> {
1065        Self::compile2(
1066            source.as_ref(),
1067            TemplateOptions {
1068                name: Some(name),
1069                ..Default::default()
1070            },
1071        )
1072    }
1073}
1074
1075#[non_exhaustive]
1076#[derive(PartialEq, Eq, Clone, Debug)]
1077pub enum TemplateElement {
1078    RawString(String),
1079    HtmlExpression(Box<HelperTemplate>),
1080    Expression(Box<HelperTemplate>),
1081    HelperBlock(Box<HelperTemplate>),
1082    DecoratorExpression(Box<DecoratorTemplate>),
1083    DecoratorBlock(Box<DecoratorTemplate>),
1084    PartialExpression(Box<DecoratorTemplate>),
1085    PartialBlock(Box<DecoratorTemplate>),
1086    Comment(String),
1087}
1088
1089#[cfg(test)]
1090mod test {
1091    use super::*;
1092    use crate::error::TemplateErrorReason;
1093
1094    #[test]
1095    fn test_parse_escaped_tag_raw_string() {
1096        let source = r"foo \{{bar}}";
1097        let t = Template::compile(source).ok().unwrap();
1098        assert_eq!(t.elements.len(), 1);
1099        assert_eq!(
1100            *t.elements.first().unwrap(),
1101            RawString("foo {{bar}}".to_string())
1102        );
1103    }
1104
1105    #[test]
1106    fn test_pure_backslash_raw_string() {
1107        let source = r"\\\\";
1108        let t = Template::compile(source).ok().unwrap();
1109        assert_eq!(t.elements.len(), 1);
1110        assert_eq!(*t.elements.first().unwrap(), RawString(source.to_string()));
1111    }
1112
1113    #[test]
1114    fn test_parse_escaped_block_raw_string() {
1115        let source = r"\{{{{foo}}}} bar";
1116        let t = Template::compile(source).ok().unwrap();
1117        assert_eq!(t.elements.len(), 1);
1118        assert_eq!(
1119            *t.elements.first().unwrap(),
1120            RawString("{{{{foo}}}} bar".to_string())
1121        );
1122    }
1123
1124    #[test]
1125    fn test_parse_template() {
1126        let source = "<h1>{{title}} 你好</h1> {{{content}}}
1127{{#if date}}<p>good</p>{{else}}<p>bad</p>{{/if}}<img>{{foo bar}}中文你好
1128{{#unless true}}kitkat{{^}}lollipop{{/unless}}";
1129        let t = Template::compile(source).ok().unwrap();
1130
1131        assert_eq!(t.elements.len(), 10);
1132
1133        assert_eq!(*t.elements.first().unwrap(), RawString("<h1>".to_string()));
1134        assert_eq!(
1135            *t.elements.get(1).unwrap(),
1136            Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1137                &["title"]
1138            ))))
1139        );
1140
1141        assert_eq!(
1142            *t.elements.get(3).unwrap(),
1143            HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1144                &["content"],
1145            ))))
1146        );
1147
1148        match *t.elements.get(5).unwrap() {
1149            HelperBlock(ref h) => {
1150                assert_eq!(h.name.as_name().unwrap(), "if".to_string());
1151                assert_eq!(h.params.len(), 1);
1152                assert_eq!(h.template.as_ref().unwrap().elements.len(), 1);
1153            }
1154            _ => {
1155                panic!("Helper expected here.");
1156            }
1157        };
1158
1159        match *t.elements.get(7).unwrap() {
1160            Expression(ref h) => {
1161                assert_eq!(h.name.as_name().unwrap(), "foo".to_string());
1162                assert_eq!(h.params.len(), 1);
1163                assert_eq!(
1164                    *(h.params.first().unwrap()),
1165                    Parameter::Path(Path::with_named_paths(&["bar"]))
1166                );
1167            }
1168            _ => {
1169                panic!("Helper expression here");
1170            }
1171        };
1172
1173        match *t.elements.get(9).unwrap() {
1174            HelperBlock(ref h) => {
1175                assert_eq!(h.name.as_name().unwrap(), "unless".to_string());
1176                assert_eq!(h.params.len(), 1);
1177                assert_eq!(h.inverse.as_ref().unwrap().elements.len(), 1);
1178            }
1179            _ => {
1180                panic!("Helper expression here");
1181            }
1182        };
1183    }
1184
1185    #[test]
1186    fn test_parse_block_partial_path_identifier() {
1187        let source = "{{#> foo/bar}}{{/foo/bar}}";
1188        assert!(Template::compile(source).is_ok());
1189    }
1190
1191    #[test]
1192    fn test_parse_error() {
1193        let source = "{{#ifequals name compare=\"hello\"}}\nhello\n\t{{else}}\ngood";
1194
1195        let terr = Template::compile(source).unwrap_err();
1196
1197        assert!(matches!(
1198            terr.reason(),
1199            TemplateErrorReason::InvalidSyntax(_)
1200        ));
1201        assert_eq!(terr.pos(), Some((4, 5)));
1202    }
1203
1204    #[test]
1205    fn test_subexpression() {
1206        let source =
1207            "{{foo (bar)}}{{foo (bar baz)}} hello {{#if (baz bar) then=(bar)}}world{{/if}}";
1208        let t = Template::compile(source).ok().unwrap();
1209
1210        assert_eq!(t.elements.len(), 4);
1211        match *t.elements.first().unwrap() {
1212            Expression(ref h) => {
1213                assert_eq!(h.name.as_name().unwrap(), "foo".to_owned());
1214                assert_eq!(h.params.len(), 1);
1215                if let Parameter::Subexpression(t) = h.params.first().unwrap() {
1216                    assert_eq!(t.name(), "bar".to_owned());
1217                } else {
1218                    panic!("Subexpression expected");
1219                }
1220            }
1221            _ => {
1222                panic!("Helper expression expected");
1223            }
1224        };
1225
1226        match *t.elements.get(1).unwrap() {
1227            Expression(ref h) => {
1228                assert_eq!(h.name.as_name().unwrap(), "foo".to_string());
1229                assert_eq!(h.params.len(), 1);
1230                if let Parameter::Subexpression(t) = h.params.first().unwrap() {
1231                    assert_eq!(t.name(), "bar".to_owned());
1232                    if let Some(Parameter::Path(p)) = t.params().unwrap().first() {
1233                        assert_eq!(p, &Path::with_named_paths(&["baz"]));
1234                    } else {
1235                        panic!("non-empty param expected ");
1236                    }
1237                } else {
1238                    panic!("Subexpression expected");
1239                }
1240            }
1241            _ => {
1242                panic!("Helper expression expected");
1243            }
1244        };
1245
1246        match *t.elements.get(3).unwrap() {
1247            HelperBlock(ref h) => {
1248                assert_eq!(h.name.as_name().unwrap(), "if".to_string());
1249                assert_eq!(h.params.len(), 1);
1250                assert_eq!(h.hash.len(), 1);
1251
1252                if let Parameter::Subexpression(t) = h.params.first().unwrap() {
1253                    assert_eq!(t.name(), "baz".to_owned());
1254                    if let Some(Parameter::Path(p)) = t.params().unwrap().first() {
1255                        assert_eq!(p, &Path::with_named_paths(&["bar"]));
1256                    } else {
1257                        panic!("non-empty param expected ");
1258                    }
1259                } else {
1260                    panic!("Subexpression expected (baz bar)");
1261                }
1262
1263                if let Parameter::Subexpression(t) = h.hash.get("then").unwrap() {
1264                    assert_eq!(t.name(), "bar".to_owned());
1265                } else {
1266                    panic!("Subexpression expected (bar)");
1267                }
1268            }
1269            _ => {
1270                panic!("HelperBlock expected");
1271            }
1272        }
1273    }
1274
1275    #[test]
1276    fn test_white_space_omitter() {
1277        let source = "hello~     {{~world~}} \n  !{{~#if true}}else{{/if~}}";
1278        let t = Template::compile(source).ok().unwrap();
1279
1280        assert_eq!(t.elements.len(), 4);
1281
1282        assert_eq!(t.elements[0], RawString("hello~".to_string()));
1283        assert_eq!(
1284            t.elements[1],
1285            Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1286                &["world"]
1287            ))))
1288        );
1289        assert_eq!(t.elements[2], RawString("!".to_string()));
1290
1291        let t2 = Template::compile("{{#if true}}1  {{~ else ~}} 2 {{~/if}}")
1292            .ok()
1293            .unwrap();
1294        assert_eq!(t2.elements.len(), 1);
1295        match t2.elements[0] {
1296            HelperBlock(ref h) => {
1297                assert_eq!(
1298                    h.template.as_ref().unwrap().elements[0],
1299                    RawString("1".to_string())
1300                );
1301                assert_eq!(
1302                    h.inverse.as_ref().unwrap().elements[0],
1303                    RawString("2".to_string())
1304                );
1305            }
1306            _ => unreachable!(),
1307        }
1308    }
1309
1310    #[test]
1311    fn test_unclosed_expression() {
1312        let sources = ["{{invalid", "{{{invalid", "{{invalid}", "{{!hello"];
1313        for s in &sources {
1314            let result = Template::compile(s.to_owned());
1315            let err = result.expect_err("expected a syntax error");
1316            let syntax_error_msg = match err.reason() {
1317                TemplateErrorReason::InvalidSyntax(s) => s,
1318                _ => panic!("InvalidSyntax expected"),
1319            };
1320            assert!(
1321                syntax_error_msg.contains("expected identifier"),
1322                "{}",
1323                syntax_error_msg
1324            );
1325        }
1326    }
1327
1328    #[test]
1329    fn test_raw_helper() {
1330        let source = "hello{{{{raw}}}}good{{night}}{{{{/raw}}}}world";
1331        match Template::compile(source) {
1332            Ok(t) => {
1333                assert_eq!(t.elements.len(), 3);
1334                assert_eq!(t.elements[0], RawString("hello".to_owned()));
1335                assert_eq!(t.elements[2], RawString("world".to_owned()));
1336                match t.elements[1] {
1337                    HelperBlock(ref h) => {
1338                        assert_eq!(h.name.as_name().unwrap(), "raw".to_owned());
1339                        if let Some(ref ht) = h.template {
1340                            assert_eq!(ht.elements.len(), 1);
1341                            assert_eq!(
1342                                *ht.elements.first().unwrap(),
1343                                RawString("good{{night}}".to_owned())
1344                            );
1345                        } else {
1346                            panic!("helper template not found");
1347                        }
1348                    }
1349                    _ => {
1350                        panic!("Unexpected element type");
1351                    }
1352                }
1353            }
1354            Err(e) => {
1355                panic!("{}", e);
1356            }
1357        }
1358    }
1359
1360    #[test]
1361    fn test_literal_parameter_parser() {
1362        match Template::compile("{{hello 1 name=\"value\" valid=false ref=someref}}") {
1363            Ok(t) => {
1364                if let Expression(ref ht) = t.elements[0] {
1365                    assert_eq!(ht.params[0], Parameter::Literal(json!(1)));
1366                    assert_eq!(
1367                        ht.hash["name"],
1368                        Parameter::Literal(Json::String("value".to_owned()))
1369                    );
1370                    assert_eq!(ht.hash["valid"], Parameter::Literal(Json::Bool(false)));
1371                    assert_eq!(
1372                        ht.hash["ref"],
1373                        Parameter::Path(Path::with_named_paths(&["someref"]))
1374                    );
1375                }
1376            }
1377            Err(e) => panic!("{}", e),
1378        }
1379    }
1380
1381    #[test]
1382    fn test_template_mapping() {
1383        match Template::compile("hello\n  {{~world}}\n{{#if nice}}\n\thello\n{{/if}}") {
1384            Ok(t) => {
1385                assert_eq!(t.mapping.len(), t.elements.len());
1386                assert_eq!(t.mapping[0], TemplateMapping(1, 1));
1387                assert_eq!(t.mapping[1], TemplateMapping(2, 3));
1388                assert_eq!(t.mapping[3], TemplateMapping(3, 1));
1389            }
1390            Err(e) => panic!("{}", e),
1391        }
1392    }
1393
1394    #[test]
1395    fn test_whitespace_elements() {
1396        let c = Template::compile(
1397            "  {{elem}}\n\t{{#if true}} \
1398         {{/if}}\n{{{{raw}}}} {{{{/raw}}}}\n{{{{raw}}}}{{{{/raw}}}}\n",
1399        );
1400        let r = c.unwrap();
1401        // the \n after last raw block is dropped by pest
1402        assert_eq!(r.elements.len(), 9);
1403    }
1404
1405    #[test]
1406    fn test_block_param() {
1407        match Template::compile("{{#each people as |person|}}{{person}}{{/each}}") {
1408            Ok(t) => {
1409                if let HelperBlock(ref ht) = t.elements[0] {
1410                    if let Some(BlockParam::Single(Parameter::Name(ref n))) = ht.block_param {
1411                        assert_eq!(n, "person");
1412                    } else {
1413                        panic!("block param expected.")
1414                    }
1415                } else {
1416                    panic!("Helper block expected");
1417                }
1418            }
1419            Err(e) => panic!("{}", e),
1420        }
1421
1422        match Template::compile("{{#each people as |val key|}}{{person}}{{/each}}") {
1423            Ok(t) => {
1424                if let HelperBlock(ref ht) = t.elements[0] {
1425                    if let Some(BlockParam::Pair((
1426                        Parameter::Name(ref n1),
1427                        Parameter::Name(ref n2),
1428                    ))) = ht.block_param
1429                    {
1430                        assert_eq!(n1, "val");
1431                        assert_eq!(n2, "key");
1432                    } else {
1433                        panic!("helper block param expected.");
1434                    }
1435                } else {
1436                    panic!("Helper block expected");
1437                }
1438            }
1439            Err(e) => panic!("{}", e),
1440        }
1441    }
1442
1443    #[test]
1444    fn test_decorator() {
1445        match Template::compile("hello {{* ssh}} world") {
1446            Err(e) => panic!("{}", e),
1447            Ok(t) => {
1448                if let DecoratorExpression(ref de) = t.elements[1] {
1449                    assert_eq!(de.name.as_name(), Some("ssh"));
1450                    assert_eq!(de.template, None);
1451                }
1452            }
1453        }
1454
1455        match Template::compile("hello {{> ssh}} world") {
1456            Err(e) => panic!("{}", e),
1457            Ok(t) => {
1458                if let PartialExpression(ref de) = t.elements[1] {
1459                    assert_eq!(de.name.as_name(), Some("ssh"));
1460                    assert_eq!(de.template, None);
1461                }
1462            }
1463        }
1464
1465        match Template::compile("{{#*inline \"hello\"}}expand to hello{{/inline}}{{> hello}}") {
1466            Err(e) => panic!("{}", e),
1467            Ok(t) => {
1468                if let DecoratorBlock(ref db) = t.elements[0] {
1469                    assert_eq!(db.name, Parameter::Name("inline".to_owned()));
1470                    assert_eq!(
1471                        db.params[0],
1472                        Parameter::Literal(Json::String("hello".to_owned()))
1473                    );
1474                    assert_eq!(
1475                        db.template.as_ref().unwrap().elements[0],
1476                        TemplateElement::RawString("expand to hello".to_owned())
1477                    );
1478                }
1479            }
1480        }
1481
1482        match Template::compile("{{#> layout \"hello\"}}expand to hello{{/layout}}{{> hello}}") {
1483            Err(e) => panic!("{}", e),
1484            Ok(t) => {
1485                if let PartialBlock(ref db) = t.elements[0] {
1486                    assert_eq!(db.name, Parameter::Name("layout".to_owned()));
1487                    assert_eq!(
1488                        db.params[0],
1489                        Parameter::Literal(Json::String("hello".to_owned()))
1490                    );
1491                    assert_eq!(
1492                        db.template.as_ref().unwrap().elements[0],
1493                        TemplateElement::RawString("expand to hello".to_owned())
1494                    );
1495                }
1496            }
1497        }
1498    }
1499
1500    #[test]
1501    fn test_panic_with_tag_name() {
1502        let s = "{{#>(X)}}{{/X}}";
1503        let result = Template::compile(s);
1504        assert!(result.is_err());
1505        assert_eq!("decorator \"Subexpression(Subexpression { element: Expression(HelperTemplate { name: Path(Relative(([Named(\\\"X\\\")], \\\"X\\\"))), params: [], hash: {}, block_param: None, template: None, inverse: None, block: false, chain: false, indent_before_write: false }) })\" was opened, but \"X\" is closing", format!("{}", result.unwrap_err().reason()));
1506    }
1507}