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#[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 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 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 Name(String),
124 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 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 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 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 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 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 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 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 if prevent_indent && with_leading_newline {
477 let t = template_stack.front_mut().unwrap();
478 if let Some(RawString(ref mut text)) = t.elements.last_mut() {
480 *text = text
482 .trim_end_matches(support::str::whitespace_matcher)
483 .to_owned();
484 }
485 }
486
487 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 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 for sub_pair in pair.into_inner().rev() {
515 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 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 let mut it = parser_queue
564 .flatten()
565 .filter(|p| {
566 !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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}