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

sunng87 / handlebars-rust / 9728481336

30 Jun 2024 01:50AM UTC coverage: 81.625% (+0.2%) from 81.461%
9728481336

Pull #654

github

web-flow
Merge 16109f659 into 3504412d7
Pull Request #654: Fix indent v2

49 of 52 new or added lines in 4 files covered. (94.23%)

33 existing lines in 1 file now uncovered.

1497 of 1834 relevant lines covered (81.62%)

7.89 hits per line

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

81.79
/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
use crate::template::{
20
    BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
21
    TemplateMapping,
22
};
23
use crate::{partial, RenderErrorReason};
24

25
const HELPER_MISSING: &str = "helperMissing";
26
const BLOCK_HELPER_MISSING: &str = "blockHelperMissing";
27

28
/// The context of a render call
29
///
30
/// This context stores information of a render and a writer where generated
31
/// content is written to.
32
///
33
#[derive(Clone, Debug)]
34
pub struct RenderContext<'reg, 'rc> {
35
    inner: Rc<RenderContextInner<'reg, 'rc>>,
36
    blocks: VecDeque<BlockContext<'rc>>,
37
    // copy-on-write context
38
    modified_context: Option<Rc<Context>>,
39
}
40

41
#[derive(Clone)]
42
pub struct RenderContextInner<'reg: 'rc, 'rc> {
43
    partials: BTreeMap<String, &'rc Template>,
44
    partial_block_stack: VecDeque<&'reg Template>,
45
    partial_block_depth: isize,
46
    local_helpers: BTreeMap<String, Rc<dyn HelperDef + Send + Sync + 'rc>>,
47
    /// current template name
48
    current_template: Option<&'rc String>,
49
    /// root template name
50
    root_template: Option<&'reg String>,
51
    disable_escape: bool,
52
    // whether the previous text that we rendered ended on a newline
53
    // necessary to make indenting decisions after the end of partials
54
    trailing_newline: bool,
55
    // whether the previous text that we render should indent itself
56
    indent_before_write: bool,
57
    indent_string: Option<Cow<'reg, str>>,
58
}
59

60
impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
61
    /// Create a render context
62
    pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> {
14✔
63
        let inner = Rc::new(RenderContextInner {
16✔
64
            partials: BTreeMap::new(),
15✔
65
            partial_block_stack: VecDeque::new(),
15✔
66
            partial_block_depth: 0,
×
67
            local_helpers: BTreeMap::new(),
16✔
68
            current_template: None,
15✔
69
            root_template,
×
70
            disable_escape: false,
×
NEW
71
            trailing_newline: false,
×
NEW
72
            indent_before_write: false,
×
73
            indent_string: None,
16✔
74
        });
75

76
        let mut blocks = VecDeque::with_capacity(5);
16✔
77
        blocks.push_front(BlockContext::new());
32✔
78

79
        let modified_context = None;
17✔
80
        RenderContext {
81
            inner,
82
            blocks,
83
            modified_context,
84
        }
85
    }
86

87
    pub(crate) fn new_for_block(&self) -> RenderContext<'reg, 'rc> {
×
88
        let inner = self.inner.clone();
×
89

90
        let mut blocks = VecDeque::with_capacity(2);
×
91
        blocks.push_front(BlockContext::new());
×
92

93
        let modified_context = self.modified_context.clone();
×
94

95
        RenderContext {
96
            inner,
97
            blocks,
98
            modified_context,
99
        }
100
    }
101

102
    /// Push a block context into render context stack. This is typically
103
    /// called when you entering a block scope.
104
    pub fn push_block(&mut self, block: BlockContext<'rc>) {
8✔
105
        self.blocks.push_front(block);
8✔
106
    }
107

108
    /// Pop and drop current block context.
109
    /// This is typically called when leaving a block scope.
110
    pub fn pop_block(&mut self) {
9✔
111
        self.blocks.pop_front();
10✔
112
    }
113

114
    pub(crate) fn clear_blocks(&mut self) {
4✔
115
        self.blocks.clear();
4✔
116
    }
117

118
    /// Borrow a reference to current block context
