summaryrefslogtreecommitdiffstats
path: root/vendor/handlebars/src/render.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/handlebars/src/render.rs')
-rw-r--r--vendor/handlebars/src/render.rs1119
1 files changed, 1119 insertions, 0 deletions
diff --git a/vendor/handlebars/src/render.rs b/vendor/handlebars/src/render.rs
new file mode 100644
index 000000000..188ea221a
--- /dev/null
+++ b/vendor/handlebars/src/render.rs
@@ -0,0 +1,1119 @@
+use std::borrow::{Borrow, Cow};
+use std::collections::{BTreeMap, VecDeque};
+use std::fmt;
+use std::rc::Rc;
+
+use serde_json::value::Value as Json;
+
+use crate::block::BlockContext;
+use crate::context::Context;
+use crate::error::RenderError;
+use crate::helpers::HelperDef;
+use crate::json::path::Path;
+use crate::json::value::{JsonRender, PathAndJson, ScopedJson};
+use crate::output::{Output, StringOutput};
+use crate::partial;
+use crate::registry::Registry;
+use crate::template::TemplateElement::*;
+use crate::template::{
+ BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
+ TemplateMapping,
+};
+
+const HELPER_MISSING: &str = "helperMissing";
+const BLOCK_HELPER_MISSING: &str = "blockHelperMissing";
+
+/// The context of a render call
+///
+/// This context stores information of a render and a writer where generated
+/// content is written to.
+///
+#[derive(Clone, Debug)]
+pub struct RenderContext<'reg, 'rc> {
+ inner: Rc<RenderContextInner<'reg, 'rc>>,
+ blocks: VecDeque<BlockContext<'reg>>,
+ // copy-on-write context
+ modified_context: Option<Rc<Context>>,
+}
+
+#[derive(Clone)]
+pub struct RenderContextInner<'reg: 'rc, 'rc> {
+ partials: BTreeMap<String, &'reg Template>,
+ partial_block_stack: VecDeque<&'reg Template>,
+ partial_block_depth: isize,
+ local_helpers: BTreeMap<String, Rc<dyn HelperDef + Send + Sync + 'rc>>,
+ /// current template name
+ current_template: Option<&'reg String>,
+ /// root template name
+ root_template: Option<&'reg String>,
+ disable_escape: bool,
+}
+
+impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
+ /// Create a render context from a `Write`
+ pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> {
+ let inner = Rc::new(RenderContextInner {
+ partials: BTreeMap::new(),
+ partial_block_stack: VecDeque::new(),
+ partial_block_depth: 0,
+ local_helpers: BTreeMap::new(),
+ current_template: None,
+ root_template,
+ disable_escape: false,
+ });
+
+ let mut blocks = VecDeque::with_capacity(5);
+ blocks.push_front(BlockContext::new());
+
+ let modified_context = None;
+ RenderContext {
+ inner,
+ blocks,
+ modified_context,
+ }
+ }
+
+ // TODO: better name
+ pub(crate) fn new_for_block(&self) -> RenderContext<'reg, 'rc> {
+ let inner = self.inner.clone();
+
+ let mut blocks = VecDeque::with_capacity(2);
+ blocks.push_front(BlockContext::new());
+
+ let modified_context = self.modified_context.clone();
+
+ RenderContext {
+ inner,
+ blocks,
+ modified_context,
+ }
+ }
+
+ /// Push a block context into render context stack. This is typically
+ /// called when you entering a block scope.
+ pub fn push_block(&mut self, block: BlockContext<'reg>) {
+ self.blocks.push_front(block);
+ }
+
+ /// Pop and drop current block context.
+ /// This is typically called when leaving a block scope.
+ pub fn pop_block(&mut self) {
+ self.blocks.pop_front();
+ }
+
+ /// Borrow a reference to current block context
+ pub fn block(&self) -> Option<&BlockContext<'reg>> {
+ self.blocks.front()
+ }
+
+ /// Borrow a mutable reference to current block context in order to
+ /// modify some data.
+ pub fn block_mut(&mut self) -> Option<&mut BlockContext<'reg>> {
+ self.blocks.front_mut()
+ }
+
+ fn inner(&self) -> &RenderContextInner<'reg, 'rc> {
+ self.inner.borrow()
+ }
+
+ fn inner_mut(&mut self) -> &mut RenderContextInner<'reg, 'rc> {
+ Rc::make_mut(&mut self.inner)
+ }
+
+ /// Get the modified context data if any
+ pub fn context(&self) -> Option<Rc<Context>> {
+ self.modified_context.clone()
+ }
+
+ /// Set new context data into the render process.
+ /// This is typically called in decorators where user can modify
+ /// the data they were rendering.
+ pub fn set_context(&mut self, ctx: Context) {
+ self.modified_context = Some(Rc::new(ctx))
+ }
+
+ /// Evaluate a Json path in current scope.
+ ///
+ /// Typically you don't need to evaluate it by yourself.
+ /// The Helper and Decorator API will provide your evaluated value of
+ /// their parameters and hash data.
+ pub fn evaluate(
+ &self,
+ context: &'rc Context,
+ relative_path: &str,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ let path = Path::parse(relative_path)?;
+ self.evaluate2(context, &path)
+ }
+
+ pub(crate) fn evaluate2(
+ &self,
+ context: &'rc Context,
+ path: &Path,
+ ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
+ match path {
+ Path::Local((level, name, _)) => Ok(self
+ .get_local_var(*level, name)
+ .map(|v| ScopedJson::Derived(v.clone()))
+ .unwrap_or_else(|| ScopedJson::Missing)),
+ Path::Relative((segs, _)) => context.navigate(segs, &self.blocks),
+ }
+ }
+
+ /// Get registered partial in this render context
+ pub fn get_partial(&self, name: &str) -> Option<&Template> {
+ if name == partial::PARTIAL_BLOCK {
+ return self
+ .inner()
+ .partial_block_stack
+ .get(self.inner().partial_block_depth as usize)
+ .copied();
+ }
+ self.inner().partials.get(name).copied()
+ }
+
+ /// Register a partial for this context
+ pub fn set_partial(&mut self, name: String, partial: &'reg Template) {
+ self.inner_mut().partials.insert(name, partial);
+ }
+
+ pub(crate) fn push_partial_block(&mut self, partial: &'reg Template) {
+ self.inner_mut().partial_block_stack.push_front(partial);
+ }
+
+ pub(crate) fn pop_partial_block(&mut self) {
+ self.inner_mut().partial_block_stack.pop_front();
+ }
+
+ pub(crate) fn inc_partial_block_depth(&mut self) {
+ self.inner_mut().partial_block_depth += 1;
+ }
+
+ pub(crate) fn dec_partial_block_depth(&mut self) {
+ self.inner_mut().partial_block_depth -= 1;
+ }
+
+ /// Remove a registered partial
+ pub fn remove_partial(&mut self, name: &str) {
+ self.inner_mut().partials.remove(name);
+ }
+
+ fn get_local_var(&self, level: usize, name: &str) -> Option<&Json> {
+ self.blocks
+ .get(level)
+ .and_then(|blk| blk.get_local_var(&name))
+ }
+
+ /// Test if given template name is current template.
+ pub fn is_current_template(&self, p: &str) -> bool {
+ self.inner()
+ .current_template
+ .map(|s| s == p)
+ .unwrap_or(false)
+ }
+
+ /// Register a helper in this render context.
+ /// This is a feature provided by Decorator where you can create
+ /// temporary helpers.
+ pub fn register_local_helper(
+ &mut self,
+ name: &str,
+ def: Box<dyn HelperDef + Send + Sync + 'rc>,
+ ) {
+ self.inner_mut()
+ .local_helpers
+ .insert(name.to_string(), def.into());
+ }
+
+ /// Remove a helper from render context
+ pub fn unregister_local_helper(&mut self, name: &str) {
+ self.inner_mut().local_helpers.remove(name);
+ }
+
+ /// Attempt to get a helper from current render context.
+ pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + Send + Sync + 'rc>> {
+ self.inner().local_helpers.get(name).cloned()
+ }
+
+ #[inline]
+ fn has_local_helper(&self, name: &str) -> bool {
+ self.inner.local_helpers.contains_key(name)
+ }
+
+ /// Returns the current template name.
+ /// Note that the name can be vary from root template when you are rendering
+ /// from partials.
+ pub fn get_current_template_name(&self) -> Option<&'reg String> {
+ self.inner().current_template
+ }
+
+ /// Set the current template name.
+ pub fn set_current_template_name(&mut self, name: Option<&'reg String>) {
+ self.inner_mut().current_template = name;
+ }
+
+ /// Get root template name if any.
+ /// This is the template name that you call `render` from `Handlebars`.
+ pub fn get_root_template_name(&self) -> Option<&'reg String> {
+ self.inner().root_template
+ }
+
+ /// Get the escape toggle
+ pub fn is_disable_escape(&self) -> bool {
+ self.inner().disable_escape
+ }
+
+ /// Set the escape toggle.
+ /// When toggle is on, escape_fn will be called when rendering.
+ pub fn set_disable_escape(&mut self, disable: bool) {
+ self.inner_mut().disable_escape = disable
+ }
+}
+
+impl<'reg, 'rc> fmt::Debug for RenderContextInner<'reg, 'rc> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ f.debug_struct("RenderContextInner")
+ .field("partials", &self.partials)
+ .field("partial_block_stack", &self.partial_block_stack)
+ .field("root_template", &self.root_template)
+ .field("current_template", &self.current_template)
+ .field("disable_eacape", &self.disable_escape)
+ .finish()
+ }
+}
+
+/// Render-time Helper data when using in a helper definition
+#[derive(Debug)]
+pub struct Helper<'reg, 'rc> {
+ name: Cow<'reg, str>,
+ params: Vec<PathAndJson<'reg, 'rc>>,
+ hash: BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>,
+ template: Option<&'reg Template>,
+ inverse: Option<&'reg Template>,
+ block_param: Option<&'reg BlockParam>,
+ block: bool,
+}
+
+impl<'reg: 'rc, 'rc> Helper<'reg, 'rc> {
+ fn try_from_template(
+ ht: &'reg HelperTemplate,
+ registry: &'reg Registry<'reg>,
+ context: &'rc Context,
+ render_context: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<Helper<'reg, 'rc>, RenderError> {
+ let name = ht.name.expand_as_name(registry, context, render_context)?;
+ let mut pv = Vec::with_capacity(ht.params.len());
+ for p in &ht.params {
+ let r = p.expand(registry, context, render_context)?;
+ pv.push(r);
+ }
+
+ let mut hm = BTreeMap::new();
+ for (k, p) in &ht.hash {
+ let r = p.expand(registry, context, render_context)?;
+ hm.insert(k.as_ref(), r);
+ }
+
+ Ok(Helper {
+ name,
+ params: pv,
+ hash: hm,
+ template: ht.template.as_ref(),
+ inverse: ht.inverse.as_ref(),
+ block_param: ht.block_param.as_ref(),
+ block: ht.block,
+ })
+ }
+
+ /// Returns helper name
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ /// Returns all helper params, resolved within the context
+ pub fn params(&self) -> &Vec<PathAndJson<'reg, 'rc>> {
+ &self.params
+ }
+
+ /// Returns nth helper param, resolved within the context.
+ ///
+ /// ## Example
+ ///
+ /// To get the first param in `{{my_helper abc}}` or `{{my_helper 2}}`,
+ /// use `h.param(0)` in helper definition.
+ /// Variable `abc` is auto resolved in current context.
+ ///
+ /// ```
+ /// use handlebars::*;
+ ///
+ /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
+ /// let v = h.param(0).map(|v| v.value())
+ /// .ok_or(RenderError::new("param not found"));
+ /// // ..
+ /// Ok(())
+ /// }
+ /// ```
+ pub fn param(&self, idx: usize) -> Option<&PathAndJson<'reg, 'rc>> {
+ self.params.get(idx)
+ }
+
+ /// Returns hash, resolved within the context
+ pub fn hash(&self) -> &BTreeMap<&'reg str, PathAndJson<'reg, 'rc>> {
+ &self.hash
+ }
+
+ /// Return hash value of a given key, resolved within the context
+ ///
+ /// ## Example
+ ///
+ /// To get the first param in `{{my_helper v=abc}}` or `{{my_helper v=2}}`,
+ /// use `h.hash_get("v")` in helper definition.
+ /// Variable `abc` is auto resolved in current context.
+ ///
+ /// ```
+ /// use handlebars::*;
+ ///
+ /// fn my_helper(h: &Helper, rc: &mut RenderContext) -> Result<(), RenderError> {
+ /// let v = h.hash_get("v").map(|v| v.value())
+ /// .ok_or(RenderError::new("param not found"));
+ /// // ..
+ /// Ok(())
+ /// }
+ /// ```
+ pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'reg, 'rc>> {
+ self.hash.get(key)
+ }
+
+ /// Returns the default inner template if the helper is a block helper.
+ ///
+ /// Typically you will render the template via: `template.render(registry, render_context)`
+ ///
+ pub fn template(&self) -> Option<&'reg Template> {
+ self.template
+ }
+
+ /// Returns the template of `else` branch if any
+ pub fn inverse(&self) -> Option<&'reg Template> {
+ self.inverse
+ }
+
+ /// Returns if the helper is a block one `{{#helper}}{{/helper}}` or not `{{helper 123}}`
+ pub fn is_block(&self) -> bool {
+ self.block
+ }
+
+ /// Returns if the helper has either a block param or block param pair
+ pub fn has_block_param(&self) -> bool {
+ self.block_param.is_some()
+ }
+
+ /// Returns block param if any
+ pub fn block_param(&self) -> Option<&'reg str> {
+ if let Some(&BlockParam::Single(Parameter::Name(ref s))) = self.block_param {
+ Some(s)
+ } else {
+ None
+ }
+ }
+
+ /// Return block param pair (for example |key, val|) if any
+ pub fn block_param_pair(&self) -> Option<(&'reg str, &'reg str)> {
+ if let Some(&BlockParam::Pair((Parameter::Name(ref s1), Parameter::Name(ref s2)))) =
+ self.block_param
+ {
+ Some((s1, s2))
+ } else {
+ None
+ }
+ }
+}
+
+/// Render-time Decorator data when using in a decorator definition
+#[derive(Debug)]
+pub struct Decorator<'reg, 'rc> {
+ name: Cow<'reg, str>,
+ params: Vec<PathAndJson<'reg, 'rc>>,
+ hash: BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>,
+ template: Option<&'reg Template>,
+}
+
+impl<'reg: 'rc, 'rc> Decorator<'reg, 'rc> {
+ fn try_from_template(
+ dt: &'reg DecoratorTemplate,
+ registry: &'reg Registry<'reg>,
+ context: &'rc Context,
+ render_context: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<Decorator<'reg, 'rc>, RenderError> {
+ let name = dt.name.expand_as_name(registry, context, render_context)?;
+
+ let mut pv = Vec::with_capacity(dt.params.len());
+ for p in &dt.params {
+ let r = p.expand(registry, context, render_context)?;
+ pv.push(r);
+ }
+
+ let mut hm = BTreeMap::new();
+ for (k, p) in &dt.hash {
+ let r = p.expand(registry, context, render_context)?;
+ hm.insert(k.as_ref(), r);
+ }
+
+ Ok(Decorator {
+ name,
+ params: pv,
+ hash: hm,
+ template: dt.template.as_ref(),
+ })
+ }
+
+ /// Returns helper name
+ pub fn name(&self) -> &str {
+ self.name.as_ref()
+ }
+
+ /// Returns all helper params, resolved within the context
+ pub fn params(&self) -> &Vec<PathAndJson<'reg, 'rc>> {
+ &self.params
+ }
+
+ /// Returns nth helper param, resolved within the context
+ pub fn param(&self, idx: usize) -> Option<&PathAndJson<'reg, 'rc>> {
+ self.params.get(idx)
+ }
+
+ /// Returns hash, resolved within the context
+ pub fn hash(&self) -> &BTreeMap<&'reg str, PathAndJson<'reg, 'rc>> {
+ &self.hash
+ }
+
+ /// Return hash value of a given key, resolved within the context
+ pub fn hash_get(&self, key: &str) -> Option<&PathAndJson<'reg, 'rc>> {
+ self.hash.get(key)
+ }
+
+ /// Returns the default inner template if any
+ pub fn template(&self) -> Option<&'reg Template> {
+ self.template
+ }
+}
+
+/// Render trait
+pub trait Renderable {
+ /// render into RenderContext's `writer`
+ fn render<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ context: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> Result<(), RenderError>;
+
+ /// render into string
+ fn renders<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<String, RenderError> {
+ let mut so = StringOutput::new();
+ self.render(registry, ctx, rc, &mut so)?;
+ so.into_string().map_err(RenderError::from)
+ }
+}
+
+/// Evaluate decorator
+pub trait Evaluable {
+ fn eval<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ context: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<(), RenderError>;
+}
+
+#[inline]
+fn call_helper_for_value<'reg: 'rc, 'rc>(
+ hd: &dyn HelperDef,
+ ht: &Helper<'reg, 'rc>,
+ r: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+) -> Result<PathAndJson<'reg, 'rc>, RenderError> {
+ match hd.call_inner(ht, r, ctx, rc) {
+ Ok(result) => Ok(PathAndJson::new(None, result)),
+ Err(e) => {
+ if e.is_unimplemented() {
+ // parse value from output
+ let mut so = StringOutput::new();
+
+ // here we don't want subexpression result escaped,
+ // so we temporarily disable it
+ let disable_escape = rc.is_disable_escape();
+ rc.set_disable_escape(true);
+
+ hd.call(ht, r, ctx, rc, &mut so)?;
+ rc.set_disable_escape(disable_escape);
+
+ let string = so.into_string().map_err(RenderError::from)?;
+ Ok(PathAndJson::new(
+ None,
+ ScopedJson::Derived(Json::String(string)),
+ ))
+ } else {
+ Err(e)
+ }
+ }
+ }
+}
+
+impl Parameter {
+ pub fn expand_as_name<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<Cow<'reg, str>, RenderError> {
+ match self {
+ Parameter::Name(ref name) => Ok(Cow::Borrowed(name)),
+ Parameter::Path(ref p) => Ok(Cow::Borrowed(p.raw())),
+ Parameter::Subexpression(_) => self
+ .expand(registry, ctx, rc)
+ .map(|v| v.value().render())
+ .map(Cow::Owned),
+ Parameter::Literal(ref j) => Ok(Cow::Owned(j.render())),
+ }
+ }
+
+ pub fn expand<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<PathAndJson<'reg, 'rc>, RenderError> {
+ match self {
+ Parameter::Name(ref name) => {
+ // FIXME: raise error when expanding with name?
+ Ok(PathAndJson::new(Some(name.to_owned()), ScopedJson::Missing))
+ }
+ Parameter::Path(ref path) => {
+ if let Some(rc_context) = rc.context() {
+ let result = rc.evaluate2(rc_context.borrow(), path)?;
+ Ok(PathAndJson::new(
+ Some(path.raw().to_owned()),
+ ScopedJson::Derived(result.as_json().clone()),
+ ))
+ } else {
+ let result = rc.evaluate2(ctx, path)?;
+ Ok(PathAndJson::new(Some(path.raw().to_owned()), result))
+ }
+ }
+ Parameter::Literal(ref j) => Ok(PathAndJson::new(None, ScopedJson::Constant(j))),
+ Parameter::Subexpression(ref t) => match *t.as_element() {
+ Expression(ref ht) => {
+ let name = ht.name.expand_as_name(registry, ctx, rc)?;
+
+ let h = Helper::try_from_template(ht, registry, ctx, rc)?;
+ if let Some(ref d) = rc.get_local_helper(&name) {
+ call_helper_for_value(d.as_ref(), &h, registry, ctx, rc)
+ } else {
+ let mut helper = registry.get_or_load_helper(&name)?;
+
+ if helper.is_none() {
+ helper = registry.get_or_load_helper(if ht.block {
+ BLOCK_HELPER_MISSING
+ } else {
+ HELPER_MISSING
+ })?;
+ }
+
+ helper
+ .ok_or_else(|| {
+ RenderError::new(format!("Helper not defined: {:?}", ht.name))
+ })
+ .and_then(|d| call_helper_for_value(d.as_ref(), &h, registry, ctx, rc))
+ }
+ }
+ _ => unreachable!(),
+ },
+ }
+ }
+}
+
+impl Renderable for Template {
+ fn render<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> Result<(), RenderError> {
+ rc.set_current_template_name(self.name.as_ref());
+ let iter = self.elements.iter();
+
+ for (idx, t) in iter.enumerate() {
+ t.render(registry, ctx, rc, out).map_err(|mut e| {
+ // add line/col number if the template has mapping data
+ if e.line_no.is_none() {
+ if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
+ e.line_no = Some(line);
+ e.column_no = Some(col);
+ }
+ }
+
+ if e.template_name.is_none() {
+ e.template_name = self.name.clone();
+ }
+
+ e
+ })?;
+ }
+ Ok(())
+ }
+}
+
+impl Evaluable for Template {
+ fn eval<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<(), RenderError> {
+ let iter = self.elements.iter();
+
+ for (idx, t) in iter.enumerate() {
+ t.eval(registry, ctx, rc).map_err(|mut e| {
+ if e.line_no.is_none() {
+ if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
+ e.line_no = Some(line);
+ e.column_no = Some(col);
+ }
+ }
+
+ e.template_name = self.name.clone();
+ e
+ })?;
+ }
+ Ok(())
+ }
+}
+
+fn helper_exists<'reg: 'rc, 'rc>(
+ name: &str,
+ reg: &Registry<'reg>,
+ rc: &RenderContext<'reg, 'rc>,
+) -> bool {
+ rc.has_local_helper(name) || reg.has_helper(name)
+}
+
+#[inline]
+fn render_helper<'reg: 'rc, 'rc>(
+ ht: &'reg HelperTemplate,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ let h = Helper::try_from_template(ht, registry, ctx, rc)?;
+ debug!(
+ "Rendering helper: {:?}, params: {:?}, hash: {:?}",
+ h.name(),
+ h.params(),
+ h.hash()
+ );
+ if let Some(ref d) = rc.get_local_helper(h.name()) {
+ d.call(&h, registry, ctx, rc, out)
+ } else {
+ let mut helper = registry.get_or_load_helper(h.name())?;
+
+ if helper.is_none() {
+ helper = registry.get_or_load_helper(if ht.block {
+ BLOCK_HELPER_MISSING
+ } else {
+ HELPER_MISSING
+ })?;
+ }
+
+ helper
+ .ok_or_else(|| RenderError::new(format!("Helper not defined: {:?}", h.name())))
+ .and_then(|d| d.call(&h, registry, ctx, rc, out))
+ }
+}
+
+pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: String) -> String {
+ if !rc.is_disable_escape() {
+ r.get_escape_fn()(&content)
+ } else {
+ content
+ }
+}
+
+impl Renderable for TemplateElement {
+ fn render<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ out: &mut dyn Output,
+ ) -> Result<(), RenderError> {
+ match *self {
+ RawString(ref v) => {
+ out.write(v.as_ref())?;
+ Ok(())
+ }
+ Expression(ref ht) | HtmlExpression(ref ht) => {
+ let is_html_expression = matches!(self, HtmlExpression(_));
+ if is_html_expression {
+ rc.set_disable_escape(true);
+ }
+
+ // test if the expression is to render some value
+ let result = if ht.is_name_only() {
+ let helper_name = ht.name.expand_as_name(registry, ctx, rc)?;
+ if helper_exists(&helper_name, registry, rc) {
+ render_helper(ht, registry, ctx, rc, out)
+ } else {
+ debug!("Rendering value: {:?}", ht.name);
+ let context_json = ht.name.expand(registry, ctx, rc)?;
+ if context_json.is_value_missing() {
+ if registry.strict_mode() {
+ Err(RenderError::strict_error(context_json.relative_path()))
+ } else {
+ // helper missing
+ if let Some(hook) = registry.get_or_load_helper(HELPER_MISSING)? {
+ let h = Helper::try_from_template(ht, registry, ctx, rc)?;
+ hook.call(&h, registry, ctx, rc, out)
+ } else {
+ Ok(())
+ }
+ }
+ } else {
+ let rendered = context_json.value().render();
+ let output = do_escape(registry, rc, rendered);
+ out.write(output.as_ref())?;
+ Ok(())
+ }
+ }
+ } else {
+ // this is a helper expression
+ render_helper(ht, registry, ctx, rc, out)
+ };
+
+ if is_html_expression {
+ rc.set_disable_escape(false);
+ }
+
+ result
+ }
+ HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out),
+ DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
+ PartialExpression(ref dt) | PartialBlock(ref dt) => {
+ let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
+
+ partial::expand_partial(&di, registry, ctx, rc, out)
+ }
+ _ => Ok(()),
+ }
+ }
+}
+
+impl Evaluable for TemplateElement {
+ fn eval<'reg: 'rc, 'rc>(
+ &'reg self,
+ registry: &'reg Registry<'reg>,
+ ctx: &'rc Context,
+ rc: &mut RenderContext<'reg, 'rc>,
+ ) -> Result<(), RenderError> {
+ match *self {
+ DecoratorExpression(ref dt) | DecoratorBlock(ref dt) => {
+ let di = Decorator::try_from_template(dt, registry, ctx, rc)?;
+ match registry.get_decorator(di.name()) {
+ Some(d) => d.call(&di, registry, ctx, rc),
+ None => Err(RenderError::new(format!(
+ "Decorator not defined: {:?}",
+ dt.name
+ ))),
+ }
+ }
+ _ => Ok(()),
+ }
+ }
+}
+
+#[test]
+fn test_raw_string() {
+ let r = Registry::new();
+ let raw_string = RawString("<h1>hello world</h1>".to_string());
+
+ let mut out = StringOutput::new();
+ let ctx = Context::null();
+ {
+ let mut rc = RenderContext::new(None);
+ raw_string.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
+ }
+ assert_eq!(
+ out.into_string().unwrap(),
+ "<h1>hello world</h1>".to_string()
+ );
+}
+
+#[test]
+fn test_expression() {
+ let r = Registry::new();
+ let element = Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
+ &["hello"],
+ ))));
+
+ let mut out = StringOutput::new();
+ let mut m: BTreeMap<String, String> = BTreeMap::new();
+ let value = "<p></p>".to_string();
+ m.insert("hello".to_string(), value);
+ let ctx = Context::wraps(&m).unwrap();
+ {
+ let mut rc = RenderContext::new(None);
+ element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
+ }
+
+ assert_eq!(
+ out.into_string().unwrap(),
+ "&lt;p&gt;&lt;/p&gt;".to_string()
+ );
+}
+
+#[test]
+fn test_html_expression() {
+ let r = Registry::new();
+ let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
+ &["hello"],
+ ))));
+
+ let mut out = StringOutput::new();
+ let mut m: BTreeMap<String, String> = BTreeMap::new();
+ let value = "world";
+ m.insert("hello".to_string(), value.to_string());
+ let ctx = Context::wraps(&m).unwrap();
+ {
+ let mut rc = RenderContext::new(None);
+ element.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
+ }
+
+ assert_eq!(out.into_string().unwrap(), value.to_string());
+}
+
+#[test]
+fn test_template() {
+ let r = Registry::new();
+ let mut out = StringOutput::new();
+ let mut m: BTreeMap<String, String> = BTreeMap::new();
+ let value = "world".to_string();
+ m.insert("hello".to_string(), value);
+ let ctx = Context::wraps(&m).unwrap();
+
+ let elements: Vec<TemplateElement> = vec![
+ RawString("<h1>".to_string()),
+ Expression(Box::new(HelperTemplate::with_path(Path::with_named_paths(
+ &["hello"],
+ )))),
+ RawString("</h1>".to_string()),
+ Comment("".to_string()),
+ ];
+
+ let template = Template {
+ elements,
+ name: None,
+ mapping: Vec::new(),
+ };
+
+ {
+ let mut rc = RenderContext::new(None);
+ template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap();
+ }
+
+ assert_eq!(out.into_string().unwrap(), "<h1>world</h1>".to_string());
+}
+
+#[test]
+fn test_render_context_promotion_and_demotion() {
+ use crate::json::value::to_json;
+ let mut render_context = RenderContext::new(None);
+ let mut block = BlockContext::new();
+
+ block.set_local_var("index", to_json(0));
+ render_context.push_block(block);
+
+ render_context.push_block(BlockContext::new());
+ assert_eq!(
+ render_context.get_local_var(1, "index").unwrap(),
+ &to_json(0)
+ );
+
+ render_context.pop_block();
+
+ assert_eq!(
+ render_context.get_local_var(0, "index").unwrap(),
+ &to_json(0)
+ );
+}
+
+#[test]
+fn test_render_subexpression_issue_115() {
+ use crate::support::str::StringWriter;
+
+ let mut r = Registry::new();
+ r.register_helper(
+ "format",
+ Box::new(
+ |h: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output|
+ -> Result<(), RenderError> {
+ out.write(format!("{}", h.param(0).unwrap().value().render()).as_ref())
+ .map(|_| ())
+ .map_err(RenderError::from)
+ },
+ ),
+ );
+
+ let mut sw = StringWriter::new();
+ let mut m: BTreeMap<String, String> = BTreeMap::new();
+ m.insert("a".to_string(), "123".to_string());
+
+ {
+ if let Err(e) = r.render_template_to_write("{{format (format a)}}", &m, &mut sw) {
+ panic!("{}", e);
+ }
+ }
+
+ assert_eq!(sw.into_string(), "123".to_string());
+}
+
+#[test]
+fn test_render_error_line_no() {
+ let mut r = Registry::new();
+ let m: BTreeMap<String, String> = BTreeMap::new();
+
+ let name = "invalid_template";
+ assert!(r
+ .register_template_string(name, "<h1>\n{{#if true}}\n {{#each}}{{/each}}\n{{/if}}")
+ .is_ok());
+
+ if let Err(e) = r.render(name, &m) {
+ assert_eq!(e.line_no.unwrap(), 3);
+ assert_eq!(e.column_no.unwrap(), 3);
+ assert_eq!(e.template_name, Some(name.to_owned()));
+ } else {
+ panic!("Error expected");
+ }
+}
+
+#[test]
+fn test_partial_failback_render() {
+ let mut r = Registry::new();
+
+ assert!(r
+ .register_template_string("parent", "<html>{{> layout}}</html>")
+ .is_ok());
+ assert!(r
+ .register_template_string(
+ "child",
+ "{{#*inline \"layout\"}}content{{/inline}}{{#> parent}}{{> seg}}{{/parent}}"
+ )
+ .is_ok());
+ assert!(r.register_template_string("seg", "1234").is_ok());
+
+ let r = r.render("child", &true).expect("should work");
+ assert_eq!(r, "<html>content</html>");
+}
+
+#[test]
+fn test_key_with_slash() {
+ let mut r = Registry::new();
+
+ assert!(r
+ .register_template_string("t", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
+ .is_ok());
+
+ let r = r.render("t", &json!({"/foo": "bar"})).unwrap();
+
+ assert_eq!(r, "/foo: bar\n");
+}
+
+#[test]
+fn test_comment() {
+ let r = Registry::new();
+
+ assert_eq!(
+ r.render_template("Hello {{this}} {{! test me }}", &0)
+ .unwrap(),
+ "Hello 0 "
+ );
+}
+
+#[test]
+fn test_zero_args_heler() {
+ let mut r = Registry::new();
+
+ r.register_helper(
+ "name",
+ Box::new(
+ |_: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output|
+ -> Result<(), RenderError> { out.write("N/A").map_err(Into::into) },
+ ),
+ );
+
+ r.register_template_string("t0", "Output name: {{name}}")
+ .unwrap();
+ r.register_template_string("t1", "Output name: {{first_name}}")
+ .unwrap();
+ r.register_template_string("t2", "Output name: {{./name}}")
+ .unwrap();
+
+ // when "name" is available in context, use context first
+ assert_eq!(
+ r.render("t0", &json!({"name": "Alex"})).unwrap(),
+ "Output name: N/A"
+ );
+
+ // when "name" is unavailable, call helper with same name
+ assert_eq!(
+ r.render("t2", &json!({"name": "Alex"})).unwrap(),
+ "Output name: Alex"
+ );
+
+ // output nothing when neither context nor helper available
+ assert_eq!(
+ r.render("t1", &json!({"name": "Alex"})).unwrap(),
+ "Output name: "
+ );
+
+ // generate error in strict mode for above case
+ r.set_strict_mode(true);
+ assert!(r.render("t1", &json!({"name": "Alex"})).is_err());
+
+ // output nothing when helperMissing was defined
+ r.set_strict_mode(false);
+ r.register_helper(
+ "helperMissing",
+ Box::new(
+ |h: &Helper<'_, '_>,
+ _: &Registry<'_>,
+ _: &Context,
+ _: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output|
+ -> Result<(), RenderError> {
+ let name = h.name();
+ out.write(&format!("{} not resolved", name))?;
+ Ok(())
+ },
+ ),
+ );
+ assert_eq!(
+ r.render("t1", &json!({"name": "Alex"})).unwrap(),
+ "Output name: first_name not resolved"
+ );
+}