]> git.proxmox.com Git - rustc.git/blobdiff - vendor/handlebars/src/render.rs
New upstream version 1.56.0~beta.4+dfsg1
[rustc.git] / vendor / handlebars / src / render.rs
index b49310fbffcd8b2c6614b8a581ad50069bfb2d1a..188ea221a17bb274db695c36633a59dd21156d6a 100644 (file)
@@ -1,7 +1,6 @@
 use std::borrow::{Borrow, Cow};
 use std::collections::{BTreeMap, VecDeque};
 use std::fmt;
-use std::ops::Deref;
 use std::rc::Rc;
 
 use serde_json::value::Value as Json;
@@ -40,7 +39,9 @@ pub struct RenderContext<'reg, 'rc> {
 #[derive(Clone)]
 pub struct RenderContextInner<'reg: 'rc, 'rc> {
     partials: BTreeMap<String, &'reg Template>,
-    local_helpers: BTreeMap<String, Rc<dyn HelperDef + 'rc>>,
+    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
@@ -53,6 +54,8 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
     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,
@@ -158,8 +161,15 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
     }
 
     /// Get registered partial in this render context
-    pub fn get_partial(&self, name: &str) -> Option<&&Template> {
-        self.inner().partials.get(name)
+    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
@@ -167,6 +177,22 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
         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);
@@ -192,11 +218,11 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
     pub fn register_local_helper(
         &mut self,
         name: &str,
-        def: Box<dyn HelperDef + 'rc>,
-    ) -> Option<Rc<dyn HelperDef + 'rc>> {
+        def: Box<dyn HelperDef + Send + Sync + 'rc>,
+    ) {
         self.inner_mut()
             .local_helpers
-            .insert(name.to_string(), def.into())
+            .insert(name.to_string(), def.into());
     }
 
     /// Remove a helper from render context
@@ -205,7 +231,7 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
     }
 
     /// Attempt to get a helper from current render context.
-    pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + 'rc>> {
+    pub fn get_local_helper(&self, name: &str) -> Option<Rc<dyn HelperDef + Send + Sync + 'rc>> {
         self.inner().local_helpers.get(name).cloned()
     }
 
@@ -248,6 +274,7 @@ 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)
@@ -504,6 +531,7 @@ pub trait Evaluable {
     ) -> Result<(), RenderError>;
 }
 
+#[inline]
 fn call_helper_for_value<'reg: 'rc, 'rc>(
     hd: &dyn HelperDef,
     ht: &Helper<'reg, 'rc>,
@@ -511,25 +539,30 @@ fn call_helper_for_value<'reg: 'rc, 'rc>(
     ctx: &'rc Context,
     rc: &mut RenderContext<'reg, 'rc>,
 ) -> Result<PathAndJson<'reg, 'rc>, RenderError> {
-    if let Some(result) = hd.call_inner(ht, r, ctx, rc)? {
-        Ok(PathAndJson::new(None, result))
-    } else {
-        // 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)),
-        ))
+    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)
+            }
+        }
     }
 }
 
