• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sunng87 / handlebars-rust / 13870729611

15 Mar 2025 07:23AM UTC coverage: 82.233% (+0.3%) from 81.928%
13870729611

Pull #699

github

web-flow
Merge cf2a8d0a1 into e9c4fe7f0
Pull Request #699: feat: correct implementation of partial block

33 of 33 new or added lines in 3 files covered. (100.0%)

4 existing lines in 2 files now uncovered.

1569 of 1908 relevant lines covered (82.23%)

7.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.18
/src/render.rs
1
use std::borrow::{Borrow, Cow};
2
use std::collections::{BTreeMap, VecDeque};
3
use std::fmt;
4
use std::rc::Rc;
5

6
use serde_json::value::Value as Json;
7

8
use crate::block::BlockContext;
9
use crate::context::Context;
10
use crate::error::RenderError;
11
use crate::helpers::HelperDef;
12
use crate::json::path::Path;
13
use crate::json::value::{JsonRender, PathAndJson, ScopedJson};
14
use crate::output::{Output, StringOutput};
15
use crate::registry::Registry;
16
use crate::support;
17
use crate::support::str::newline_matcher;
18
use crate::template::TemplateElement::{
19
    DecoratorBlock, DecoratorExpression, Expression, HelperBlock, HtmlExpression, PartialBlock,
20
    PartialExpression, RawString,
21
};
22
use crate::template::{
23
    BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
24
    TemplateMapping,
25
};
26
use crate::{partial, RenderErrorReason};
27

28
const HELPER_MISSING: &str = "helperMissing";
29
const BLOCK_HELPER_MISSING: &str = "blockHelperMissing";
30

31
/// The context of a render call
32
///
33
/// This context stores information of a render and a writer where generated
34
/// content is written to.
35
///
36
#[derive(Clone)]
37
pub struct RenderContext<'reg: 'rc, 'rc> {
38
    dev_mode_templates: Option<&'rc BTreeMap<String, Cow<'rc, Template>>>,
39

40
    blocks: VecDeque<BlockContext<'rc>>,
41

42
    // copy-on-write context
43
    modified_context: Option<Rc<Context>>,
44

45
    partials: BTreeMap<String, &'rc Template>,
46
    partial_block_stack: VecDeque<&'rc Template>,
47
    partial_block_depth: isize,
48
    local_helpers: BTreeMap<String, Rc<dyn HelperDef + Send + Sync + 'rc>>,
49
    /// current template name
50
    current_template: Option<&'rc String>,
51
    /// root template name
52
    root_template: Option<&'reg String>,
53
    disable_escape: bool,
54

55
    // Indicates whether the previous text that we rendered ended on a newline.
56
    // This is necessary to make indenting decisions after the end of partials.
57
    trailing_newline: bool,
58

59
    // This should be set to true whenever any output is written.
60
    // We need this to detect empty partials/helpers for indenting decisions.
61
    content_produced: bool,
62

63
    // The next text that we render should indent itself.
64
    indent_before_write: bool,
65
    indent_string: Option<Cow<'rc, str>>,
66
}
67

68
impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
69
    /// Create a render context
70
    pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> {
18✔
71
        let mut blocks = VecDeque::with_capacity(5);
17✔
72
        blocks.push_front(BlockContext::new());
33✔
73

74
        let modified_context = None;
21✔
75
        RenderContext {
76
            partials: BTreeMap::new(),
18✔
77
            partial_block_stack: VecDeque::new(),
21✔
78
            partial_block_depth: 0,
79
            local_helpers: BTreeMap::new(),
19✔
80
            current_template: None,
81
            root_template,
82
            disable_escape: false,
83
            trailing_newline: false,
84
            content_produced: false,
85
            indent_before_write: false,
86
            indent_string: None,
87
            blocks,
88
            modified_context,
89
            dev_mode_templates: None,
90
        }
91
    }
92

93
    /// Push a block context into render context stack. This is typically
94
    /// called when you entering a block scope.
95
    pub fn push_block(&mut self, block: BlockContext<'rc>) {
8✔
96
        self.blocks.push_front(block);
11✔
97
    }
98

99
    /// Pop and drop current block context.
100
    /// This is typically called when leaving a block scope.
101
    pub fn pop_block(&mut self) {
8✔
102
        self.blocks.pop_front();
7✔
103
    }
104

105
    /// Remove all blocks
106
    pub(crate) fn clear_blocks(&mut self) {
×
107
        self.blocks.clear();
×
108
    }
109

110
    /// Borrow a reference to current block context
UNCOV
111
    pub fn block(&self) -> Option<&BlockContext<'rc>> {
×
UNCOV
112
        self.blocks.front()
×
113
    }
114

115
    /// Borrow a mutable reference to current block context in order to
116
    /// modify some data.
117
    pub fn block_mut(&mut self) -> Option<&mut BlockContext<'rc>> {
8✔
118
        self.blocks.front_mut()
7✔
119
    }
120

121
    /// Replace blocks hold by render context and return the replaced blocks
122
    pub fn replace_blocks(
6✔
123
        &mut self,
124
        blocks: VecDeque<BlockContext<'rc>>,
125
    ) -> VecDeque<BlockContext<'rc>> {
126
        std::mem::replace(&mut self.blocks, blocks)
5✔
127
    }
128

129
    /// Get the modified context data if any
130
    pub fn context(&self) -> Option<Rc<Context>> {
13✔
131
        self.modified_context.clone()
12✔
132
    }
133

134
    /// Set new context data into the render process.
135
    /// This is typically called in decorators where user can modify
136
    /// the data they were rendering.
137
    pub fn set_context(&mut self, ctx: Context) {
1✔
138
        self.modified_context = Some(Rc::new(ctx));
2✔
139
    }
140

141
    /// Evaluate a Json path in current scope.
142
    ///
143
    /// Typically you don't need to evaluate it by yourself.
144
    /// The Helper and Decorator API will provide your evaluated value of
145
    /// their parameters and hash data.
146
    pub fn evaluate(
3✔
147
        &self,
148
        context: &'rc Context,
149
        relative_path: &str,
150
    ) -> Result<ScopedJson<'rc>, RenderError> {
151
        let path = Path::parse(relative_path)?;
3✔
152
        self.evaluate2(context, &path)
3✔
153
    }
154

