handlebars/
template.rs

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