119
    pub fn block(&self) -> Option<&BlockContext<'rc>> {
2✔
120
        self.blocks.front()
2✔
121
    }
122

123
    /// Borrow a mutable reference to current block context in order to
124
    /// modify some data.
125
    pub fn block_mut(&mut self) -> Option<&mut BlockContext<'rc>> {
9✔
126
        self.blocks.front_mut()
9✔
127
    }
128

129
    fn inner(&self) -> &RenderContextInner<'reg, 'rc> {
15✔
130
        self.inner.borrow()
15✔
131
    }
132

133
    fn inner_mut(&mut self) -> &mut RenderContextInner<'reg, 'rc> {
19✔
134
        Rc::make_mut(&mut self.inner)
17✔
135
    }
136

137
    /// Get the modified context data if any
138
    pub fn context(&self) -> Option<Rc<Context>> {
13✔
139
        self.modified_context.clone()
15✔
140
    }
141

142
    /// Set new context data into the render process.
143
    /// This is typically called in decorators where user can modify
144
    /// the data they were rendering.
145
    pub fn set_context(&mut self, ctx: Context) {
1✔
146
        self.modified_context = Some(Rc::new(ctx))
2✔
147
    }
148

149
    /// Evaluate a Json path in current scope.
150
    ///
151
    /// Typically you don't need to evaluate it by yourself.
152
    /// The Helper and Decorator API will provide your evaluated value of
153
    /// their parameters and hash data.
154
    pub fn evaluate(
×
155
        &self,
156
        context: &'rc Context,
157
        relative_path: &str,
158
    ) -> Result<ScopedJson<'rc>, RenderError> {
159
        let path = Path::parse(relative_path)?;
×
160
        self.evaluate2(context, &path)
×
161
    }
162

163
    pub(crate) fn evaluate2(
15✔
164
        &self,
165
        context: &'rc Context,
166
        path: &Path,
167
    ) -> Result<ScopedJson<'rc>, RenderError> {
168
        match path {
13✔
169
            Path::Local((level, name, _)) => Ok(self
2✔
170
                .get_local_var(*level, name)
1✔
171
                .map(|v| ScopedJson::Derived(v.clone()))
2✔
172
                .unwrap_or_else(|| ScopedJson::Missing)),
2✔
173
            Path::Relative((segs, _)) => context.navigate(segs, &self.blocks),
15✔
174
        }
175
    }
176

177
    /// Get registered partial in this render context
178
    pub fn get_partial(&self, name: &str) -> Option<&Template> {
5✔
179
        if name == partial::PARTIAL_BLOCK {
7✔
180
            return self
2✔
181
                .inner()
×
182
                .partial_block_stack
×
183
                .get(self.inner().partial_block_depth as usize)
1✔
184
                .copied();
×
185
        }
186
        self.inner().partials.get(name).copied()
5✔
187
    }
188

189
    /// Register a partial for this context
190
    pub fn set_partial(&mut self, name: String, partial: &'rc Template) {
5✔
191
        self.inner_mut().partials.insert(name, partial);
8✔
192
    }
193

194
    pub(crate) fn push_partial_block(&mut self, partial: &'reg Template) {
1✔
195
        self.inner_mut().partial_block_stack.push_front(partial);
2✔
196
    }
197

198
    pub(crate) fn pop_partial_block(&mut self) {
1✔
199
        self.inner_mut().partial_block_stack.pop_front();
1✔
200
    }
201

202
    pub(crate) fn inc_partial_block_depth(&mut self) {
1✔
203
        self.inner_mut().partial_block_depth += 1;
1✔
204
    }
205

206
    pub(crate) fn dec_partial_block_depth(&mut self) {
7✔
207
        let depth = &mut self.inner_mut().partial_block_depth;
5✔
208
        if *depth > 0 {
8✔
209
            *depth -= 1;
1✔
210
        }
211
    }
212

213
    pub(crate) fn set_indent_string(&mut self, indent: Option<Cow<'reg, str>>) {
5✔
214
        self.inner_mut().indent_string = indent;
7✔
215
    }