155
    pub(crate) fn evaluate2(
12✔
156
        &self,
157
        context: &'rc Context,
158
        path: &Path,
159
    ) -> Result<ScopedJson<'rc>, RenderError> {
160
        match path {
13✔
161
            Path::Local((level, name, _)) => Ok(self
2✔
162
                .get_local_var(*level, name)
1✔
163
                .map_or_else(|| ScopedJson::Missing, |v| ScopedJson::Derived(v.clone()))),
4✔
164
            Path::Relative((segs, _)) => context.navigate(segs, &self.blocks),
12✔
165
        }
166
    }
167

168
    /// Get registered partial in this render context
169
    pub fn get_partial(&self, name: &str) -> Option<&'rc Template> {
6✔
170
        if name == partial::PARTIAL_BLOCK {
5✔
171
            return self
2✔
172
                .partial_block_stack
×
173
                .get(self.partial_block_depth as usize)
1✔
174
                .copied();
×
175
        }
176
        self.partials.get(name).copied()
6✔
177
    }
178

179
    /// Register a partial for this context
180
    pub fn set_partial(&mut self, name: String, partial: &'rc Template) {
4✔
181
        self.partials.insert(name, partial);
3✔
182
    }
183

184
    pub(crate) fn push_partial_block(&mut self, partial: &'rc Template) {
1✔
185
        self.partial_block_stack.push_front(partial);
1✔
186
    }
187

188
    pub(crate) fn pop_partial_block(&mut self) {
1✔
189
        self.partial_block_stack.pop_front();
1✔
190
    }
191

192
    pub(crate) fn inc_partial_block_depth(&mut self) {
1✔
193
        self.partial_block_depth += 1;
1✔
194
    }
195

196
    pub(crate) fn dec_partial_block_depth(&mut self) {
5✔
197
        let depth = &mut self.partial_block_depth;
6✔
198
        if *depth > 0 {
6✔
199
            *depth -= 1;
1✔
200
        }
201
    }
202

203
    pub(crate) fn set_indent_string(&mut self, indent: Option<Cow<'rc, str>>) {
5✔
204
        self.indent_string = indent;
12✔
205
    }
206

207
    #[inline]
208
    pub(crate) fn get_indent_string(&self) -> Option<&Cow<'rc, str>> {
14✔
209
        self.indent_string.as_ref()
15✔
210
    }
211

212
    pub(crate) fn get_dev_mode_template(&self, name: &str) -> Option<&'rc Template> {
3✔
213
        self.dev_mode_templates
3✔
214
            .and_then(|dmt| dmt.get(name).map(|t| &**t))
×
215
    }
216

217
    pub(crate) fn set_dev_mode_templates(
1✔
218
        &mut self,
219
        t: Option<&'rc BTreeMap<String, Cow<'rc, Template>>>,
220
    ) {
221
        self.dev_mode_templates = t;
1✔
222
    }
223

224
    /// Remove a registered partial
225
    pub fn remove_partial(&mut self, name: &str) {
×
226
        self.partials.remove(name);
×
227
    }
228

229
    fn get_local_var(&self, level: usize, name: &str) -> Option<&Json> {
1✔
230
        self.blocks
1✔
231
            .get(level)
×
232
            .and_then(|blk| blk.get_local_var(name))
2✔
233
    }
234

235
    /// Test if given template name is current template.
236
    pub fn is_current_template(&self, p: &str) -> bool {
5✔
237
        self.current_template.is_some_and(|s| s == p)
7✔
238
    }
239

240
    /// Register a helper in this render context.
241
    /// This is a feature provided by Decorator where you can create
242
    /// temporary helpers.
243
    pub fn register_local_helper(
1✔
244
        &mut self,
245
        name: &str,
246
        def: Box<dyn HelperDef + Send + Sync + 'rc>,
247
    ) {
248
        self.local_helpers.insert(name.to_string(), def.into());
2✔
249
    }
250

251
    /// Remove a helper from render context
252
    pub fn unregister_local_helper(&mut self, name: &str) {
1✔
253
        self.local_helpers.remove(name);
1✔
254
    }
255

256
    /// Attempt to get a helper from current render context.
257
    pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + Send + Sync + 'rc>> {
13✔
258
        self.local_helpers.get(name).cloned()
10✔
259
    }
260

261
    #[inline]
262
    fn has_local_helper(&self, name: &str) -> bool {
12✔
263
        self.local_helpers.contains_key(name)
12✔
264
    }
265

266
    /// Returns the current template name.
267
    /// Note that the name can be vary from root template when you are rendering
268
    /// from partials.
269
    pub fn get_current_template_name(&self) -> Option<&'rc String> {
5✔
270
        self.current_template
6✔
271
    }
272

273
    /// Set the current template name.
274
    pub fn set_current_template_name(&mut self, name: Option<&'rc String>) {
21✔
275
        self.current_template = name;
18✔
276
    }
277

278
    /// Get root template name if any.
279
    /// This is the template name that you call `render` from `Handlebars`.
280
    pub fn get_root_template_name(&self) -> Option<&'reg String> {
×
281
        self.root_template
×
282
    }
283

284
    /// Get the escape toggle
285
    pub fn is_disable_escape(&self) -> bool {
12✔
286
        self.disable_escape
12✔
287
    }
288

289
    /// Set the escape toggle.
290
    /// When toggle is on, `escape_fn` will be called when rendering.
291
    pub fn set_disable_escape(&mut self, disable: bool) {
4✔
292
        self.disable_escape = disable;
4✔
293
    }
294

295
    #[inline]
296
    pub fn set_trailing_newline(&mut self, trailing_newline: bool) {
18✔
297
        self.trailing_newline = trailing_newline;
19✔
298
    }
299

300
    #[inline]
301
    pub fn get_trailine_newline(&self) -> bool {
14✔
302
        self.trailing_newline
12✔
303
    }
304

305
    #[inline]
306
    pub fn set_content_produced(&mut self, content_produced: bool) {
16✔
307
        self.content_produced = content_produced;
15✔
308
    }
309

310
    #[inline]
311
    pub fn get_content_produced(&self) -> bool {
14✔
312
        self.content_produced
13✔
313
    }
314

315
    #[inline]
316
    pub fn set_indent_before_write(&mut self, indent_before_write: bool) {
16✔
317
        self.indent_before_write = indent_before_write;
15✔
318
    }
