Coverage Report

Created: 2025-06-02 07:01

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