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