319

320
    #[inline]
321
    pub fn get_indent_before_write(&self) -> bool {
18✔
322
        self.indent_before_write
17✔
323
    }
324
}
325

326
impl fmt::Debug for RenderContext<'_, '_> {
327
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
×
328
        f.debug_struct("RenderContextInner")
×
329
            .field("dev_mode_templates", &self.dev_mode_templates)
×
330
            .field("blocks", &self.blocks)
331
            .field("modified_context", &self.modified_context)
×
332
            .field("partials", &self.partials)
×
333
            .field("partial_block_stack", &self.partial_block_stack)
×
334
            .field("partial_block_depth", &self.partial_block_depth)
×
335
            .field("root_template", &self.root_template)
×
336
            .field("current_template", &self.current_template)
×
337
            .field("disable_escape", &self.disable_escape)
×
338
            .finish()
339
    }
340
}
341

342
/// Render-time Helper data when using in a helper definition
343
#[derive(Debug, Clone)]
344
pub struct Helper<'rc> {
345
    name: Cow<'rc, str>,
346
    params: Vec<PathAndJson<'rc>>,
347
    hash: BTreeMap<&'rc str, PathAndJson<'rc>>,
348
    template: Option<&'rc Template>,
349
    inverse: Option<&'rc Template>,
350
    block_param: Option<&'rc BlockParam>,
351
    block: bool,
352
}
353

354
impl<'reg: 'rc, 'rc> Helper<'rc> {
355
    fn try_from_template(
11✔
356
        ht: &'rc HelperTemplate,
357
        registry: &'reg Registry<'reg>,
358
        context: &'rc Context,
359
        render_context: &mut RenderContext<'reg, 'rc>,
360
    ) -> Result<Helper<'rc>, RenderError> {
361
        let name = ht.name.expand_as_name(registry, context, render_context)?;
11✔
362
        let mut pv = Vec::with_capacity(ht.params.len());
22✔
363
        for p in &ht.params {
41✔
364
            let r = p.expand(registry, context, render_context)?;
21✔
365
            pv.push(r);
10✔
366
        }
367

368
        let mut hm = BTreeMap::new();
12✔
369
        for (k, p) in &ht.hash {
28✔
370
            let r = p.expand(registry, context, render_context)?;
6✔
371
            hm.insert(k.as_ref(), r);
6✔
372
        }
373

374
        Ok(Helper {
12✔
375
            name,
10✔
376
            params: pv,
13✔
377
            hash: hm,
11✔
378
            template: ht.template.as_ref(),
13✔
379
            inverse: ht.inverse.as_ref(),
11✔
380
            block_param: ht.block_param.as_ref(),
11✔
381
            block: ht.block,
11✔
382
        })
383
    }
384

385
    /// Returns helper name
386
    pub fn name(&self) -> &str {
10✔
387
        &self.name
13✔
388
    }
389

390
    /// Returns all helper params, resolved within the context
391
    pub fn params(&self) -> &Vec<PathAndJson<'rc>> {
3✔
392
        &self.params
×
393
    }
394

395
    /// Returns nth helper param, resolved within the context.
396
    ///
397
    /// ## Example
398
    ///
399
    /// To get the first param in `{{my_helper abc}}` or `{{my_helper 2}}`,
400
    /// use `h.param(0)` in helper definition.
401
    /// Variable `abc` is auto resolved in current context.
402
    ///
403
    /// ```
404
    /// use handlebars::*;
405
    ///
406
    /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
407
    ///     let v = h.param(0).map(|v| v.value())
408
    ///         .ok_or(RenderErrorReason::ParamNotFoundForIndex("myhelper", 0));
409
    ///     // ..
410
    ///     Ok(())
411
    /// }
412
    /// ```
413
    pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> {
12✔
414
        self.params.get(idx)
10✔
415
    }
416

417
    /// Returns hash, resolved within the context
418
    pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> {
2✔
419
        &self.hash
2✔
420
    }
421

422
    /// Return hash value of a given key, resolved within the context
423
    ///
424
    /// ## Example
425
    ///
426
    /// To get the first param in `{{my_helper v=abc}}` or `{{my_helper v=2}}`,
427
    /// use `h.hash_get("v")` in helper definition.
428
    /// Variable `abc` is auto resolved in current context.
429
    ///
430
    /// ```
431
    /// use handlebars::*;
432
    ///
433
    /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
434
    ///     let v = h.hash_get("v").map(|v| v.value())
435
    ///         .ok_or(RenderErrorReason::ParamNotFoundForIndex("my_helper", 0));
436
    ///     // ..
437
    ///     Ok(())
438
    /// }
439
    /// ```
440
    pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'rc>> {
4✔
441
        self.hash.get(key)
5✔
442
    }
443

444
    /// Returns the default inner template if the helper is a block helper.
445
    ///
446
    /// Typically you will render the template via: `template.render(registry, render_context)`
447
    ///
448
    pub fn template(&self) -> Option<&'rc Template> {
10✔
449
        self.template
10✔
450
    }
451

452
    /// Returns the template of `else` branch if any
453
    pub fn inverse(&self) -> Option<&'rc Template> {
2✔
454
        self.inverse
2✔
455
    }
456

457
    /// Returns if the helper is a block one `{{#helper}}{{/helper}}` or not `{{helper 123}}`
458
    pub fn is_block(&self) -> bool {
1✔
459
        self.block
1✔
460
    }
461

462
    /// Returns if the helper has either a block param or block param pair
463
    pub fn has_block_param(&self) -> bool {
×
464
        self.block_param.is_some()
×
465
    }
466

467
    /// Returns block param if any
468
    pub fn block_param(&self) -> Option<&'rc str> {
9✔
469
        if let Some(&BlockParam::Single(Parameter::Name(ref s))) = self.block_param {
13✔
470
            Some(s)
3✔
471
        } else {
472
            None
7✔
473
        }
474
    }
475

476
    /// Return block param pair (for example |key, val|) if any
477
    pub fn block_param_pair(&self) -> Option<(&'rc str, &'rc str)> {
6✔
478
        if let Some(&BlockParam::Pair((Parameter::Name(ref s1), Parameter::Name(ref s2)))) =
18✔
479
            self.block_param
×
480
        {
481
            Some((s1, s2))
2✔
482
        } else {
483
            None
6✔
484
        }
485
    }