216

217
    #[inline]
218
    pub(crate) fn get_indent_string(&self) -> Option<&Cow<'reg, str>> {
16✔
219
        self.inner.indent_string.as_ref()
33✔
220
    }
221

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

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

233
    /// Test if given template name is current template.
234
    pub fn is_current_template(&self, p: &str) -> bool {
7✔
235
        self.inner()
10✔
236
            .current_template
×
237
            .map(|s| s == p)
9✔
238
            .unwrap_or(false)
239
    }
240

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

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

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

264
    #[inline]
265
    fn has_local_helper(&self, name: &str) -> bool {
10✔
266
        self.inner.local_helpers.contains_key(name)
21✔
267
    }
268

269
    /// Returns the current template name.
270
    /// Note that the name can be vary from root template when you are rendering
271
    /// from partials.
272
    pub fn get_current_template_name(&self) -> Option<&'rc String> {
×
273
        self.inner().current_template
×
274
    }
275

276
    /// Set the current template name.
277
    pub fn set_current_template_name(&mut self, name: Option<&'rc String>) {
19✔
278
        self.inner_mut().current_template = name;
17✔
279
    }
280

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

287
    /// Get the escape toggle
288
    pub fn is_disable_escape(&self) -> bool {
13✔
289
        self.inner().disable_escape
13✔
290
    }
291

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

298
    #[inline]
299
    pub fn set_trailing_newline(&mut self, trailing_newline: bool) {
17✔
300
        self.inner_mut().trailing_newline = trailing_newline;
18✔
301
    }
302

303
    #[inline]
304
    pub fn get_trailine_newline(&self) -> bool {
12✔
305
        self.inner().trailing_newline
13✔
306
    }
307

308
    #[inline]
309
    pub fn set_indent_before_write(&mut self, indent_before_write: bool) {
15✔
310
        self.inner_mut().indent_before_write = indent_before_write;
15✔
311
    }
312

313
    #[inline]
314
    pub fn get_indent_before_write(&self) -> bool {
20✔
315
        self.inner().indent_before_write
18✔
316
    }
317

318
    #[inline]
319
    pub fn take_indent_before_write(&mut self) -> bool {
20✔
320
        let res = self.get_indent_before_write();
18✔
321
        self.set_indent_before_write(false);
20✔
NEW
322
        res
×
323
    }
324
}
325

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

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

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

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

371
        Ok(Helper {
12✔
372
            name,
13✔
373
            params: pv,
12✔
374
            hash: hm,
13✔
375
            template: ht.template.as_ref(),
12✔
376
            inverse: ht.inverse.as_ref(),
13✔
377
            block_param: ht.block_param.as_ref(),
12✔
378
            block: ht.block,
13✔
379
        })
380
    }
381

382
    /// Returns helper name
383
    pub fn name(&self) -> &str {
13✔
384
        &self.name
13✔
385
    }
386

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

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

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

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

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

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

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

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

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

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

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

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

504
        let mut pv = Vec::with_capacity(dt.params.len());
14✔
505
        for p in &dt.params {
26✔
506
            let r = p.expand(registry, context, render_context)?;
10✔
507
            pv.push(r);
4✔
508
        }
509

510
        let mut hm = BTreeMap::new();
5✔
511
        for (k, p) in &dt.hash {
16✔
512
            let r = p.expand(registry, context, render_context)?;
5✔
513
            hm.insert(k.as_ref(), r);
4✔
514
        }
515

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

527
        Ok(Decorator {
8✔
528
            name,
6✔
529
            params: pv,
8✔
530
            hash: hm,
6✔
531
            template: dt.template.as_ref(),
8✔
532
            indent,
6✔
533
        })
534
    }
535

536
    /// Returns helper name
537
    pub fn name(&self) -> &str {
8✔
538
        self.name.as_ref()
6✔
539
    }
540

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

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

551
    /// Returns hash, resolved within the context
552
    pub fn hash(&self) -> &BTreeMap<&'rc str, PathAndJson<'rc>> {
7✔
553
        &self.hash
5✔
554
    }
555

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

