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::partial; |
16 | use crate::registry::Registry; |
17 | use crate::support; |
18 | use crate::template::TemplateElement::*; |
19 | use crate::template::{ |
20 | BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement, |
21 | TemplateMapping, |
22 | }; |
23 | |
24 | const HELPER_MISSING: &str = "helperMissing" ; |
25 | const BLOCK_HELPER_MISSING: &str = "blockHelperMissing" ; |
26 | |
27 | /// The context of a render call |
28 | /// |
29 | /// This context stores information of a render and a writer where generated |
30 | /// content is written to. |
31 | /// |
32 | #[derive (Clone, Debug)] |
33 | pub struct RenderContext<'reg, 'rc> { |
34 | inner: Rc<RenderContextInner<'reg, 'rc>>, |
35 | blocks: VecDeque<BlockContext<'reg>>, |
36 | // copy-on-write context |
37 | modified_context: Option<Rc<Context>>, |
38 | } |
39 | |
40 | #[derive (Clone)] |
41 | pub struct RenderContextInner<'reg: 'rc, 'rc> { |
42 | partials: BTreeMap<String, &'reg Template>, |
43 | partial_block_stack: VecDeque<&'reg Template>, |
44 | partial_block_depth: isize, |
45 | local_helpers: BTreeMap<String, Rc<dyn HelperDef + Send + Sync + 'rc>>, |
46 | /// current template name |
47 | current_template: Option<&'reg String>, |
48 | /// root template name |
49 | root_template: Option<&'reg String>, |
50 | disable_escape: bool, |
51 | indent_string: Option<&'reg String>, |
52 | } |
53 | |
54 | impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> { |
55 | /// Create a render context |
56 | pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> { |
57 | let inner = Rc::new(RenderContextInner { |
58 | partials: BTreeMap::new(), |
59 | partial_block_stack: VecDeque::new(), |
60 | partial_block_depth: 0, |
61 | local_helpers: BTreeMap::new(), |
62 | current_template: None, |
63 | root_template, |
64 | disable_escape: false, |
65 | indent_string: None, |
66 | }); |
67 | |
68 | let mut blocks = VecDeque::with_capacity(5); |
69 | blocks.push_front(BlockContext::new()); |
70 | |
71 | let modified_context = None; |
72 | RenderContext { |
73 | inner, |
74 | blocks, |
75 | modified_context, |
76 | } |
77 | } |
78 | |
79 | pub(crate) fn new_for_block(&self) -> RenderContext<'reg, 'rc> { |
80 | let inner = self.inner.clone(); |
81 | |
82 | let mut blocks = VecDeque::with_capacity(2); |
83 | blocks.push_front(BlockContext::new()); |
84 | |
85 | let modified_context = self.modified_context.clone(); |
86 | |
87 | RenderContext { |
88 | inner, |
89 | blocks, |
90 | modified_context, |
91 | } |
92 | } |
93 | |
94 | /// Push a block context into render context stack. This is typically |
95 | /// called when you entering a block scope. |
96 | pub fn push_block(&mut self, block: BlockContext<'reg>) { |
97 | self.blocks.push_front(block); |
98 | } |
99 | |
100 | /// Pop and drop current block context. |
101 | /// This is typically called when leaving a block scope. |
102 | pub fn pop_block(&mut self) { |
103 | self.blocks.pop_front(); |
104 | } |
105 | |
106 | pub(crate) fn clear_blocks(&mut self) { |
107 | self.blocks.clear(); |
108 | } |
109 | |
110 | /// Borrow a reference to current block context |
111 | pub fn block(&self) -> Option<&BlockContext<'reg>> { |
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<'reg>> { |
118 | self.blocks.front_mut() |
119 | } |
120 | |
121 | fn inner(&self) -> &RenderContextInner<'reg, 'rc> { |
122 | self.inner.borrow() |
123 | } |
124 | |
125 | fn inner_mut(&mut self) -> &mut RenderContextInner<'reg, 'rc> { |
126 | Rc::make_mut(&mut self.inner) |
127 | } |
128 | |
129 | /// Get the modified context data if any |
130 | pub fn context(&self) -> Option<Rc<Context>> { |
131 | self.modified_context.clone() |
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) { |
138 | self.modified_context = Some(Rc::new(ctx)) |
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( |
147 | &self, |
148 | context: &'rc Context, |
149 | relative_path: &str, |
150 | ) -> Result<ScopedJson<'reg, 'rc>, RenderError> { |
151 | let path = Path::parse(relative_path)?; |
152 | self.evaluate2(context, &path) |
153 | } |
154 | |
155 | pub(crate) fn evaluate2( |
156 | &self, |
157 | context: &'rc Context, |
158 | path: &Path, |
159 | ) -> Result<ScopedJson<'reg, 'rc>, RenderError> { |
160 | match path { |
161 | Path::Local((level, name, _)) => Ok(self |
162 | .get_local_var(*level, name) |
163 | .map(|v| ScopedJson::Derived(v.clone())) |
164 | .unwrap_or_else(|| ScopedJson::Missing)), |
165 | Path::Relative((segs, _)) => context.navigate(segs, &self.blocks), |
166 | } |
167 | } |
168 | |
169 | /// Get registered partial in this render context |
170 | pub fn get_partial(&self, name: &str) -> Option<&Template> { |
171 | if name == partial::PARTIAL_BLOCK { |
172 | return self |
173 | .inner() |
174 | .partial_block_stack |
175 | .get(self.inner().partial_block_depth as usize) |
176 | .copied(); |
177 | } |
178 | self.inner().partials.get(name).copied() |
179 | } |
180 | |
181 | /// Register a partial for this context |
182 | pub fn set_partial(&mut self, name: String, partial: &'reg Template) { |
183 | self.inner_mut().partials.insert(name, partial); |
184 | } |
185 | |
186 | pub(crate) fn push_partial_block(&mut self, partial: &'reg Template) { |
187 | self.inner_mut().partial_block_stack.push_front(partial); |
188 | } |
189 | |
190 | pub(crate) fn pop_partial_block(&mut self) { |
191 | self.inner_mut().partial_block_stack.pop_front(); |
192 | } |
193 | |
194 | pub(crate) fn inc_partial_block_depth(&mut self) { |
195 | self.inner_mut().partial_block_depth += 1; |
196 | } |
197 | |
198 | pub(crate) fn dec_partial_block_depth(&mut self) { |
199 | let depth = &mut self.inner_mut().partial_block_depth; |
200 | if *depth > 0 { |
201 | *depth -= 1; |
202 | } |
203 | } |
204 | |
205 | pub(crate) fn set_indent_string(&mut self, indent: Option<&'reg String>) { |
206 | self.inner_mut().indent_string = indent; |
207 | } |
208 | |
209 | #[inline ] |
210 | pub(crate) fn get_indent_string(&self) -> Option<&'reg String> { |
211 | self.inner.indent_string |
212 | } |
213 | |
214 | /// Remove a registered partial |
215 | pub fn remove_partial(&mut self, name: &str) { |
216 | self.inner_mut().partials.remove(name); |
217 | } |
218 | |
219 | fn get_local_var(&self, level: usize, name: &str) -> Option<&Json> { |
220 | self.blocks |
221 | .get(level) |
222 | .and_then(|blk| blk.get_local_var(name)) |
223 | } |
224 | |
225 | /// Test if given template name is current template. |
226 | pub fn is_current_template(&self, p: &str) -> bool { |
227 | self.inner() |
228 | .current_template |
229 | .map(|s| s == p) |
230 | .unwrap_or(false) |
231 | } |
232 | |
233 | /// Register a helper in this render context. |
234 | /// This is a feature provided by Decorator where you can create |
235 | /// temporary helpers. |
236 | pub fn register_local_helper( |
237 | &mut self, |
238 | name: &str, |
239 | def: Box<dyn HelperDef + Send + Sync + 'rc>, |
240 | ) { |
241 | self.inner_mut() |
242 | .local_helpers |
243 | .insert(name.to_string(), def.into()); |
244 | } |
245 | |
246 | /// Remove a helper from render context |
247 | pub fn unregister_local_helper(&mut self, name: &str) { |
248 | self.inner_mut().local_helpers.remove(name); |
249 | } |
250 | |
251 | /// Attempt to get a helper from current render context. |
252 | pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + Send + Sync + 'rc>> { |
253 | self.inner().local_helpers.get(name).cloned() |
254 | } |
255 | |
256 | #[inline ] |
257 | fn has_local_helper(&self, name: &str) -> bool { |
258 | self.inner.local_helpers.contains_key(name) |
259 | } |
260 | |
261 | /// Returns the current template name. |
262 | /// Note that the name can be vary from root template when you are rendering |
263 | /// from partials. |
264 | pub fn get_current_template_name(&self) -> Option<&'reg String> { |
265 | self.inner().current_template |
266 | } |
267 | |
268 | /// Set the current template name. |
269 | pub fn set_current_template_name(&mut self, name: Option<&'reg String>) { |
270 | self.inner_mut().current_template = name; |
271 | } |
272 | |
273 | /// Get root template name if any. |
274 | /// This is the template name that you call `render` from `Handlebars`. |
275 | pub fn get_root_template_name(&self) -> Option<&'reg String> { |
276 | self.inner().root_template |
277 | } |
278 | |
279 | /// Get the escape toggle |
280 | pub fn is_disable_escape(&self) -> bool { |
281 | self.inner().disable_escape |
282 | } |
283 | |
284 | /// Set the escape toggle. |
285 | /// When toggle is on, escape_fn will be called when rendering. |
286 | pub fn set_disable_escape(&mut self, disable: bool) { |
287 | self.inner_mut().disable_escape = disable |
288 | } |
289 | } |
290 | |
291 | impl<'reg, 'rc> fmt::Debug for RenderContextInner<'reg, 'rc> { |
292 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
293 | f&mut DebugStruct<'_, '_>.debug_struct("RenderContextInner" ) |
294 | .field("partials" , &self.partials) |
295 | .field("partial_block_stack" , &self.partial_block_stack) |
296 | .field("partial_block_depth" , &self.partial_block_depth) |
297 | .field("root_template" , &self.root_template) |
298 | .field("current_template" , &self.current_template) |
299 | .field(name:"disable_escape" , &self.disable_escape) |
300 | .finish() |
301 | } |
302 | } |
303 | |
304 | /// Render-time Helper data when using in a helper definition |
305 | #[derive (Debug)] |
306 | pub struct Helper<'reg, 'rc> { |
307 | name: Cow<'reg, str>, |
308 | params: Vec<PathAndJson<'reg, 'rc>>, |
309 | hash: BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>, |
310 | template: Option<&'reg Template>, |
311 | inverse: Option<&'reg Template>, |
312 | block_param: Option<&'reg BlockParam>, |
313 | block: bool, |
314 | } |
315 | |
316 | impl<'reg: 'rc, 'rc> Helper<'reg, 'rc> { |
317 | fn try_from_template( |
318 | ht: &'reg HelperTemplate, |
319 | registry: &'reg Registry<'reg>, |
320 | context: &'rc Context, |
321 | render_context: &mut RenderContext<'reg, 'rc>, |
322 | ) -> Result<Helper<'reg, 'rc>, RenderError> { |
323 | let name = ht.name.expand_as_name(registry, context, render_context)?; |
324 | let mut pv = Vec::with_capacity(ht.params.len()); |
325 | for p in &ht.params { |
326 | let r = p.expand(registry, context, render_context)?; |
327 | pv.push(r); |
328 | } |
329 | |
330 | let mut hm = BTreeMap::new(); |
331 | for (k, p) in &ht.hash { |
332 | let r = p.expand(registry, context, render_context)?; |
333 | hm.insert(k.as_ref(), r); |
334 | } |
335 | |
336 | Ok(Helper { |
337 | name, |
338 | params: pv, |
339 | hash: hm, |
340 | template: ht.template.as_ref(), |
341 | inverse: ht.inverse.as_ref(), |
342 | block_param: ht.block_param.as_ref(), |
343 | block: ht.block, |
344 | }) |
345 | } |
346 | |
347 | /// Returns helper name |
348 | pub fn name(&self) -> &str { |
349 | &self.name |
350 | } |
351 | |
352 | /// Returns all helper params, resolved within the context |
353 | pub fn params(&self) -> &Vec<PathAndJson<'reg, 'rc>> { |
354 | &self.params |
355 | } |
356 | |
357 | /// Returns nth helper param, resolved within the context. |
358 | /// |
359 | /// ## Example |
360 | /// |
361 | /// To get the first param in `{{my_helper abc}}` or `{{my_helper 2}}`, |
362 | /// use `h.param(0)` in helper definition. |
363 | /// Variable `abc` is auto resolved in current context. |
364 | /// |
365 | /// ``` |
366 | /// use handlebars::*; |
367 | /// |
368 | /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> { |
369 | /// let v = h.param(0).map(|v| v.value()) |
370 | /// .ok_or(RenderError::new("param not found" )); |
371 | /// // .. |
372 | /// Ok(()) |
373 | /// } |
374 | /// ``` |
375 | pub fn param(&self, idx: usize) -> Option<&PathAndJson<'reg, 'rc>> { |
376 | self.params.get(idx) |
377 | } |
378 | |
379 | /// Returns hash, resolved within the context |
380 | pub fn hash(&self) -> &BTreeMap<&'reg str, PathAndJson<'reg, 'rc>> { |
381 | &self.hash |
382 | } |
383 | |
384 | /// Return hash value of a given key, resolved within the context |
385 | /// |
386 | /// ## Example |
387 | /// |
388 | /// To get the first param in `{{my_helper v=abc}}` or `{{my_helper v=2}}`, |
389 | /// use `h.hash_get("v")` in helper definition. |
390 | /// Variable `abc` is auto resolved in current context. |
391 | /// |
392 | /// ``` |
393 | /// use handlebars::*; |
394 | /// |
395 | /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> { |
396 | /// let v = h.hash_get("v" ).map(|v| v.value()) |
397 | /// .ok_or(RenderError::new("param not found" )); |
398 | /// // .. |
399 | /// Ok(()) |
400 | /// } |
401 | /// ``` |
402 | pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'reg, 'rc>> { |
403 | self.hash.get(key) |
404 | } |
405 | |
406 | /// Returns the default inner template if the helper is a block helper. |
407 | /// |
408 | /// Typically you will render the template via: `template.render(registry, render_context)` |
409 | /// |
410 | pub fn template(&self) -> Option<&'reg Template> { |
411 | self.template |
412 | } |
413 | |
414 | /// Returns the template of `else` branch if any |
415 | pub fn inverse(&self) -> Option<&'reg Template> { |
416 | self.inverse |
417 | } |
418 | |
419 | /// Returns if the helper is a block one `{{#helper}}{{/helper}}` or not `{{helper 123}}` |
420 | pub fn is_block(&self) -> bool { |
421 | self.block |
422 | } |
423 | |
424 | /// Returns if the helper has either a block param or block param pair |
425 | pub fn has_block_param(&self) -> bool { |
426 | self.block_param.is_some() |
427 | } |
428 | |
429 | /// Returns block param if any |
430 | pub fn block_param(&self) -> Option<&'reg str> { |
431 | if let Some(&BlockParam::Single(Parameter::Name(ref s))) = self.block_param { |
432 | Some(s) |
433 | } else { |
434 | None |
435 | } |
436 | } |
437 | |
438 | /// Return block param pair (for example |key, val|) if any |
439 | pub fn block_param_pair(&self) -> Option<(&'reg str, &'reg str)> { |
440 | if let Some(&BlockParam::Pair((Parameter::Name(ref s1), Parameter::Name(ref s2)))) = |
441 | self.block_param |
442 | { |
443 | Some((s1, s2)) |
444 | } else { |
445 | None |
446 | } |
447 | } |
448 | } |
449 | |
450 | /// Render-time Decorator data when using in a decorator definition |
451 | #[derive (Debug)] |
452 | pub struct Decorator<'reg, 'rc> { |
453 | name: Cow<'reg, str>, |
454 | params: Vec<PathAndJson<'reg, 'rc>>, |
455 | hash: BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>, |
456 | template: Option<&'reg Template>, |
457 | indent: Option<&'reg String>, |
458 | } |
459 | |
460 | impl<'reg: 'rc, 'rc> Decorator<'reg, 'rc> { |
461 | fn try_from_template( |
462 | dt: &'reg DecoratorTemplate, |
463 | registry: &'reg Registry<'reg>, |
464 | context: &'rc Context, |
465 | render_context: &mut RenderContext<'reg, 'rc>, |
466 | ) -> Result<Decorator<'reg, 'rc>, RenderError> { |
467 | let name = dt.name.expand_as_name(registry, context, render_context)?; |
468 | |
469 | let mut pv = Vec::with_capacity(dt.params.len()); |
470 | for p in &dt.params { |
471 | let r = p.expand(registry, context, render_context)?; |
472 | pv.push(r); |
473 | } |
474 | |
475 | let mut hm = BTreeMap::new(); |
476 | for (k, p) in &dt.hash { |
477 | let r = p.expand(registry, context, render_context)?; |
478 | hm.insert(k.as_ref(), r); |
479 | } |
480 | |
481 | Ok(Decorator { |
482 | name, |
483 | params: pv, |
484 | hash: hm, |
485 | template: dt.template.as_ref(), |
486 | indent: dt.indent.as_ref(), |
487 | }) |
488 | } |
489 | |
490 | /// Returns helper name |
491 | pub fn name(&self) -> &str { |
492 | self.name.as_ref() |
493 | } |
494 | |
495 | /// Returns all helper params, resolved within the context |
496 | pub fn params(&self) -> &Vec<PathAndJson<'reg, 'rc>> { |
497 | &self.params |
498 | } |
499 | |
500 | /// Returns nth helper param, resolved within the context |
501 | pub fn param(&self, idx: usize) -> Option<&PathAndJson<'reg, 'rc>> { |
502 | self.params.get(idx) |
503 | } |
504 | |
505 | /// Returns hash, resolved within the context |
506 | pub fn hash(&self) -> &BTreeMap<&'reg str, PathAndJson<'reg, 'rc>> { |
507 | &self.hash |
508 | } |
509 | |
510 | /// Return hash value of a given key, resolved within the context |
511 | pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'reg, 'rc>> { |
512 | self.hash.get(key) |
513 | } |
514 | |
515 | /// Returns the default inner template if any |
516 | pub fn template(&self) -> Option<&'reg Template> { |
517 | self.template |
518 | } |
519 | |
520 | pub fn indent(&self) -> Option<&'reg String> { |
521 | self.indent |
522 | } |
523 | } |
524 | |
525 | /// Render trait |
526 | pub trait Renderable { |
527 | /// render into RenderContext's `writer` |
528 | fn render<'reg: 'rc, 'rc>( |
529 | &'reg self, |
530 | registry: &'reg Registry<'reg>, |
531 | context: &'rc Context, |
532 | rc: &mut RenderContext<'reg, 'rc>, |
533 | out: &mut dyn Output, |
534 | ) -> Result<(), RenderError>; |
535 | |
536 | /// render into string |
537 | fn renders<'reg: 'rc, 'rc>( |
538 | &'reg self, |
539 | registry: &'reg Registry<'reg>, |
540 | ctx: &'rc Context, |
541 | rc: &mut RenderContext<'reg, 'rc>, |
542 | ) -> Result<String, RenderError> { |
543 | let mut so: StringOutput = StringOutput::new(); |
544 | self.render(registry, context:ctx, rc, &mut so)?; |
545 | so.into_string().map_err(op:RenderError::from) |
546 | } |
547 | } |
548 | |
549 | /// Evaluate decorator |
550 | pub trait Evaluable { |
551 | fn eval<'reg: 'rc, 'rc>( |
552 | &'reg self, |
553 | registry: &'reg Registry<'reg>, |
554 | context: &'rc Context, |
555 | rc: &mut RenderContext<'reg, 'rc>, |
556 | ) -> Result<(), RenderError>; |
557 | } |
558 | |
559 | #[inline ] |
560 | fn call_helper_for_value<'reg: 'rc, 'rc>( |
561 | hd: &dyn HelperDef, |
562 | ht: &Helper<'reg, 'rc>, |
563 | r: &'reg Registry<'reg>, |
564 | ctx: &'rc Context, |
565 | rc: &mut RenderContext<'reg, 'rc>, |
566 | ) -> Result<PathAndJson<'reg, 'rc>, RenderError> { |
567 | match hd.call_inner(ht, r, ctx, rc) { |
568 | Ok(result) => Ok(PathAndJson::new(None, result)), |
569 | Err(e) => { |
570 | if e.is_unimplemented() { |
571 | // parse value from output |
572 | let mut so = StringOutput::new(); |
573 | |
574 | // here we don't want subexpression result escaped, |
575 | // so we temporarily disable it |
576 | let disable_escape = rc.is_disable_escape(); |
577 | rc.set_disable_escape(true); |
578 | |
579 | hd.call(ht, r, ctx, rc, &mut so)?; |
580 | rc.set_disable_escape(disable_escape); |
581 | |
582 | let string = so.into_string().map_err(RenderError::from)?; |
583 | Ok(PathAndJson::new( |
584 | None, |
585 | ScopedJson::Derived(Json::String(string)), |
586 | )) |
587 | } else { |
588 | Err(e) |
589 | } |
590 | } |
591 | } |
592 | } |
593 | |
594 | impl Parameter { |
595 | pub fn expand_as_name<'reg: 'rc, 'rc>( |
596 | &'reg self, |
597 | registry: &'reg Registry<'reg>, |
598 | ctx: &'rc Context, |
599 | rc: &mut RenderContext<'reg, 'rc>, |
600 | ) -> Result<Cow<'reg, str>, RenderError> { |
601 | match self { |
602 | Parameter::Name(ref name) => Ok(Cow::Borrowed(name)), |
603 | Parameter::Path(ref p) => Ok(Cow::Borrowed(p.raw())), |
604 | Parameter::Subexpression(_) => self |
605 | .expand(registry, ctx, rc) |
606 | .map(|v| v.value().render()) |
607 | .map(Cow::Owned), |
608 | Parameter::Literal(ref j) => Ok(Cow::Owned(j.render())), |
609 | } |
610 | } |
611 | |
612 | pub fn expand<'reg: 'rc, 'rc>( |
613 | &'reg self, |
614 | registry: &'reg Registry<'reg>, |
615 | ctx: &'rc Context, |
616 | rc: &mut RenderContext<'reg, 'rc>, |
617 | ) -> Result<PathAndJson<'reg, 'rc>, RenderError> { |
618 | match self { |
619 | Parameter::Name(ref name) => { |
620 | // FIXME: raise error when expanding with name? |
621 | Ok(PathAndJson::new(Some(name.to_owned()), ScopedJson::Missing)) |
622 | } |
623 | Parameter::Path(ref path) => { |
624 | if let Some(rc_context) = rc.context() { |
625 | let result = rc.evaluate2(rc_context.borrow(), path)?; |
626 | Ok(PathAndJson::new( |
627 | Some(path.raw().to_owned()), |
628 | ScopedJson::Derived(result.as_json().clone()), |
629 | )) |
630 | } else { |
631 | let result = rc.evaluate2(ctx, path)?; |
632 | Ok(PathAndJson::new(Some(path.raw().to_owned()), result)) |
633 | } |
634 | } |
635 | Parameter::Literal(ref j) => Ok(PathAndJson::new(None, ScopedJson::Constant(j))), |
636 | Parameter::Subexpression(ref t) => match *t.as_element() { |
637 | Expression(ref ht) => { |
638 | let name = ht.name.expand_as_name(registry, ctx, rc)?; |
639 | |
640 | let h = Helper::try_from_template(ht, registry, ctx, rc)?; |
641 | if let Some(ref d) = rc.get_local_helper(&name) { |
642 | call_helper_for_value(d.as_ref(), &h, registry, ctx, rc) |
643 | } else { |
644 | let mut helper = registry.get_or_load_helper(&name)?; |
645 | |
646 | if helper.is_none() { |
647 | helper = registry.get_or_load_helper(if ht.block { |
648 | BLOCK_HELPER_MISSING |
649 | } else { |
650 | HELPER_MISSING |
651 | })?; |
652 | } |
653 | |
654 | helper |
655 | .ok_or_else(|| { |
656 | RenderError::new(format!("Helper not defined: {:?}" , ht.name)) |
657 | }) |
658 | .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)) |
659 | } |
660 | } |
661 | _ => unreachable!(), |
662 | }, |
663 | } |
664 | } |
665 | } |
666 | |
667 | impl Renderable for Template { |
668 | fn render<'reg: 'rc, 'rc>( |
669 | &'reg self, |
670 | registry: &'reg Registry<'reg>, |
671 | ctx: &'rc Context, |
672 | rc: &mut RenderContext<'reg, 'rc>, |
673 | out: &mut dyn Output, |
674 | ) -> Result<(), RenderError> { |
675 | rc.set_current_template_name(self.name.as_ref()); |
676 | let iter = self.elements.iter(); |
677 | |
678 | for (idx, t) in iter.enumerate() { |
679 | t.render(registry, ctx, rc, out).map_err(|mut e| { |
680 | // add line/col number if the template has mapping data |
681 | if e.line_no.is_none() { |
682 | if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) { |
683 | e.line_no = Some(line); |
684 | e.column_no = Some(col); |
685 | } |
686 | } |
687 | |
688 | if e.template_name.is_none() { |
689 | e.template_name = self.name.clone(); |
690 | } |
691 | |
692 | e |
693 | })?; |
694 | } |
695 | Ok(()) |
696 | } |
697 | } |
698 | |
699 | impl Evaluable for Template { |
700 | fn eval<'reg: 'rc, 'rc>( |
701 | &'reg self, |
702 | registry: &'reg Registry<'reg>, |
703 | ctx: &'rc Context, |
704 | rc: &mut RenderContext<'reg, 'rc>, |
705 | ) -> Result<(), RenderError> { |
706 | let iter = self.elements.iter(); |
707 | |
708 | for (idx, t) in iter.enumerate() { |
709 | t.eval(registry, ctx, rc).map_err(|mut e| { |
710 | if e.line_no.is_none() { |
711 | if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) { |
712 | e.line_no = Some(line); |
713 | e.column_no = Some(col); |
714 | } |
715 | } |
716 | |
717 | e.template_name = self.name.clone(); |
718 | e |
719 | })?; |
720 | } |
721 | Ok(()) |
722 | } |
723 | } |
724 | |
725 | fn helper_exists<'reg: 'rc, 'rc>( |
726 | name: &str, |
727 | reg: &Registry<'reg>, |
728 | rc: &RenderContext<'reg, 'rc>, |
729 | ) -> bool { |
730 | rc.has_local_helper(name) || reg.has_helper(name) |
731 | } |
732 | |
733 | #[inline ] |
734 | fn render_helper<'reg: 'rc, 'rc>( |
735 | ht: &'reg HelperTemplate, |
736 | registry: &'reg Registry<'reg>, |
737 | ctx: &'rc Context, |
738 | rc: &mut RenderContext<'reg, 'rc>, |
739 | out: &mut dyn Output, |
740 | ) -> Result<(), RenderError> { |
741 | let h = Helper::try_from_template(ht, registry, ctx, rc)?; |
742 | debug!( |
743 | "Rendering helper: {:?}, params: {:?}, hash: {:?}" , |
744 | h.name(), |
745 | h.params(), |
746 | h.hash() |
747 | ); |
748 | if let Some(ref d) = rc.get_local_helper(h.name()) { |
749 | d.call(&h, registry, ctx, rc, out) |
750 | } else { |
751 | let mut helper = registry.get_or_load_helper(h.name())?; |
752 | |
753 | if helper.is_none() { |
754 | helper = registry.get_or_load_helper(if ht.block { |
755 | BLOCK_HELPER_MISSING |
756 | } else { |
757 | HELPER_MISSING |
758 | })?; |
759 | } |
760 | |
761 | helper |
762 | .ok_or_else(|| RenderError::new(format!("Helper not defined: {:?}" , h.name()))) |
763 | .and_then(|d| d.call(&h, registry, ctx, rc, out)) |
764 | } |
765 | } |
766 | |
767 | pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: String) -> String { |
768 | if !rc.is_disable_escape() { |
769 | r.get_escape_fn()(&content) |
770 | } else { |
771 | content |
772 | } |
773 | } |
774 | |
775 | #[inline ] |
776 | fn indent_aware_write( |
777 | v: &str, |
778 | rc: &mut RenderContext<'_, '_>, |
779 | out: &mut dyn Output, |
780 | ) -> Result<(), RenderError> { |
781 | if let Some(indent: &String) = rc.get_indent_string() { |
782 | out.write(seg:support::str::with_indent(s:v, indent).as_ref())?; |
783 | } else { |
784 | out.write(seg:v.as_ref())?; |
785 | } |
786 | Ok(()) |
787 | } |
788 | |
789 | impl Renderable for TemplateElement { |
790 | fn render<'reg: 'rc, 'rc>( |
791 | &'reg self, |
792 | registry: &'reg Registry<'reg>, |
793 | ctx: &'rc Context, |
794 | rc: &mut RenderContext<'reg, 'rc>, |
795 | out: &mut dyn Output, |
796 | ) -> Result<(), RenderError> { |
797 | match self { |
798 | RawString(ref v) => indent_aware_write(v.as_ref(), rc, out), |
799 | Expression(ref ht) | HtmlExpression(ref ht) => { |
800 | let is_html_expression = matches!(self, HtmlExpression(_)); |
801 | if is_html_expression { |
802 | rc.set_disable_escape(true); |
803 | } |
804 | |
805 | // test if the expression is to render some value |
806 | let result = if ht.is_name_only() { |
807 | let helper_name = ht.name.expand_as_name(registry, ctx, rc)?; |
808 | if helper_exists(&helper_name, registry, rc) { |
809 | render_helper(ht, registry, ctx, rc, out) |
810 | } else { |
811 | debug!("Rendering value: {:?}" , ht.name); |
812 | let context_json = ht.name.expand(registry, ctx, rc)?; |
813 | if context_json.is_value_missing() { |
814 | if registry.strict_mode() { |
815 | Err(RenderError::strict_error(context_json.relative_path())) |
816 | } else { |
817 | // helper missing |
818 | if let Some(hook) = registry.get_or_load_helper(HELPER_MISSING)? { |
819 | let h = Helper::try_from_template(ht, registry, ctx, rc)?; |
820 | hook.call(&h, registry, ctx, rc, out) |
821 | } else { |
822 | Ok(()) |
823 | } |
824 | } |
825 | } else { |
826 | let rendered = context_json.value().render(); |
827 | let output = do_escape(registry, rc, rendered); |
828 | indent_aware_write(output.as_ref(), rc, out) |
829 | } |
830 | } |
831 | } else { |
832 | // this is a helper expression |
833 | render_helper(ht, registry, ctx, rc, out) |
834 | }; |
835 | |
836 | if is_html_expression { |
837 | rc.set_disable_escape(false); |
838 | } |
839 | |
840 | result |
841 | } |
842 | HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out), |
843 | DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc), |
844 | PartialExpression(ref dt) | PartialBlock(ref dt) => { |
845 | let di = Decorator::try_from_template(dt, registry, ctx, rc)?; |
846 | |
847 | partial::expand_partial(&di, registry, ctx, rc, out) |
848 | } |
849 | _ => Ok(()), |
850 | } |
851 | } |
852 | } |
853 | |
854 | impl Evaluable for TemplateElement { |
855 | fn eval<'reg: 'rc, 'rc>( |
856 | &'reg self, |
857 | registry: &'reg Registry<'reg>, |
858 | ctx: &'rc Context, |
859 | rc: &mut RenderContext<'reg, 'rc>, |
860 | ) -> Result<(), RenderError> { |
861 | match *self { |
862 | DecoratorExpression(ref dt: &Box) | DecoratorBlock(ref dt: &Box) => { |
863 | let di: Decorator<'_, '_> = Decorator::try_from_template(dt, registry, context:ctx, render_context:rc)?; |
864 | match registry.get_decorator(di.name()) { |
865 | Some(d: &(dyn DecoratorDef + Sync + Send)) => d.call(&di, r:registry, ctx, rc), |
866 | None => Err(RenderError::new(desc:format!( |
867 | "Decorator not defined: {:?}" , |
868 | dt.name |
869 | ))), |
870 | } |
871 | } |
872 | _ => Ok(()), |
873 | } |
874 | } |
875 | } |
876 | |
877 | #[cfg (test)] |
878 | mod test { |
879 | use std::collections::BTreeMap; |
880 | |
881 | use super::{Helper, RenderContext, Renderable}; |
882 | use crate::block::BlockContext; |
883 | use crate::context::Context; |
884 | use crate::error::RenderError; |
885 | use crate::json::path::Path; |
886 | use crate::json::value::JsonRender; |
887 | use crate::output::{Output, StringOutput}; |
888 | use crate::registry::Registry; |
889 | use crate::template::TemplateElement::*; |
890 | use crate::template::{HelperTemplate, Template, TemplateElement}; |
891 | |
892 | #[test ] |
893 | fn test_raw_string() { |
894 | let r = Registry::new(); |
895 | let raw_string = RawString("<h1>hello world</h1>" .to_string()); |
896 | |
897 | let mut out = StringOutput::new(); |
898 | let ctx = Context::null(); |
899 | { |
900 | let mut rc = RenderContext::new(None); |
901 | raw_string.render(&r, &ctx, &mut rc, &mut out).ok().unwrap(); |
902 | } |
903 | assert_eq!( |
904 | out.into_string().unwrap(), |
905 | "<h1>hello world</h1>" .to_string() |
906 | ); |
907 | } |
908 | |
909 | #[test ] |
910 | fn test_expression() { |
911 | let r = Registry::new(); |
912 | let element = Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths( |
913 | &["hello" ], |
914 | )))); |
915 | |
916 | let mut out = StringOutput::new(); |
917 | let mut m: BTreeMap<String, String> = BTreeMap::new(); |
918 | let value = "<p></p>" .to_string(); |
919 | m.insert("hello" .to_string(), value); |
920 | let ctx = Context::wraps(&m).unwrap(); |
921 | { |
922 | let mut rc = RenderContext::new(None); |
923 | element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap(); |
924 | } |
925 | |
926 | assert_eq!( |
927 | out.into_string().unwrap(), |
928 | "<p></p>" .to_string() |
929 | ); |
930 | } |
931 | |
932 | #[test ] |
933 | fn test_html_expression() { |
934 | let r = Registry::new(); |
935 | let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths( |
936 | &["hello" ], |
937 | )))); |
938 | |
939 | let mut out = StringOutput::new(); |
940 | let mut m: BTreeMap<String, String> = BTreeMap::new(); |
941 | let value = "world" ; |
942 | m.insert("hello" .to_string(), value.to_string()); |
943 | let ctx = Context::wraps(&m).unwrap(); |
944 | { |
945 | let mut rc = RenderContext::new(None); |
946 | element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap(); |
947 | } |
948 | |
949 | assert_eq!(out.into_string().unwrap(), value.to_string()); |
950 | } |
951 | |
952 | #[test ] |
953 | fn test_template() { |
954 | let r = Registry::new(); |
955 | let mut out = StringOutput::new(); |
956 | let mut m: BTreeMap<String, String> = BTreeMap::new(); |
957 | let value = "world" .to_string(); |
958 | m.insert("hello" .to_string(), value); |
959 | let ctx = Context::wraps(&m).unwrap(); |
960 | |
961 | let elements: Vec<TemplateElement> = vec![ |
962 | RawString("<h1>" .to_string()), |
963 | Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths( |
964 | &["hello" ], |
965 | )))), |
966 | RawString("</h1>" .to_string()), |
967 | Comment("" .to_string()), |
968 | ]; |
969 | |
970 | let template = Template { |
971 | elements, |
972 | name: None, |
973 | mapping: Vec::new(), |
974 | }; |
975 | |
976 | { |
977 | let mut rc = RenderContext::new(None); |
978 | template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap(); |
979 | } |
980 | |
981 | assert_eq!(out.into_string().unwrap(), "<h1>world</h1>" .to_string()); |
982 | } |
983 | |
984 | #[test ] |
985 | fn test_render_context_promotion_and_demotion() { |
986 | use crate::json::value::to_json; |
987 | let mut render_context = RenderContext::new(None); |
988 | let mut block = BlockContext::new(); |
989 | |
990 | block.set_local_var("index" , to_json(0)); |
991 | render_context.push_block(block); |
992 | |
993 | render_context.push_block(BlockContext::new()); |
994 | assert_eq!( |
995 | render_context.get_local_var(1, "index" ).unwrap(), |
996 | &to_json(0) |
997 | ); |
998 | |
999 | render_context.pop_block(); |
1000 | |
1001 | assert_eq!( |
1002 | render_context.get_local_var(0, "index" ).unwrap(), |
1003 | &to_json(0) |
1004 | ); |
1005 | } |
1006 | |
1007 | #[test ] |
1008 | fn test_render_subexpression_issue_115() { |
1009 | use crate::support::str::StringWriter; |
1010 | |
1011 | let mut r = Registry::new(); |
1012 | r.register_helper( |
1013 | "format" , |
1014 | Box::new( |
1015 | |h: &Helper<'_, '_>, |
1016 | _: &Registry<'_>, |
1017 | _: &Context, |
1018 | _: &mut RenderContext<'_, '_>, |
1019 | out: &mut dyn Output| |
1020 | -> Result<(), RenderError> { |
1021 | out.write(&h.param(0).unwrap().value().render()) |
1022 | .map(|_| ()) |
1023 | .map_err(RenderError::from) |
1024 | }, |
1025 | ), |
1026 | ); |
1027 | |
1028 | let mut sw = StringWriter::new(); |
1029 | let mut m: BTreeMap<String, String> = BTreeMap::new(); |
1030 | m.insert("a" .to_string(), "123" .to_string()); |
1031 | |
1032 | { |
1033 | if let Err(e) = r.render_template_to_write("{{format (format a)}}" , &m, &mut sw) { |
1034 | panic!("{}" , e); |
1035 | } |
1036 | } |
1037 | |
1038 | assert_eq!(sw.into_string(), "123" .to_string()); |
1039 | } |
1040 | |
1041 | #[test ] |
1042 | fn test_render_error_line_no() { |
1043 | let mut r = Registry::new(); |
1044 | let m: BTreeMap<String, String> = BTreeMap::new(); |
1045 | |
1046 | let name = "invalid_template" ; |
1047 | assert!(r |
1048 | .register_template_string(name, "<h1> \n{{#if true}} \n {{#each}}{{/each}} \n{{/if}}" ) |
1049 | .is_ok()); |
1050 | |
1051 | if let Err(e) = r.render(name, &m) { |
1052 | assert_eq!(e.line_no.unwrap(), 3); |
1053 | assert_eq!(e.column_no.unwrap(), 3); |
1054 | assert_eq!(e.template_name, Some(name.to_owned())); |
1055 | } else { |
1056 | panic!("Error expected" ); |
1057 | } |
1058 | } |
1059 | |
1060 | #[test ] |
1061 | fn test_partial_failback_render() { |
1062 | let mut r = Registry::new(); |
1063 | |
1064 | assert!(r |
1065 | .register_template_string("parent" , "<html>{{> layout}}</html>" ) |
1066 | .is_ok()); |
1067 | assert!(r |
1068 | .register_template_string( |
1069 | "child" , |
1070 | "{{#*inline \"layout \"}}content{{/inline}}{{#> parent}}{{> seg}}{{/parent}}" |
1071 | ) |
1072 | .is_ok()); |
1073 | assert!(r.register_template_string("seg" , "1234" ).is_ok()); |
1074 | |
1075 | let r = r.render("child" , &true).expect("should work" ); |
1076 | assert_eq!(r, "<html>content</html>" ); |
1077 | } |
1078 | |
1079 | #[test ] |
1080 | fn test_key_with_slash() { |
1081 | let mut r = Registry::new(); |
1082 | |
1083 | assert!(r |
1084 | .register_template_string("t" , "{{#each this}}{{@key}}: {{this}} \n{{/each}}" ) |
1085 | .is_ok()); |
1086 | |
1087 | let r = r.render("t" , &json!({"/foo" : "bar" })).unwrap(); |
1088 | |
1089 | assert_eq!(r, "/foo: bar \n" ); |
1090 | } |
1091 | |
1092 | #[test ] |
1093 | fn test_comment() { |
1094 | let r = Registry::new(); |
1095 | |
1096 | assert_eq!( |
1097 | r.render_template("Hello {{this}} {{! test me }}" , &0) |
1098 | .unwrap(), |
1099 | "Hello 0 " |
1100 | ); |
1101 | } |
1102 | |
1103 | #[test ] |
1104 | fn test_zero_args_heler() { |
1105 | let mut r = Registry::new(); |
1106 | |
1107 | r.register_helper( |
1108 | "name" , |
1109 | Box::new( |
1110 | |_: &Helper<'_, '_>, |
1111 | _: &Registry<'_>, |
1112 | _: &Context, |
1113 | _: &mut RenderContext<'_, '_>, |
1114 | out: &mut dyn Output| |
1115 | -> Result<(), RenderError> { |
1116 | out.write("N/A" ).map_err(Into::into) |
1117 | }, |
1118 | ), |
1119 | ); |
1120 | |
1121 | r.register_template_string("t0" , "Output name: {{name}}" ) |
1122 | .unwrap(); |
1123 | r.register_template_string("t1" , "Output name: {{first_name}}" ) |
1124 | .unwrap(); |
1125 | r.register_template_string("t2" , "Output name: {{./name}}" ) |
1126 | .unwrap(); |
1127 | |
1128 | // when "name" is available in context, use context first |
1129 | assert_eq!( |
1130 | r.render("t0" , &json!({"name" : "Alex" })).unwrap(), |
1131 | "Output name: N/A" |
1132 | ); |
1133 | |
1134 | // when "name" is unavailable, call helper with same name |
1135 | assert_eq!( |
1136 | r.render("t2" , &json!({"name" : "Alex" })).unwrap(), |
1137 | "Output name: Alex" |
1138 | ); |
1139 | |
1140 | // output nothing when neither context nor helper available |
1141 | assert_eq!( |
1142 | r.render("t1" , &json!({"name" : "Alex" })).unwrap(), |
1143 | "Output name: " |
1144 | ); |
1145 | |
1146 | // generate error in strict mode for above case |
1147 | r.set_strict_mode(true); |
1148 | assert!(r.render("t1" , &json!({"name" : "Alex" })).is_err()); |
1149 | |
1150 | // output nothing when helperMissing was defined |
1151 | r.set_strict_mode(false); |
1152 | r.register_helper( |
1153 | "helperMissing" , |
1154 | Box::new( |
1155 | |h: &Helper<'_, '_>, |
1156 | _: &Registry<'_>, |
1157 | _: &Context, |
1158 | _: &mut RenderContext<'_, '_>, |
1159 | out: &mut dyn Output| |
1160 | -> Result<(), RenderError> { |
1161 | let name = h.name(); |
1162 | write!(out, " {} not resolved" , name)?; |
1163 | Ok(()) |
1164 | }, |
1165 | ), |
1166 | ); |
1167 | assert_eq!( |
1168 | r.render("t1" , &json!({"name" : "Alex" })).unwrap(), |
1169 | "Output name: first_name not resolved" |
1170 | ); |
1171 | } |
1172 | |
1173 | #[test ] |
1174 | fn test_identifiers_starting_with_numbers() { |
1175 | let mut r = Registry::new(); |
1176 | |
1177 | assert!(r |
1178 | .register_template_string("r1" , "{{#if 0a}}true{{/if}}" ) |
1179 | .is_ok()); |
1180 | let r1 = r.render("r1" , &json!({"0a" : true})).unwrap(); |
1181 | assert_eq!(r1, "true" ); |
1182 | |
1183 | assert!(r.register_template_string("r2" , "{{eq 1a 1}}" ).is_ok()); |
1184 | let r2 = r.render("r2" , &json!({"1a" : 2, "a" : 1})).unwrap(); |
1185 | assert_eq!(r2, "false" ); |
1186 | |
1187 | assert!(r |
1188 | .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? |
1189 | .is_ok()); |
1190 | let r3 = r |
1191 | .render("r3" , &json!({"0" : true, "1a" : true, "2_2" : true})) |
1192 | .unwrap(); |
1193 | assert_eq!( |
1194 | r3, |
1195 | "0: true \n1a: true resolved from context \n2_2: true resolved from context" |
1196 | ); |
1197 | |
1198 | // these should all be errors: |
1199 | assert!(r.register_template_string("r4" , "{{eq 1}}" ).is_ok()); |
1200 | assert!(r.register_template_string("r5" , "{{eq a1}}" ).is_ok()); |
1201 | assert!(r.register_template_string("r6" , "{{eq 1a}}" ).is_ok()); |
1202 | assert!(r.render("r4" , &()).is_err()); |
1203 | assert!(r.render("r5" , &()).is_err()); |
1204 | assert!(r.render("r6" , &()).is_err()); |
1205 | } |
1206 | } |
1207 | |