486
}
487

488
/// Render-time Decorator data when using in a decorator definition
489
#[derive(Debug)]
490
pub struct Decorator<'rc> {
491
    name: Cow<'rc, str>,
492
    params: Vec<PathAndJson<'rc>>,
493
    hash: BTreeMap<&'rc str, PathAndJson<'rc>>,
494
    template: Option<&'rc Template>,
495
    indent: Option<Cow<'rc, str>>,
496
}
497

498
impl<'reg: 'rc, 'rc> Decorator<'rc> {
499
    fn try_from_template(
6✔
500
        dt: &'rc DecoratorTemplate,
501
        registry: &'reg Registry<'reg>,
502
        context: &'rc Context,
503
        render_context: &mut RenderContext<'reg, 'rc>,
504
    ) -> Result<Decorator<'rc>, RenderError> {
505
        let name = dt.name.expand_as_name(registry, context, render_context)?;
6✔
506

507
        let mut pv = Vec::with_capacity(dt.params.len());
12✔
508
        for p in &dt.params {
22✔
509
            let r = p.expand(registry, context, render_context)?;
10✔
510
            pv.push(r);
5✔
511
        }
512

513
        let mut hm = BTreeMap::new();
6✔
514
        for (k, p) in &dt.hash {
18✔
515
            let r = p.expand(registry, context, render_context)?;
4✔
516
            hm.insert(k.as_ref(), r);
4✔
517
        }
518

519
        let indent = match (render_context.get_indent_string(), dt.indent.as_ref()) {
14✔
520
            (None, None) => None,
8✔
521
            (Some(s), None) => Some(s.clone()),
2✔
522
            (None, Some(s)) => Some(Cow::Borrowed(&**s)),
4✔
523
            (Some(s1), Some(s2)) => {
2✔
524
                let mut res = s1.to_string();
4✔
525
                res.push_str(s2);
4✔
526
                Some(Cow::from(res))
2✔
527
            }
528
        };
529

530
        Ok(Decorator {
8✔
531
            name,
8✔
532
            params: pv,
8✔
533
            hash: hm,
8✔
534
            template: dt.template.as_ref(),
8✔
535
            indent,
8✔
536
        })
537
    }
538

539
    /// Returns helper name
540
    pub fn name(&self) -> &str {
8✔
541
        self.name.as_ref()
8✔
542
    }
543

544
    /// Returns all helper params, resolved within the context
545
    pub fn params(&self) -> &Vec<PathAndJson<'rc>> {
×
546
        &self.params
×
547
    }
548

549
    /// Returns nth helper param, resolved within the context
550
    pub fn param(&self, idx: usize) -> Option<&PathAndJson<'rc>> {
7✔
551
        self.params.get(idx)
7✔
552
    }
553

554
    /// Returns hash, resolved within the context
555
    pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> {
5✔
556
        &self.hash
6✔
557
    }
558

559
    /// Return hash value of a given key, resolved within the context
560
    pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'rc>> {
×
561
        self.hash.get(key)
×
562
    }
563

564
    /// Returns the default inner template if any
565
    pub fn template(&self) -> Option<&'rc Template> {
5✔
566
        self.template
6✔
567
    }
568

569
    pub fn indent(&self) -> Option<&Cow<'rc, str>> {
5✔
570
        self.indent.as_ref()
6✔
571
    }
572
}
573

574
/// Render trait
575
pub trait Renderable {
576
    /// render into `RenderContext`'s `writer`
577
    fn render<'reg: 'rc, 'rc>(
578
        &'rc self,
579
        registry: &'reg Registry<'reg>,
580
        context: &'rc Context,
581
        rc: &mut RenderContext<'reg, 'rc>,
582
        out: &mut dyn Output,
583
    ) -> Result<(), RenderError>;
584

585
    /// render into string
586
    fn renders<'reg: 'rc, 'rc>(
×
587
        &'rc self,
588
        registry: &'reg Registry<'reg>,
589
        ctx: &'rc Context,
590
        rc: &mut RenderContext<'reg, 'rc>,
591
    ) -> Result<String, RenderError> {
592
        let mut so = StringOutput::new();
×
593
        self.render(registry, ctx, rc, &mut so)?;
×
594
        so.into_string()
×
595
            .map_err(|e| RenderErrorReason::from(e).into())
×
596
    }
597
}
598

599
/// Evaluate decorator
600
pub trait Evaluable {
601
    fn eval<'reg: 'rc, 'rc>(
602
        &'rc self,
603
        registry: &'reg Registry<'reg>,
604
        context: &'rc Context,
605
        rc: &mut RenderContext<'reg, 'rc>,
606
    ) -> Result<(), RenderError>;
607
}
608

609
#[inline]
610
fn call_helper_for_value<'reg: 'rc, 'rc>(
5✔
611
    hd: &dyn HelperDef,
612
    ht: &Helper<'rc>,
613
    r: &'reg Registry<'reg>,
614
    ctx: &'rc Context,
615
    rc: &mut RenderContext<'reg, 'rc>,
616
) -> Result<PathAndJson<'rc>, RenderError> {
617
    match hd.call_inner(ht, r, ctx, rc) {
5✔
618
        Ok(result) => Ok(PathAndJson::new(None, result)),
3✔
619
        Err(e) => {
2✔
620
            if e.is_unimplemented() {
6✔
621
                // parse value from output
622
                let mut so = StringOutput::new();
1✔
623

624
                // here we don't want subexpression result escaped,
625
                // so we temporarily disable it
626
                let disable_escape = rc.is_disable_escape();
2✔
627
                rc.set_disable_escape(true);
1✔
628

629
                hd.call(ht, r, ctx, rc, &mut so)?;
1✔
630
                rc.set_disable_escape(disable_escape);
1✔
631

632
                let string = so.into_string().map_err(RenderError::from)?;
1✔
633
                Ok(PathAndJson::new(
2✔
634
                    None,
1✔
635
                    ScopedJson::Derived(Json::String(string)),
1✔
636
                ))
637
            } else {
638
                Err(e)
1✔
639
            }
640
        }
641
    }
642
}
643