561
    /// Returns the default inner template if any
562
    pub fn template(&self) -> Option<&'rc Template> {
5✔
563
        self.template
7✔
564
    }
565

566
    pub fn indent(&self) -> Option<&Cow<'rc, str>> {
7✔
567
        self.indent.as_ref()
5✔
568
    }
569
}
570

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

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

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

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

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

626
                hd.call(ht, r, ctx, rc, &mut so)?;
6✔
627
                rc.set_disable_escape(disable_escape);
1✔
628

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

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

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

687
                    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
6✔
688
                    if let Some(ref d) = rc.get_local_helper(&name) {
8✔
UNCOV
689
                        call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)
×
690
                    } else {
691
                        let mut helper = registry.get_or_load_helper(&name)?;
6✔
692

693
                        if helper.is_none() {
7✔
694
                            helper = registry.get_or_load_helper(if ht.block {
4✔
UNCOV
695
                                BLOCK_HELPER_MISSING
×
696
                            } else {
697
                                HELPER_MISSING
1✔
698
                            })?;
699
                        }
700

701
                        helper
12✔
702
                            .ok_or_else(|| {
2✔
UNCOV
703
                                RenderErrorReason::HelperNotFound(name.to_string()).into()
×
704
                            })
705
                            .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc))
8✔
706
                    }
707
                }
708
                _ => unreachable!(),
709
            },
710
        }
711
    }
712
}
713

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

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

735
                if e.template_name.is_none() {
4✔
736
                    e.template_name.clone_from(&self.name);
2✔
737
                }
738

739
                e
2✔
740
            })?;
741
        }
742

743
        Ok(())
14✔
744
    }
745
}
746

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

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

UNCOV
765
                e.template_name.clone_from(&self.name);
×
UNCOV
766
                e
×
767
            })?;
768
        }
769
        Ok(())
2✔
770
    }
771
}
772

773
fn helper_exists<'reg: 'rc, 'rc>(
10✔
774
    name: &str,
775
    reg: &Registry<'reg>,
776
    rc: &RenderContext<'reg, 'rc>,
777
) -> bool {
778
    rc.has_local_helper(name) || reg.has_helper(name)
11✔
779
}
780

781
#[inline]
782
fn render_helper<'reg: 'rc, 'rc>(
12✔
783
    ht: &'rc HelperTemplate,
784
    registry: &'reg Registry<'reg>,
785
    ctx: &'rc Context,
786
    rc: &mut RenderContext<'reg, 'rc>,
787
    out: &mut dyn Output,
788
) -> Result<(), RenderError> {
789
    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
13✔
790
    debug!(
×
791
        "Rendering helper: {:?}, params: {:?}, hash: {:?}",
792
        h.name(),
×
793
        h.params(),
×
794
        h.hash()
×
795
    );
796
    let mut call_indent_aware = |helper_def: &dyn HelperDef, rc: &mut RenderContext<'reg, 'rc>| {
25✔
797
        rc.set_indent_before_write(ht.indent_before_write);
12✔
798
        helper_def.call(&h, registry, ctx, rc, out)?;
27✔
799
        rc.set_indent_before_write(rc.get_trailine_newline());
11✔
800
        Ok(())
10✔
801
    };
802
    if let Some(ref d) = rc.get_local_helper(h.name()) {
37✔
803
        call_indent_aware(&**d, rc)
1✔
804
    } else {
805
        let mut helper = registry.get_or_load_helper(h.name())?;
26✔
806

807
        if helper.is_none() {
28✔
808
            helper = registry.get_or_load_helper(if ht.block {
8✔
809
                BLOCK_HELPER_MISSING
1✔
810
            } else {
811
                HELPER_MISSING
2✔
812
            })?;
813
        }
814

815
        helper
39✔
816
            .ok_or_else(|| RenderErrorReason::HelperNotFound(h.name().to_owned()).into())
14✔
817
            .and_then(|d| call_indent_aware(&*d, rc))
37✔
818
    }
819
}
820

821
pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: String) -> String {
13✔
822
    if !rc.is_disable_escape() {
30✔
823
        r.get_escape_fn()(&content)
26✔
824
    } else {
825
        content
4✔
826
    }
827
}
828

