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