644
impl Parameter {
645
    pub fn expand_as_name<'reg: 'rc, 'rc>(
14✔
646
        &'rc self,
647
        registry: &'reg Registry<'reg>,
648
        ctx: &'rc Context,
649
        rc: &mut RenderContext<'reg, 'rc>,
650
    ) -> Result<Cow<'rc, str>, RenderError> {
651
        match self {
15✔
652
            Parameter::Name(ref name) => Ok(Cow::Borrowed(name)),
12✔
653
            Parameter::Path(ref p) => Ok(Cow::Borrowed(p.raw())),
12✔
654
            Parameter::Subexpression(_) => self
1✔
655
                .expand(registry, ctx, rc)
×
656
                .map(|v| v.value().render())
2✔
657
                .map(Cow::Owned),
×
658
            Parameter::Literal(ref j) => Ok(Cow::Owned(j.render())),
×
659
        }
660
    }
661

662
    pub fn expand<'reg: 'rc, 'rc>(
13✔
663
        &'rc self,
664
        registry: &'reg Registry<'reg>,
665
        ctx: &'rc Context,
666
        rc: &mut RenderContext<'reg, 'rc>,
667
    ) -> Result<PathAndJson<'rc>, RenderError> {
668
        match self {
13✔
669
            Parameter::Name(ref name) => {
×
670
                // FIXME: raise error when expanding with name?
671
                Ok(PathAndJson::new(Some(name.to_owned()), ScopedJson::Missing))
×
672
            }
673
            Parameter::Path(ref path) => {
13✔
674
                if let Some(rc_context) = rc.context() {
13✔
675
                    let result = rc.evaluate2(rc_context.borrow(), path)?;
2✔
676
                    Ok(PathAndJson::new(
2✔
677
                        Some(path.raw().to_owned()),
2✔
678
                        ScopedJson::Derived(result.as_json().clone()),
2✔
679
                    ))
680
                } else {
681
                    let result = rc.evaluate2(ctx, path)?;
26✔
682
                    Ok(PathAndJson::new(Some(path.raw().to_owned()), result))
24✔
683
                }
684
            }
685
            Parameter::Literal(ref j) => Ok(PathAndJson::new(None, ScopedJson::Constant(j))),
8✔
686
            Parameter::Subexpression(ref t) => match *t.as_element() {
5✔
687
                Expression(ref ht) => {
5✔
688
                    let name = ht.name.expand_as_name(registry, ctx, rc)?;
10✔
689

690
                    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
10✔
691
                    if let Some(ref d) = rc.get_local_helper(&name) {
13✔
692
                        call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)
×
693
                    } else {
694
                        let mut helper = registry.get_or_load_helper(&name)?;
10✔
695

696
                        if helper.is_none() {
11✔
697
                            helper = registry.get_or_load_helper(if ht.block {
4✔
698
                                BLOCK_HELPER_MISSING
×
699
                            } else {
700
                                HELPER_MISSING
1✔
701
                            })?;
702
                        }
703

704
                        helper
10✔
705
                            .ok_or_else(|| {
×
706
                                RenderErrorReason::HelperNotFound(name.to_string()).into()
×
707
                            })
708
                            .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc))
15✔
709
                    }
710
                }
711
                _ => unreachable!(),
712
            },
713
        }
714
    }
715
}
716

717
impl Renderable for Template {
718
    fn render<'reg: 'rc, 'rc>(
20✔
719
        &'rc self,
720
        registry: &'reg Registry<'reg>,
721
        ctx: &'rc Context,
722
        rc: &mut RenderContext<'reg, 'rc>,
723
        out: &mut dyn Output,
724
    ) -> Result<(), RenderError> {
725
        rc.set_current_template_name(self.name.as_ref());
17✔
726
        let iter = self.elements.iter();
21✔
727

728
        for (idx, t) in iter.enumerate() {
39✔
729
            t.render(registry, ctx, rc, out).map_err(|mut e| {
22✔
730
                // add line/col number if the template has mapping data
731
                if e.line_no.is_none() {
4✔
732
                    if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
4✔
733
                        e.line_no = Some(line);
2✔
734
                        e.column_no = Some(col);
2✔
735
                    }
736
                }
737

738
                if e.template_name.is_none() {
4✔
739
                    e.template_name.clone_from(&self.name);
2✔
740
                }
741

742
                e
2✔
743
            })?;
744
        }
745

746
        Ok(())
16✔
747
    }
748
}
749

750
impl Evaluable for Template {
751
    fn eval<'reg: 'rc, 'rc>(
1✔
752
        &'rc self,
753
        registry: &'reg Registry<'reg>,
754
        ctx: &'rc Context,
755
        rc: &mut RenderContext<'reg, 'rc>,
756
    ) -> Result<(), RenderError> {
757
        let iter = self.elements.iter();
1✔
758

759
        for (idx, t) in iter.enumerate() {
2✔
760
            t.eval(registry, ctx, rc).map_err(|mut e| {
1✔
761
                if e.line_no.is_none() {
×
762
                    if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
×
763
                        e.line_no = Some(line);
×
764
                        e.column_no = Some(col);
×
765
                    }
766
                }
767

768
                e.template_name.clone_from(&self.name);
×
769
                e
×
770
            })?;
771
        }
772
        Ok(())
1✔
773
    }
774
}
775

776
fn helper_exists<'reg: 'rc, 'rc>(
11✔
777
    name: &str,
778
    reg: &Registry<'reg>,
779
    rc: &RenderContext<'reg, 'rc>,
780
) -> bool {
781
    rc.has_local_helper(name) || reg.has_helper(name)
12✔
782
}
783

784
#[inline]
785
fn render_helper<'reg: 'rc, 'rc>(
11✔
786
    ht: &'rc HelperTemplate,
787
    registry: &'reg Registry<'reg>,
788
    ctx: &'rc Context,
789
    rc: &mut RenderContext<'reg, 'rc>,
790
    out: &mut dyn Output,