829
#[inline]
830
pub fn indent_aware_write(
17✔
831
    v: &str,
832
    rc: &mut RenderContext<'_, '_>,
833
    out: &mut dyn Output,
834
) -> Result<(), RenderError> {
835
    if v.is_empty() {
17✔
836
        return Ok(());
6✔
837
    }
838
    if !v.starts_with(newline_matcher) && rc.take_indent_before_write() {
33✔
839
        if let Some(indent) = rc.get_indent_string() {
7✔
840
            out.write(indent)?;
4✔
841
        }
842
    }
843
    if let Some(indent) = rc.get_indent_string() {
22✔
844
        out.write(support::str::with_indent(v, indent).as_ref())?;
4✔
845
    } else {
846
        out.write(v.as_ref())?;
34✔
847
    }
848
    rc.set_trailing_newline(v.ends_with(newline_matcher));
18✔
849
    Ok(())
17✔
850
}
851

852
impl Renderable for TemplateElement {
853
    fn render<'reg: 'rc, 'rc>(
19✔
854
        &'rc self,
855
        registry: &'reg Registry<'reg>,
856
        ctx: &'rc Context,
857
        rc: &mut RenderContext<'reg, 'rc>,
858
        out: &mut dyn Output,
859
    ) -> Result<(), RenderError> {
860
        match self {
45✔
861
            RawString(ref v) => indent_aware_write(v.as_ref(), rc, out),
11✔
862
            Expression(ref ht) | HtmlExpression(ref ht) => {
16✔
863
                let is_html_expression = matches!(self, HtmlExpression(_));
13✔
864
                if is_html_expression {
14✔
865
                    rc.set_disable_escape(true);
4✔
866
                }
867

868
                // test if the expression is to render some value
869
                let result = if ht.is_name_only() {
13✔
870
                    let helper_name = ht.name.expand_as_name(registry, ctx, rc)?;
22✔
871
                    if helper_exists(&helper_name, registry, rc) {
21✔
872
                        render_helper(ht, registry, ctx, rc, out)
6✔
873
                    } else {
UNCOV
874
                        debug!("Rendering value: {:?}", ht.name);
×
875
                        let context_json = ht.name.expand(registry, ctx, rc)?;
18✔
876
                        if context_json.is_value_missing() {
18✔
877
                            if registry.strict_mode() {
5✔
878
                                Err(RenderError::strict_error(context_json.relative_path()))
4✔
879
                            } else {
880
                                // helper missing
881
                                if let Some(hook) = registry.get_or_load_helper(HELPER_MISSING)? {
3✔
882
                                    let h = Helper::try_from_template(ht, registry, ctx, rc)?;
2✔
883
                                    hook.call(&h, registry, ctx, rc, out)
2✔
884
                                } else {
885
                                    Ok(())
1✔
886
                                }
887
                            }
888
                        } else {
889
                            let rendered = context_json.value().render();
19✔
890
                            let output = do_escape(registry, rc, rendered);
10✔
891
                            indent_aware_write(output.as_ref(), rc, out)
20✔
892
                        }
893
                    }
894
                } else {
895
                    // this is a helper expression
896
                    render_helper(ht, registry, ctx, rc, out)
6✔
897
                };
898

899
                if is_html_expression {
13✔
900
                    rc.set_disable_escape(false);
8✔
901
                }
902

903
                result
12✔
904
            }
905
            HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out),
10✔
906
            DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
5✔
907
            PartialExpression(ref dt) | PartialBlock(ref dt) => {
8✔
908
                let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
6✔
909
                rc.set_indent_before_write(dt.indent_before_write);
10✔
910
                partial::expand_partial(&di, registry, ctx, rc, out)?;
26✔
911
                rc.set_indent_before_write(rc.get_trailine_newline());
12✔
912
                Ok(())
6✔
913
            }
914
            _ => Ok(()),
1✔
915
        }
916
    }
917
}
918

919
impl Evaluable for TemplateElement {
920
    fn eval<'reg: 'rc, 'rc>(
3✔
921
        &'rc self,
922
        registry: &'reg Registry<'reg>,
923
        ctx: &'rc Context,
924
        rc: &mut RenderContext<'reg, 'rc>,
925
    ) -> Result<(), RenderError> {
926
        match *self {
6✔
927
            DecoratorExpression(ref dt) | DecoratorBlock(ref dt) => {
4✔
928
                let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
5✔
929
                match registry.get_decorator(di.name()) {
10✔
930
                    Some(d) => d.call(&di, registry, ctx, rc),
8✔
931
                    None => Err(RenderErrorReason::DecoratorNotFound(di.name().to_owned()).into()),
2✔
932
                }
933
            }
934
            _ => Ok(()),
2✔
935
        }
936
    }
937
}
938

939
#[cfg(test)]
940
mod test {
941
    use std::collections::BTreeMap;
942

943
    use super::{Helper, RenderContext, Renderable};
944
    use crate::block::BlockContext;
945
    use crate::context::Context;
946
    use crate::error::RenderError;
947
    use crate::json::path::Path;
948
    use crate::json::value::JsonRender;
949
    use crate::output::{Output, StringOutput};
950
    use crate::registry::Registry;
951
    use crate::template::TemplateElement::*;
952
    use crate::template::{HelperTemplate, Template, TemplateElement};
953

954
    #[test]
955
    fn test_raw_string() {
956
        let r = Registry::new();
957
        let raw_string = RawString("<h1>hello world</h1>".to_string());
958

959
        let mut out = StringOutput::new();
960
        let ctx = Context::null();
961
        {
962
            let mut rc = RenderContext::new(None);
963
            raw_string.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
964
        }
965
        assert_eq!(
966
            out.into_string().unwrap(),
967
            "<h1>hello world</h1>".to_string()
968
        );
969
    }
970

971
    #[test]
972
    fn test_expression() {
973
        let r = Registry::new();
974
        let element = Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
975
            &["hello"],
976
        ))));
977

978
        let mut out = StringOutput::new();
979
        let mut m: BTreeMap<String, String> = BTreeMap::new();
980
        let value = "<p></p>".to_string();
981
        m.insert("hello".to_string(), value);
982
        let ctx = Context::wraps(&m).unwrap();
983
        {
984
            let mut rc = RenderContext::new(None);
985
            element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
986
        }
987

988
        assert_eq!(
989
            out.into_string().unwrap(),
990
            "&lt;p&gt;&lt;/p&gt;".to_string()
991
        );
992
    }
993

994
    #[test]
995
    fn test_html_expression() {
996
        let r = Registry::new();
997
        let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
998
            &["hello"],
999
        ))));
1000

1001
        let mut out = StringOutput::new();
1002
        let mut m: BTreeMap<String, String> = BTreeMap::new();
1003
        let value = "world";
1004
        m.insert("hello".to_string(), value.to_string());
1005
        let ctx = Context::wraps(&m).unwrap();
1006
        {
1007
            let mut rc = RenderContext::new(None);
1008
            element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1009
        }
1010

1011
        assert_eq!(out.into_string().unwrap(), value.to_string());
1012
    }
1013

1014
    #[test]
1015
    fn test_template() {
1016
        let r = Registry::new();
1017
        let mut out = StringOutput::new();
1018
        let mut m: BTreeMap<String, String> = BTreeMap::new();
1019
        let value = "world".to_string();
1020
        m.insert("hello".to_string(), value);
1021
        let ctx = Context::wraps(&m).unwrap();
1022

1023
        let elements: Vec<TemplateElement> = vec![
1024
            RawString("<h1>".to_string()),
1025
            Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
1026
                &["hello"],
1027
            )))),
1028
            RawString("</h1>".to_string()),
1029
            Comment("".to_string()),
1030
        ];
1031

1032
        let template = Template {
1033
            elements,
1034
            name: None,
1035
            mapping: Vec::new(),
1036
        };
1037

1038
        {
1039
            let mut rc = RenderContext::new(None);
1040
            template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
1041
        }
1042

1043
        assert_eq!(out.into_string().unwrap(), "<h1>world</h1>".to_string());
1044
    }
1045

1046
    #[test]
1047
    fn test_render_context_promotion_and_demotion() {
1048
        use crate::json::value::to_json;
1049
        let mut render_context = RenderContext::new(None);
1050
        let mut block = BlockContext::new();
1051

1052
        block.set_local_var("index", to_json(0));
1053
        render_context.push_block(block);
1054

1055
        render_context.push_block(BlockContext::new());
1056
        assert_eq!(
1057
            render_context.get_local_var(1, "index").unwrap(),
1058
            &to_json(0)
1059
        );
1060

1061
        render_context.pop_block();
1062

1063
        assert_eq!(
1064
            render_context.get_local_var(0, "index").unwrap(),
1065
            &to_json(0)
1066
        );
1067
    }
1068

1069
    #[test]
1070
    fn test_render_subexpression_issue_115() {
1071
        use crate::support::str::StringWriter;
1072

1073
        let mut r = Registry::new();
1074
        r.register_helper(
1075
            "format",
1076
            Box::new(
1077
                |h: &Helper<'_>,
1078
                 _: &Registry<'_>,
1079
                 _: &Context,
1080
                 _: &mut RenderContext<'_, '_>,
1081
                 out: &mut dyn Output|
1082
                 -> Result<(), RenderError> {
1083
                    out.write(&h.param(0).unwrap().value().render())
1084
                        .map(|_| ())
1085
                        .map_err(RenderError::from)
1086
                },
1087
            ),
1088
        );
1089

1090
        let mut sw = StringWriter::new();
1091
        let mut m: BTreeMap<String, String> = BTreeMap::new();
1092
        m.insert("a".to_string(), "123".to_string());
1093

1094
        {
1095
            if let Err(e) = r.render_template_to_write("{{format (format a)}}", &m, &mut sw) {
1096
                panic!("{}", e);
1097
            }
1098
        }
1099

1100
        assert_eq!(sw.into_string(), "123".to_string());
1101
    }
1102

1103
    #[test]
1104
    fn test_render_error_line_no() {
1105
        let mut r = Registry::new();
1106
        let m: BTreeMap<String, String> = BTreeMap::new();
1107

1108
        let name = "invalid_template";
1109
        assert!(r
1110
            .register_template_string(name, "<h1>\n{{#if true}}\n  {{#each}}{{/each}}\n{{/if}}")
1111
            .is_ok());
1112

1113
        if let Err(e) = r.render(name, &m) {
1114
            assert_eq!(e.line_no.unwrap(), 3);
1115
            assert_eq!(e.column_no.unwrap(), 3);
1116
            assert_eq!(e.template_name, Some(name.to_owned()));
1117
        } else {
1118
            panic!("Error expected");
1119
        }
1120
    }
1121

1122
    #[test]
1123
    fn test_partial_failback_render() {
1124
        let mut r = Registry::new();
1125

1126
        assert!(r
1127
            .register_template_string("parent", "<html>{{> layout}}</html>")
1128
            .is_ok());
1129
        assert!(r
1130
            .register_template_string(
1131
                "child",
1132
                "{{#*inline \"layout\"}}content{{/inline}}{{#> parent}}{{> seg}}{{/parent}}",
1133
            )
1134
            .is_ok());
1135
        assert!(r.register_template_string("seg", "1234").is_ok());
1136

1137
        let r = r.render("child", &true).expect("should work");
1138
        assert_eq!(r, "<html>content</html>");
1139
    }
1140

1141
    #[test]