@@ -577,30 +610,27 @@ impl Parameter {
             Parameter::Literal(ref j) => Ok(PathAndJson::new(None, ScopedJson::Constant(j))),
             Parameter::Subexpression(ref t) => match *t.as_element() {
                 Expression(ref ht) => {
-                    if ht.is_name_only() {
-                        ht.name.expand(registry, ctx, rc)
+                    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 name = ht.name.expand_as_name(registry, ctx, rc)?;
+                        let mut helper = registry.get_or_load_helper(&name)?;
 
-                        let h = Helper::try_from_template(ht, registry, ctx, rc)?;
-                        if let Some(ref d) = rc.get_local_helper(&name) {
-                            let helper_def = d.deref();
-                            call_helper_for_value(helper_def, &h, registry, ctx, rc)
-                        } else {
-                            registry
-                                .get_helper(&name)
-                                .or_else(|| {
-                                    registry.get_helper(if ht.block {
-                                        BLOCK_HELPER_MISSING
-                                    } else {
-                                        HELPER_MISSING
-                                    })
-                                })
-                                .ok_or_else(|| {
-                                    RenderError::new(format!("Helper not defined: {:?}", ht.name))
-                                })
-                                .and_then(move |d| call_helper_for_value(d, &h, registry, ctx, rc))
+                        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!(),
@@ -624,11 +654,9 @@ impl Renderable for Template {
             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(ref mapping) = self.mapping {
-                        if let Some(&TemplateMapping(line, col)) = mapping.get(idx) {
-                            e.line_no = Some(line);
-                            e.column_no = Some(col);
-                        }
+                    if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
+                        e.line_no = Some(line);
+                        e.column_no = Some(col);
                     }
                 }
 
@@ -655,11 +683,9 @@ impl Evaluable for Template {
         for (idx, t) in iter.enumerate() {
             t.eval(registry, ctx, rc).map_err(|mut e| {
                 if e.line_no.is_none() {
-                    if let Some(ref mapping) = self.mapping {
-                        if let Some(&TemplateMapping(line, col)) = mapping.get(idx) {
-                            e.line_no = Some(line);
-                            e.column_no = Some(col);
-                        }
+                    if let Some(&TemplateMapping(line, col)) = self.mapping.get(idx) {
+                        e.line_no = Some(line);
+                        e.column_no = Some(col);
                     }
                 }
 
@@ -679,6 +705,7 @@ fn helper_exists<'reg: 'rc, 'rc>(
     rc.has_local_helper(name) || reg.has_helper(name)
 }
 
+#[inline]
 fn render_helper<'reg: 'rc, 'rc>(
     ht: &'reg HelperTemplate,
     registry: &'reg Registry<'reg>,
@@ -696,17 +723,19 @@ fn render_helper<'reg: 'rc, 'rc>(
     if let Some(ref d) = rc.get_local_helper(h.name()) {
         d.call(&h, registry, ctx, rc, out)
     } else {
-        registry
-            .get_helper(h.name())
-            .or_else(|| {
-                registry.get_helper(if ht.block {
-                    BLOCK_HELPER_MISSING
-                } else {
-                    HELPER_MISSING
-                })
-            })
-            .ok_or_else(|| RenderError::new(format!("Helper not defined: {:?}", ht.name)))
-            .and_then(move |d| d.call(&h, registry, ctx, rc, out))
+        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))
     }
 }
 
@@ -731,9 +760,14 @@ impl Renderable for TemplateElement {
                 out.write(v.as_ref())?;
                 Ok(())
             }
-            Expression(ref ht) => {
+            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
-                if ht.is_name_only() {
+                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)
@@ -745,7 +779,7 @@ impl Renderable for TemplateElement {
                                 Err(RenderError::strict_error(context_json.relative_path()))
                             } else {
                                 // helper missing
-                                if let Some(hook) = registry.get_helper(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 {
@@ -762,20 +796,13 @@ impl Renderable for TemplateElement {
                 } else {
                     // this is a helper expression
                     render_helper(ht, registry, ctx, rc, out)
-                }
-            }
-            HTMLExpression(ref v) => {
-                debug!("Rendering value: {:?}", v);
-                let context_json = v.expand(registry, ctx, rc)?;
+                };
 
-                // strict mode check
-                if registry.strict_mode() && context_json.is_value_missing() {
-                    return Err(RenderError::strict_error(context_json.relative_path()));
+                if is_html_expression {
+                    rc.set_disable_escape(false);
                 }
 
-                let rendered = context_json.value().render();
-                out.write(rendered.as_ref())?;
-                Ok(())
+                result
             }
             HelperBlock(ref ht) => render_helper(ht, registry, ctx, rc, out),
             DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
@@ -855,7 +882,9 @@ fn test_expression() {
 #[test]
 fn test_html_expression() {
     let r = Registry::new();
-    let element = HTMLExpression(Parameter::Path(Path::with_named_paths(&["hello"])));
+    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();
@@ -891,7 +920,7 @@ fn test_template() {
     let template = Template {
         elements,
         name: None,
-        mapping: None,
+        mapping: Vec::new(),
     };
 
     {
@@ -908,7 +937,7 @@ fn test_render_context_promotion_and_demotion() {
     let mut render_context = RenderContext::new(None);
     let mut block = BlockContext::new();
 
-    block.set_local_var("@index".to_string(), to_json(0));
+    block.set_local_var("index", to_json(0));
     render_context.push_block(block);
 
     render_context.push_block(BlockContext::new());
@@ -925,29 +954,6 @@ fn test_render_context_promotion_and_demotion() {
     );
 }
 
-#[test]
-fn test_render_subexpression() {
-    use crate::support::str::StringWriter;
-
-    let r = Registry::new();
-    let mut sw = StringWriter::new();
-
-    let mut m: BTreeMap<String, String> = BTreeMap::new();
-    m.insert("hello".to_string(), "world".to_string());
-    m.insert("world".to_string(), "nice".to_string());
-    m.insert("const".to_string(), "truthy".to_string());
-
-    {
-        if let Err(e) =
-            r.render_template_to_write("<h1>{{#if (const)}}{{(hello)}}{{/if}}</h1>", &m, &mut sw)
-        {
-            panic!("{}", e);
-        }
-    }
-
-    assert_eq!(sw.into_string(), "<h1>world</h1>".to_string());
-}
-
 #[test]
 fn test_render_subexpression_issue_115() {
     use crate::support::str::StringWriter;
@@ -1094,16 +1100,20 @@ fn test_zero_args_heler() {
     r.register_helper(
         "helperMissing",
         Box::new(
-            |_: &Helper<'_, '_>,
+            |h: &Helper<'_, '_>,
              _: &Registry<'_>,
              _: &Context,
              _: &mut RenderContext<'_, '_>,
              out: &mut dyn Output|
-             -> Result<(), RenderError> { out.write("Default").map_err(Into::into) },
+             -> Result<(), RenderError> {
+                let name = h.name();
+                out.write(&format!("{} not resolved", name))?;
+                Ok(())
+            },
         ),
     );
     assert_eq!(
         r.render("t1", &json!({"name": "Alex"})).unwrap(),
-        "Output name: Default"
+        "Output name: first_name not resolved"
     );
 }