791
) -> Result<(), RenderError> {
792
    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
12✔
793
    debug!(
×
794
        "Rendering helper: {:?}, params: {:?}, hash: {:?}",
795
        h.name(),
×
796
        h.params(),
×
797
        h.hash()
×
798
    );
799
    let mut call_indent_aware = |helper_def: &dyn HelperDef, rc: &mut RenderContext<'reg, 'rc>| {
24✔
800
        let indent_directive_before = rc.get_indent_before_write();
14✔
801
        let content_produced_before = rc.get_content_produced();
13✔
802
        rc.set_content_produced(false);
11✔
803
        rc.set_indent_before_write(
14✔
804
            indent_directive_before || (ht.indent_before_write && rc.get_trailine_newline()),
12✔
805
        );
806

807
        helper_def.call(&h, registry, ctx, rc, out)?;
11✔
808

809
        if rc.get_content_produced() {
12✔
810
            rc.set_indent_before_write(rc.get_trailine_newline());
10✔
811
        } else {
812
            rc.set_content_produced(content_produced_before);
4✔
813
            rc.set_indent_before_write(indent_directive_before);
4✔
814
        }
815
        Ok(())
12✔
816
    };
817
    if let Some(ref d) = rc.get_local_helper(h.name()) {
32✔
818
        call_indent_aware(&**d, rc)
1✔
819
    } else {
820
        let mut helper = registry.get_or_load_helper(h.name())?;
23✔
821

822
        if helper.is_none() {
25✔
823
            helper = registry.get_or_load_helper(if ht.block {
8✔
824
                BLOCK_HELPER_MISSING
1✔
825
            } else {
826
                HELPER_MISSING
2✔
827
            })?;
828
        }
829

830
        helper
23✔
831
            .ok_or_else(|| RenderErrorReason::HelperNotFound(h.name().to_owned()).into())
2✔
832
            .and_then(|d| call_indent_aware(&*d, rc))
26✔
833
    }
834
}
835

836
pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: String) -> String {
12✔
837
    if !rc.is_disable_escape() {
28✔
838
        r.get_escape_fn()(&content)
25✔
839
    } else {
840
        content
5✔
841
    }
842
}
843

844
#[inline]
845
pub fn indent_aware_write(
17✔
846
    v: &str,
847
    rc: &mut RenderContext<'_, '_>,
848
    out: &mut dyn Output,
849
) -> Result<(), RenderError> {
850
    if v.is_empty() {
18✔
851
        return Ok(());
5✔
852
    }
853
    rc.set_content_produced(true);
16✔
854

855
    if !v.starts_with(newline_matcher) && rc.get_indent_before_write() {
34✔
856
        if let Some(indent) = rc.get_indent_string() {
4✔
857
            out.write(indent)?;
2✔
858
        }
859
    }
860

861
    if let Some(indent) = rc.get_indent_string() {
18✔
862
        support::str::write_indented(v, indent, out)?;
2✔
863
    } else {
864
        out.write(v.as_ref())?;
16✔
865
    }
866

867
    let trailing_newline = v.ends_with(newline_matcher);
18✔
868
    rc.set_trailing_newline(trailing_newline);
19✔
869
    rc.set_indent_before_write(trailing_newline);
18✔
870

871
    Ok(())
19✔
872
}
873

874
impl Renderable for TemplateElement {
875
    fn render<'reg: 'rc, 'rc>(
21✔
876
        &'rc self,
877
        registry: &'reg Registry<'reg>,
878
        ctx: &'rc Context,
879
        rc: &mut RenderContext<'reg, 'rc>,
880
        out: &mut dyn Output,
881
    ) -> Result<(), RenderError> {
882
        match self {
41✔
883
            RawString(ref v) => indent_aware_write(v.as_ref(), rc, out),
13✔
884
            Expression(ref ht) | HtmlExpression(ref ht) => {
16✔
885
                let is_html_expression = matches!(self, HtmlExpression(_));
12✔
886
                if is_html_expression {
13✔
887
                    rc.set_disable_escape(true);
4✔
888
                }
889

890
                // test if the expression is to render some value
891
                let result = if ht.is_name_only() {
12✔
892
                    let helper_name = ht.name.expand_as_name(registry, ctx, rc)?;
24✔
893
                    if helper_exists(&helper_name, registry, rc) {
22✔
894
                        render_helper(ht, registry, ctx, rc, out)
6✔
895
                    } else {
896
                        debug!("Rendering value: {:?}", ht.name);
×
897
                        let context_json = ht.name.expand(registry, ctx, rc)?;
17✔
898
                        if context_json.is_value_missing() {
17✔
899
                            if registry.strict_mode() {
5✔
900
                                Err(RenderError::strict_error(context_json.relative_path()))
2✔
901
                            } else {
902
                                // helper missing
903
                                if let Some(hook) = registry.get_or_load_helper(HELPER_MISSING)? {
3✔
904
                                    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
2✔
905
                                    hook.call(&h, registry, ctx, rc, out)
2✔
906
                                } else {
907
                                    Ok(())
1✔
908
                                }
909
                            }
910
                        } else {
911
                            let rendered = context_json.value().render();
19✔
912
                            let output = do_escape(registry, rc, rendered);
11✔
913
                            indent_aware_write(output.as_ref(), rc, out)
22✔
914
                        }
915
                    }
916
                } else {
917
                    // this is a helper expression
918
                    render_helper(ht, registry, ctx, rc, out)
5✔
919
                };
920

921
                if is_html_expression {
15✔
922
                    rc.set_disable_escape(false);
10✔
923
                }
924

925
                result
14✔
926
            }
927
            HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out),
10✔
928
            DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
3✔
929
            PartialExpression(ref dt) | PartialBlock(ref dt) => {
7✔
930
                let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
6✔
931

932
                let indent_directive_before = rc.get_indent_before_write();
10✔
933
                let content_produced_before = rc.get_content_produced();
6✔
934

935
                rc.set_indent_before_write(
6✔
936
                    dt.indent_before_write && (rc.get_trailine_newline() || dt.indent.is_some()),
10✔
937
                );
938
                rc.set_content_produced(false);
5✔
939

940
                partial::expand_partial(&di, registry, ctx, rc, out)?;
7✔
941

942
                if rc.get_content_produced() {
13✔
943
                    rc.set_indent_before_write(rc.get_trailine_newline());
12✔
944
                } else {
945
                    rc.set_content_produced(content_produced_before);
1✔
946
                    rc.set_indent_before_write(indent_directive_before);
1✔
947
                }
948
                Ok(())
6✔
949
            }
950
            _ => Ok(()),
2✔
951
        }
952
    }
953
}
954