1142
    fn test_key_with_slash() {
1143
        let mut r = Registry::new();
1144

1145
        assert!(r
1146
            .register_template_string("t", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
1147
            .is_ok());
1148

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

1151
        assert_eq!(r, "/foo: bar\n");
1152
    }
1153

1154
    #[test]
1155
    fn test_comment() {
1156
        let r = Registry::new();
1157

1158
        assert_eq!(
1159
            r.render_template("Hello {{this}} {{! test me }}", &0)
1160
                .unwrap(),
1161
            "Hello 0 "
1162
        );
1163
    }
1164

1165
    #[test]
1166
    fn test_zero_args_heler() {
1167
        let mut r = Registry::new();
1168

1169
        r.register_helper(
1170
            "name",
1171
            Box::new(
1172
                |_: &Helper<'_>,
1173
                 _: &Registry<'_>,
1174
                 _: &Context,
1175
                 _: &mut RenderContext<'_, '_>,
1176
                 out: &mut dyn Output|
1177
                 -> Result<(), RenderError> {
1178
                    out.write("N/A").map_err(Into::into)
1179
                },
1180
            ),
1181
        );
1182

1183
        r.register_template_string("t0", "Output name: {{name}}")
1184
            .unwrap();
1185
        r.register_template_string("t1", "Output name: {{first_name}}")
1186
            .unwrap();
1187
        r.register_template_string("t2", "Output name: {{./name}}")
1188
            .unwrap();
1189

1190
        // when "name" is available in context, use context first
1191
        assert_eq!(
1192
            r.render("t0", &json!({"name": "Alex"})).unwrap(),
1193
            "Output name: N/A"
1194
        );
1195

1196
        // when "name" is unavailable, call helper with same name
1197
        assert_eq!(
1198
            r.render("t2", &json!({"name": "Alex"})).unwrap(),
1199
            "Output name: Alex"
1200
        );
1201

1202
        // output nothing when neither context nor helper available
1203
        assert_eq!(
1204
            r.render("t1", &json!({"name": "Alex"})).unwrap(),
1205
            "Output name: "
1206
        );
1207

1208
        // generate error in strict mode for above case
1209
        r.set_strict_mode(true);
1210
        assert!(r.render("t1", &json!({"name": "Alex"})).is_err());
1211

1212
        // output nothing when helperMissing was defined
1213
        r.set_strict_mode(false);
1214
        r.register_helper(
1215
            "helperMissing",
1216
            Box::new(
1217
                |h: &Helper<'_>,
1218
                 _: &Registry<'_>,
1219
                 _: &Context,
1220
                 _: &mut RenderContext<'_, '_>,
1221
                 out: &mut dyn Output|
1222
                 -> Result<(), RenderError> {
1223
                    let name = h.name();
1224
                    write!(out, "{} not resolved", name)?;
1225
                    Ok(())
1226
                },
1227
            ),
1228
        );
1229
        assert_eq!(
1230
            r.render("t1", &json!({"name": "Alex"})).unwrap(),
1231
            "Output name: first_name not resolved"
1232
        );
1233
    }
1234

1235
    #[test]
1236
    fn test_identifiers_starting_with_numbers() {
1237
        let mut r = Registry::new();
1238

1239
        assert!(r
1240
            .register_template_string("r1", "{{#if 0a}}true{{/if}}")
1241
            .is_ok());
1242
        let r1 = r.render("r1", &json!({"0a": true})).unwrap();
1243
        assert_eq!(r1, "true");
1244

1245
        assert!(r.register_template_string("r2", "{{eq 1a 1}}").is_ok());
1246
        let r2 = r.render("r2", &json!({"1a": 2, "a": 1})).unwrap();
1247
        assert_eq!(r2, "false");
1248

1249
        assert!(r
1250
            .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?
1251
            .is_ok());
1252
        let r3 = r
1253
            .render("r3", &json!({"0": true, "1a": true, "2_2": true}))
1254
            .unwrap();
1255
        assert_eq!(
1256
            r3,
1257
            "0: true \n1a: true resolved from context\n2_2: true resolved from context"
1258
        );
1259

1260
        // these should all be errors:
1261
        assert!(r.register_template_string("r4", "{{eq 1}}").is_ok());
1262
        assert!(r.register_template_string("r5", "{{eq a1}}").is_ok());
1263
        assert!(r.register_template_string("r6", "{{eq 1a}}").is_ok());
1264
        assert!(r.render("r4", &()).is_err());
1265
        assert!(r.render("r5", &()).is_err());
1266
        assert!(r.render("r6", &()).is_err());
1267
    }
1268
}
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