955
impl Evaluable for TemplateElement {
956
    fn eval<'reg: 'rc, 'rc>(
3✔
957
        &'rc self,
958
        registry: &'reg Registry<'reg>,
959
        ctx: &'rc Context,
960
        rc: &mut RenderContext<'reg, 'rc>,
961
    ) -> Result<(), RenderError> {
962
        match *self {
4✔
963
            DecoratorExpression(ref dt) | DecoratorBlock(ref dt) => {
4✔
964
                let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
4✔
965
                match registry.get_decorator(di.name()) {
12✔
966
                    Some(d) => d.call(&di, registry, ctx, rc),
8✔
967
                    None => Err(RenderErrorReason::DecoratorNotFound(di.name().to_owned()).into()),
2✔
968
                }
969
            }
970
            _ => Ok(()),
1✔
971
        }
972
    }
973
}
974

975
#[cfg(test)]
976
mod test {
977
    use std::collections::BTreeMap;
978

979
    use super::{Helper, RenderContext, Renderable};
980
    use crate::block::BlockContext;
981
    use crate::context::Context;
982
    use crate::error::RenderError;
983
    use crate::json::path::Path;
984
    use crate::json::value::JsonRender;
985
    use crate::output::{Output, StringOutput};
986
    use crate::registry::Registry;
987
    use crate::template::TemplateElement::*;
988
    use crate::template::{HelperTemplate, Template, TemplateElement};
989

990
    #[test]
991
    fn test_raw_string() {
992
        let r = Registry::new();
993
        let raw_string = RawString("<h1>hello world</h1>".to_string());
994

995
        let mut out = StringOutput::new();
996
        let ctx = Context::null();
997
        {
998
            let mut rc = RenderContext::new(None);
999
            raw_string.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1000
        }
1001
        assert_eq!(
1002
            out.into_string().unwrap(),
1003
            "<h1>hello world</h1>".to_string()
1004
        );
1005
    }
1006

1007
    #[test]
1008
    fn test_expression() {
1009
        let r = Registry::new();
1010
        let element = Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1011
            &["hello"],
1012
        ))));
1013

1014
        let mut out = StringOutput::new();
1015
        let mut m: BTreeMap<String, String> = BTreeMap::new();
1016
        let value = "<p></p>".to_string();
1017
        m.insert("hello".to_string(), value);
1018
        let ctx = Context::wraps(&m).unwrap();
1019
        {
1020
            let mut rc = RenderContext::new(None);
1021
            element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1022
        }
1023

1024
        assert_eq!(
1025
            out.into_string().unwrap(),
1026
            "&lt;p&gt;&lt;/p&gt;".to_string()
1027
        );
1028
    }
1029

1030
    #[test]
1031
    fn test_html_expression() {
1032
        let r = Registry::new();
1033
        let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1034
            &["hello"],
1035
        ))));
1036

1037
        let mut out = StringOutput::new();
1038
        let mut m: BTreeMap<String, String> = BTreeMap::new();
1039
        let value = "world";
1040
        m.insert("hello".to_string(), value.to_string());
1041
        let ctx = Context::wraps(&m).unwrap();
1042
        {
1043
            let mut rc = RenderContext::new(None);
1044
            element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1045
        }
1046

1047
        assert_eq!(out.into_string().unwrap(), value.to_string());
1048
    }
1049

1050
    #[test]
1051
    fn test_template() {
1052
        let r = Registry::new();
1053
        let mut out = StringOutput::new();
1054
        let mut m: BTreeMap<String, String> = BTreeMap::new();
1055
        let value = "world".to_string();
1056
        m.insert("hello".to_string(), value);
1057
        let ctx = Context::wraps(&m).unwrap();
1058

1059
        let elements: Vec<TemplateElement> = vec![
1060
            RawString("<h1>".to_string()),
1061
            Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1062
                &["hello"],
1063
            )))),
1064
            RawString("</h1>".to_string()),
1065
            Comment(String::new()),
1066
        ];
1067

1068
        let template = Template {
1069
            elements,
1070
            name: None,
1071
            mapping: Vec::new(),
1072
        };
1073

1074
        {
1075
            let mut rc = RenderContext::new(None);
1076
            template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1077
        }
1078

1079
        assert_eq!(out.into_string().unwrap(), "<h1>world</h1>".to_string());
1080
    }
1081

1082
    #[test]
1083
    fn test_render_context_promotion_and_demotion() {
1084
        use crate::json::value::to_json;
1085
        let mut render_context = RenderContext::new(None);
1086
        let mut block = BlockContext::new();
1087

1088
        block.set_local_var("index", to_json(0));
1089
        render_context.push_block(block);
1090

1091
        render_context.push_block(BlockContext::new());
1092
        assert_eq!(
1093
            render_context.get_local_var(1, "index").unwrap(),
1094
            &to_json(0)
1095
        );
1096

1097
        render_context.pop_block();
1098

1099
        assert_eq!(
1100
            render_context.get_local_var(0, "index").unwrap(),
1101
            &to_json(0)
1102
        );
1103
    }
1104

1105
    #[test]
1106
    fn test_render_subexpression_issue_115() {
1107
        use crate::support::str::StringWriter;
1108

1109
        let mut r = Registry::new();
1110
        r.register_helper(
1111
            "format",
1112
            Box::new(
1113
                |h: &Helper<'_>,
1114
                 _: &Registry<'_>,
1115
                 _: &Context,
1116
                 _: &mut RenderContext<'_, '_>,
1117
                 out: &mut dyn Output|
1118
                 -> Result<(), RenderError> {
1119
                    out.write(&h.param(0).unwrap().value().render())
1120
                        .map_err(RenderError::from)
1121
                },
1122
            ),
1123
        );
1124

1125
        let mut sw = StringWriter::new();
1126
        let mut m: BTreeMap<String, String> = BTreeMap::new();
1127
        m.insert("a".to_string(), "123".to_string());
1128

1129
        {
1130
            if let Err(e) = r.render_template_to_write("{{format (format a)}}", &m, &mut sw) {
1131
                panic!("{}", e);
1132
            }
1133
        }
1134

1135
        assert_eq!(sw.into_string(), "123".to_string());
1136
    }
1137

1138
    #[test]
1139
    fn test_render_error_line_no() {
1140
        let mut r = Registry::new();
1141
        let m: BTreeMap<String, String> = BTreeMap::new();
1142

1143
        let name = "invalid_template";
1144
        assert!(r
1145
            .register_template_string(name, "<h1>\n{{#if true}}\n  {{#each}}{{/each}}\n{{/if}}")
1146
            .is_ok());
1147

1148
        if let Err(e) = r.render(name, &m) {
1149
            assert_eq!(e.line_no.unwrap(), 3);
1150
            assert_eq!(e.column_no.unwrap(), 3);
1151
            assert_eq!(e.template_name, Some(name.to_owned()));
1152
        } else {
1153
            panic!("Error expected");
1154
        }
1155
    }
1156

1157
    #[test]
1158
    fn test_partial_failback_render() {
1159
        let mut r = Registry::new();
1160

1161
        assert!(r
1162
            .register_template_string("parent", "<html>{{> layout}}</html>")
1163
            .is_ok());
1164
        assert!(r
1165
            .register_template_string(
1166
                "child",
1167
                "{{#*inline \"layout\"}}content{{/inline}}{{#> parent}}{{> seg}}{{/parent}}",
1168
            )
1169
            .is_ok());
1170
        assert!(r.register_template_string("seg", "1234").is_ok());
1171

1172
        let r = r.render("child", &true).expect("should work");
1173
        assert_eq!(r, "<html>content</html>");
1174
    }
1175

1176
    #[test]
1177
    fn test_key_with_slash() {
1178
        let mut r = Registry::new();
1179

1180
        assert!(r
1181
            .register_template_string("t", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
1182
            .is_ok());
1183

1184
        let r = r.render("t", &json!({"/foo": "bar"})).unwrap();
1185

1186
        assert_eq!(r, "/foo: bar\n");
1187
    }
1188

1189
    #[test]
1190
    fn test_comment() {
1191
        let r = Registry::new();
1192

1193
        assert_eq!(
1194
            r.render_template("Hello {{this}} {{! test me }}", &0)
1195
                .unwrap(),
1196
            "Hello 0 "
1197
        );
1198
    }
1199

1200
    #[test]
1201
    fn test_zero_args_heler() {
1202
        let mut r = Registry::new();
1203

1204
        r.register_helper(
1205
            "name",
1206
            Box::new(
1207
                |_: &Helper<'_>,
1208
                 _: &Registry<'_>,
1209
                 _: &Context,
1210
                 _: &mut RenderContext<'_, '_>,
1211
                 out: &mut dyn Output|
1212
                 -> Result<(), RenderError> {
1213
                    out.write("N/A").map_err(Into::into)
1214
                },
1215
            ),
1216
        );
1217

1218
        r.register_template_string("t0", "Output name: {{name}}")
1219
            .unwrap();
1220
        r.register_template_string("t1", "Output name: {{first_name}}")
1221
            .unwrap();
1222
        r.register_template_string("t2", "Output name: {{./name}}")
1223
            .unwrap();
1224

1225
        // when "name" is available in context, use context first
1226
        assert_eq!(
1227
            r.render("t0", &json!({"name": "Alex"})).unwrap(),
1228
            "Output name: N/A"
1229
        );
1230

1231
        // when "name" is unavailable, call helper with same name
1232
        assert_eq!(
1233
            r.render("t2", &json!({"name": "Alex"})).unwrap(),
1234
            "Output name: Alex"
1235
        );
1236

1237
        // output nothing when neither context nor helper available
1238
        assert_eq!(
1239
            r.render("t1", &json!({"name": "Alex"})).unwrap(),
1240
            "Output name: "
1241
        );
1242

1243
        // generate error in strict mode for above case
1244
        r.set_strict_mode(true);
1245
        assert!(r.render("t1", &json!({"name": "Alex"})).is_err());
1246

1247
        // output nothing when helperMissing was defined
1248
        r.set_strict_mode(false);
1249
        r.register_helper(
1250
            "helperMissing",
1251
            Box::new(
1252
                |h: &Helper<'_>,
1253
                 _: &Registry<'_>,
1254
                 _: &Context,
1255
                 _: &mut RenderContext<'_, '_>,
1256
                 out: &mut dyn Output|
1257
                 -> Result<(), RenderError> {
1258
                    let name = h.name();
1259
                    write!(out, "{name} not resolved")?;
1260
                    Ok(())
1261
                },
1262
            ),
1263
        );
1264
        assert_eq!(
1265
            r.render("t1", &json!({"name": "Alex"})).unwrap(),
1266
            "Output name: first_name not resolved"
1267
        );
1268
    }
1269

1270
    #[test]
1271
    fn test_identifiers_starting_with_numbers() {
1272
        let mut r = Registry::new();
1273

1274
        assert!(r
1275
            .register_template_string("r1", "{{#if 0a}}true{{/if}}")
1276
            .is_ok());
1277
        let r1 = r.render("r1", &json!({"0a": true})).unwrap();
1278
        assert_eq!(r1, "true");
1279

1280
        assert!(r.register_template_string("r2", "{{eq 1a 1}}").is_ok());
1281
        let r2 = r.render("r2", &json!({"1a": 2, "a": 1})).unwrap();
1282
        assert_eq!(r2, "false");
1283

1284
        assert!(r
1285
            .register_template_string("r3", "0: {{0}} {{#if (eq 0 true)}}resolved from context{{/if}}\n1a: {{1a}} {{#if (eq 1a true)}}resolved from context{{/if}}\n2_2: {{2_2}} {{#if (eq 2_2 true)}}resolved from context{{/if}}") // YUP it is just eq that barfs! is if handled specially? maybe this test should go nearer to specific helpers that fail?
1286
            .is_ok());
1287
        let r3 = r
1288
            .render("r3", &json!({"0": true, "1a": true, "2_2": true}))
1289
            .unwrap();
1290
        assert_eq!(
1291
            r3,
1292
            "0: true \n1a: true resolved from context\n2_2: true resolved from context"
1293
        );
1294

1295
        // these should all be errors:
1296
        assert!(r.register_template_string("r4", "{{eq 1}}").is_ok());
1297
        assert!(r.register_template_string("r5", "{{eq a1}}").is_ok());
1298
        assert!(r.register_template_string("r6", "{{eq 1a}}").is_ok());
1299
        assert!(r.render("r4", &()).is_err());
1300
        assert!(r.render("r5", &()).is_err());
1301
        assert!(r.render("r6", &()).is_err());
1302
    }
1303
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc