diff options
Diffstat (limited to 'vendor/handlebars/src')
-rw-r--r-- | vendor/handlebars/src/decorators/mod.rs | 12 | ||||
-rw-r--r-- | vendor/handlebars/src/error.rs | 15 | ||||
-rw-r--r-- | vendor/handlebars/src/helpers/mod.rs | 16 | ||||
-rw-r--r-- | vendor/handlebars/src/lib.rs | 4 | ||||
-rw-r--r-- | vendor/handlebars/src/macros.rs | 2 | ||||
-rw-r--r-- | vendor/handlebars/src/output.rs | 21 | ||||
-rw-r--r-- | vendor/handlebars/src/partial.rs | 67 | ||||
-rw-r--r-- | vendor/handlebars/src/registry.rs | 48 | ||||
-rw-r--r-- | vendor/handlebars/src/render.rs | 598 | ||||
-rw-r--r-- | vendor/handlebars/src/template.rs | 50 |
10 files changed, 470 insertions, 363 deletions
diff --git a/vendor/handlebars/src/decorators/mod.rs b/vendor/handlebars/src/decorators/mod.rs index bd2d23458..8633c1d5f 100644 --- a/vendor/handlebars/src/decorators/mod.rs +++ b/vendor/handlebars/src/decorators/mod.rs @@ -230,14 +230,14 @@ mod test { _: &mut RenderContext<'_, '_>, out: &mut dyn Output| -> Result<(), RenderError> { - let s = format!( + write!( + out, "{}m", h.param(0) .as_ref() .map(|v| v.value()) .unwrap_or(&to_json(0)) - ); - out.write(s.as_ref())?; + )?; Ok(()) }, ), @@ -262,15 +262,15 @@ mod test { _: &mut RenderContext<'_, '_>, out: &mut dyn Output| -> Result<(), RenderError> { - let s = format!( + write!( + out, "{}{}", h.param(0) .as_ref() .map(|v| v.value()) .unwrap_or(&to_json(0)), new_unit - ); - out.write(s.as_ref())?; + )?; Ok(()) }; diff --git a/vendor/handlebars/src/error.rs b/vendor/handlebars/src/error.rs index 618d68e4f..160986350 100644 --- a/vendor/handlebars/src/error.rs +++ b/vendor/handlebars/src/error.rs @@ -1,4 +1,3 @@ -// use std::backtrace::Backtrace; use std::error::Error as StdError; use std::fmt::{self, Write}; use std::io::Error as IOError; @@ -152,6 +151,7 @@ pub enum TemplateErrorReason { /// Error on parsing template. #[derive(Debug, Error)] pub struct TemplateError { + #[deprecated(note = "public access to reason to be removed soon, use .reason() instead.")] pub reason: TemplateErrorReason, pub template_name: Option<String>, pub line_no: Option<usize>, @@ -160,6 +160,7 @@ pub struct TemplateError { } impl TemplateError { + #[allow(deprecated)] pub fn of(e: TemplateErrorReason) -> TemplateError { TemplateError { reason: e, @@ -181,6 +182,12 @@ impl TemplateError { self.template_name = Some(name); self } + + /// Get underlying reason for the error + #[allow(deprecated)] + pub fn reason(&self) -> &TemplateErrorReason { + &self.reason + } } impl From<(IOError, String)> for TemplateError { @@ -229,16 +236,16 @@ impl fmt::Display for TemplateError { (Some(line), Some(col), &Some(ref seg)) => writeln!( f, "Template error: {}\n --> Template error in \"{}\":{}:{}\n |\n{} |\n = reason: {}", - self.reason, + self.reason(), self.template_name .as_ref() .unwrap_or(&"Unnamed template".to_owned()), line, col, seg, - self.reason + self.reason() ), - _ => write!(f, "{}", self.reason), + _ => write!(f, "{}", self.reason()), } } } diff --git a/vendor/handlebars/src/helpers/mod.rs b/vendor/handlebars/src/helpers/mod.rs index ff5fa2495..8019f46f9 100644 --- a/vendor/handlebars/src/helpers/mod.rs +++ b/vendor/handlebars/src/helpers/mod.rs @@ -208,15 +208,11 @@ mod test { ) -> Result<(), RenderError> { let v = h.param(0).unwrap(); - if !h.is_block() { - let output = format!("{}:{}", h.name(), v.value().render()); - out.write(output.as_ref())?; - } else { - let output = format!("{}:{}", h.name(), v.value().render()); - out.write(output.as_ref())?; + write!(out, "{}:{}", h.name(), v.value().render())?; + if h.is_block() { out.write("->")?; h.template().unwrap().render(r, ctx, rc, out)?; - }; + } Ok(()) } } @@ -258,8 +254,7 @@ mod test { _: &mut RenderContext<'_, '_>, out: &mut dyn Output| -> Result<(), RenderError> { - let output = format!("{}{}", h.name(), h.param(0).unwrap().value()); - out.write(output.as_ref())?; + write!(out, "{}{}", h.name(), h.param(0).unwrap().value())?; Ok(()) }, ), @@ -273,8 +268,7 @@ mod test { _: &mut RenderContext<'_, '_>, out: &mut dyn Output| -> Result<(), RenderError> { - let output = format!("{}", h.hash_get("value").unwrap().value().render()); - out.write(output.as_ref())?; + write!(out, "{}", h.hash_get("value").unwrap().value().render())?; Ok(()) }, ), diff --git a/vendor/handlebars/src/lib.rs b/vendor/handlebars/src/lib.rs index 1f9ab1ed3..1cb9e79ee 100644 --- a/vendor/handlebars/src/lib.rs +++ b/vendor/handlebars/src/lib.rs @@ -1,5 +1,7 @@ -#![doc(html_root_url = "https://docs.rs/handlebars/4.3.3")] +#![doc(html_root_url = "https://docs.rs/handlebars/4.3.7")] #![cfg_attr(docsrs, feature(doc_cfg))] +#![allow(unknown_lints)] +#![allow(clippy::result_large_err)] //! # Handlebars //! //! [Handlebars](http://handlebarsjs.com/) is a modern and extensible templating solution originally created in the JavaScript world. It's used by many popular frameworks like [Ember.js](http://emberjs.com) and Chaplin. It's also ported to some other platforms such as [Java](https://github.com/jknack/handlebars.java). diff --git a/vendor/handlebars/src/macros.rs b/vendor/handlebars/src/macros.rs index bd1eab0bb..2d893acab 100644 --- a/vendor/handlebars/src/macros.rs +++ b/vendor/handlebars/src/macros.rs @@ -54,7 +54,7 @@ macro_rules! handlebars_helper { r: &'reg $crate::Handlebars<'reg>, _: &'rc $crate::Context, _: &mut $crate::RenderContext<'reg, 'rc>, - ) -> Result<$crate::ScopedJson<'reg, 'rc>, $crate::RenderError> { + ) -> std::result::Result<$crate::ScopedJson<'reg, 'rc>, $crate::RenderError> { let mut param_idx = 0; $( diff --git a/vendor/handlebars/src/output.rs b/vendor/handlebars/src/output.rs index 67e62b849..12075b365 100644 --- a/vendor/handlebars/src/output.rs +++ b/vendor/handlebars/src/output.rs @@ -6,6 +6,19 @@ use std::string::FromUtf8Error; /// Handlebars uses this trait to define rendered output. pub trait Output { fn write(&mut self, seg: &str) -> Result<(), IOError>; + + /// Designed to be used with `write!` macro. + /// for backward compatibility and to avoid breakage the default implementation + /// uses `format!` this may be not what you want. + fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> Result<(), IOError> { + // Check if there is nothing to format to avoid allocation on case like + // write!(out, "hey")?; + if let Some(content) = args.as_str() { + self.write(content) + } else { + self.write(&std::fmt::format(args)) + } + } } pub struct WriteOutput<W: Write> { @@ -16,6 +29,10 @@ impl<W: Write> Output for WriteOutput<W> { fn write(&mut self, seg: &str) -> Result<(), IOError> { self.write.write_all(seg.as_bytes()) } + + fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> Result<(), IOError> { + self.write.write_fmt(args) + } } impl<W: Write> WriteOutput<W> { @@ -33,6 +50,10 @@ impl Output for StringOutput { self.buf.extend_from_slice(seg.as_bytes()); Ok(()) } + + fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> Result<(), IOError> { + self.buf.write_fmt(args) + } } impl StringOutput { diff --git a/vendor/handlebars/src/partial.rs b/vendor/handlebars/src/partial.rs index bcf9803fd..6c41091f4 100644 --- a/vendor/handlebars/src/partial.rs +++ b/vendor/handlebars/src/partial.rs @@ -72,7 +72,6 @@ pub fn expand_partial<'reg: 'rc, 'rc>( local_rc.dec_partial_block_depth(); } - let mut block = None; let mut block_created = false; // create context if param given @@ -80,7 +79,15 @@ pub fn expand_partial<'reg: 'rc, 'rc>( // path given, update base_path let mut block_inner = BlockContext::new(); *block_inner.base_path_mut() = base_path.to_vec(); - block = Some(block_inner); + + // because block is moved here, we need another bool variable to track + // its status for later cleanup + block_created = true; + // clear blocks to prevent block params from parent + // template to be leaked into partials + // see `test_partial_context_issue_495` for the case. + local_rc.clear_blocks(); + local_rc.push_block(block_inner); } if !d.hash().is_empty() { @@ -91,31 +98,34 @@ pub fn expand_partial<'reg: 'rc, 'rc>( .map(|(k, v)| (*k, v.value())) .collect::<HashMap<&str, &Json>>(); + // create block if we didn't (no param provided for partial expression) + if !block_created { + let block_inner = if let Some(block) = local_rc.block() { + // reuse current block information, including base_path and + // base_value if any + block.clone() + } else { + BlockContext::new() + }; + + local_rc.clear_blocks(); + local_rc.push_block(block_inner); + } + + // evaluate context within current block, this includes block + // context provided by partial expression parameter let merged_context = merge_json( local_rc.evaluate2(ctx, &Path::current())?.as_json(), &hash_ctx, ); - if let Some(ref mut block_inner) = block { - block_inner.set_base_value(merged_context); - } else { - let mut block_inner = BlockContext::new(); - block_inner.set_base_value(merged_context); - block = Some(block_inner); + // update the base value, there must be a block for this so it's + // also safe to unwrap. + if let Some(block) = local_rc.block_mut() { + block.set_base_value(merged_context); } } - if let Some(block_inner) = block { - // because block is moved here, we need another bool variable to track - // its status for later cleanup - block_created = true; - // clear blocks to prevent block params from parent - // template to be leaked into partials - // see `test_partial_context_issue_495` for the case. - local_rc.clear_blocks(); - local_rc.push_block(block_inner); - } - // @partial-block if let Some(pb) = d.template() { local_rc.push_partial_block(pb); @@ -654,3 +664,22 @@ outer third line"#, ) } } + +#[test] +fn test_issue_534() { + let t1 = "{{title}}"; + let t2 = "{{#each modules}}{{> (lookup this \"module\") content name=0}}{{/each}}"; + + let data = json!({ + "modules": [ + {"module": "t1", "content": {"title": "foo"}}, + {"module": "t1", "content": {"title": "bar"}}, + ] + }); + + let mut hbs = Registry::new(); + hbs.register_template_string("t1", t1).unwrap(); + hbs.register_template_string("t2", t2).unwrap(); + + assert_eq!("foobar", hbs.render("t2", &data).unwrap()); +} diff --git a/vendor/handlebars/src/registry.rs b/vendor/handlebars/src/registry.rs index 438f8573c..d84dba715 100644 --- a/vendor/handlebars/src/registry.rs +++ b/vendor/handlebars/src/registry.rs @@ -298,22 +298,13 @@ impl<'reg> Registry<'reg> { { let dir_path = dir_path.as_ref(); - // Allowing dots at the beginning as to not break old - // applications. - let tpl_extension = tpl_extension.strip_prefix('.').unwrap_or(tpl_extension); - let walker = WalkDir::new(dir_path); let dir_iter = walker .min_depth(1) .into_iter() .filter_map(|e| e.ok().map(|e| e.into_path())) // Checks if extension matches - .filter(|tpl_path| { - tpl_path - .extension() - .map(|extension| extension == tpl_extension) - .unwrap_or(false) - }) + .filter(|tpl_path| tpl_path.to_string_lossy().ends_with(tpl_extension)) // Rejects any hidden or temporary files. .filter(|tpl_path| { tpl_path @@ -327,12 +318,16 @@ impl<'reg> Registry<'reg> { .strip_prefix(dir_path) .ok() .map(|tpl_canonical_name| { - tpl_canonical_name - .with_extension("") + let tpl_name = tpl_canonical_name .components() .map(|component| component.as_os_str().to_string_lossy()) .collect::<Vec<_>>() - .join("/") + .join("/"); + + tpl_name + .strip_suffix(tpl_extension) + .map(|s| s.to_owned()) + .unwrap_or(tpl_name) }) .map(|tpl_canonical_name| (tpl_canonical_name, tpl_path)) }); @@ -609,7 +604,7 @@ impl<'reg> Registry<'reg> { T: Serialize, { let mut output = StringOutput::new(); - let ctx = Context::wraps(&data)?; + let ctx = Context::wraps(data)?; self.render_to_output(name, &ctx, &mut output)?; output.into_string().map_err(RenderError::from) } @@ -908,6 +903,31 @@ mod test { drop(file1); dir.close().unwrap(); } + + { + let dir = tempdir().unwrap(); + let mut r = Registry::new(); + + let file1_path = dir.path().join("t11.hbs.html"); + let mut file1: File = File::create(&file1_path).unwrap(); + writeln!(file1, "<h1>Bonjour {{world}}!</h1>").unwrap(); + + let mut dir_path = dir + .path() + .to_string_lossy() + .replace(std::path::MAIN_SEPARATOR, "/"); + if !dir_path.ends_with("/") { + dir_path.push('/'); + } + r.register_templates_directory(".hbs.html", dir_path) + .unwrap(); + + assert_eq!(r.templates.len(), 1); + assert_eq!(r.templates.contains_key("t11"), true); + + drop(file1); + dir.close().unwrap(); + } } #[test] diff --git a/vendor/handlebars/src/render.rs b/vendor/handlebars/src/render.rs index 036352b3a..6d7418e12 100644 --- a/vendor/handlebars/src/render.rs +++ b/vendor/handlebars/src/render.rs @@ -874,315 +874,333 @@ impl Evaluable for TemplateElement { } } -#[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(), - "<p></p>".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(); +#[cfg(test)] +mod test { + use std::collections::BTreeMap; + + use super::{Helper, RenderContext, Renderable}; + use crate::block::BlockContext; + use crate::context::Context; + use crate::error::RenderError; + use crate::json::path::Path; + use crate::json::value::JsonRender; + use crate::output::{Output, StringOutput}; + use crate::registry::Registry; + use crate::template::TemplateElement::*; + use crate::template::{HelperTemplate, Template, TemplateElement}; + + #[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() + ); } - 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( + #[test] + fn test_expression() { + let r = Registry::new(); + let element = 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 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(); + } - { - let mut rc = RenderContext::new(None); - template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap(); + assert_eq!( + out.into_string().unwrap(), + "<p></p>".to_string() + ); } - 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_html_expression() { + let r = Registry::new(); + let element = HtmlExpression(Box::new(HelperTemplate::with_path(Path::with_named_paths( + &["hello"], + )))); -#[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 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(); + } - let mut sw = StringWriter::new(); - let mut m: BTreeMap<String, String> = BTreeMap::new(); - m.insert("a".to_string(), "123".to_string()); + 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(), + }; - { - if let Err(e) = r.render_template_to_write("{{format (format a)}}", &m, &mut sw) { - panic!("{}", e); + { + let mut rc = RenderContext::new(None); + template.render(&r, &ctx, &mut rc, &mut out).ok().unwrap(); } - } - - 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(); + 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(&h.param(0).unwrap().value().render()) + .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()); - 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_template_to_write("{{format (format a)}}", &m, &mut sw) { + panic!("{}", e); + } + } - 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"); + assert_eq!(sw.into_string(), "123".to_string()); } -} -#[test] -fn test_partial_failback_render() { - let mut r = Registry::new(); + #[test] + fn test_render_error_line_no() { + let mut r = Registry::new(); + let m: BTreeMap<String, String> = BTreeMap::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" - ); + let name = "invalid_template"; + assert!(r + .register_template_string(name, "<h1>\n{{#if true}}\n {{#each}}{{/each}}\n{{/if}}") + .is_ok()); - // 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" - ); -} - -#[test] -fn test_identifiers_starting_with_numbers() { - let mut r = Registry::new(); - - assert!(r - .register_template_string("r1", "{{#if 0a}}true{{/if}}") - .is_ok()); - let r1 = r.render("r1", &json!({"0a": true})).unwrap(); - assert_eq!(r1, "true"); - - assert!(r.register_template_string("r2", "{{eq 1a 1}}").is_ok()); - let r2 = r.render("r2", &json!({"1a": 2, "a": 1})).unwrap(); - assert_eq!(r2, "false"); + 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"); + } + } - assert!(r + #[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(); + write!(out, "{} not resolved", name)?; + Ok(()) + }, + ), + ); + assert_eq!( + r.render("t1", &json!({"name": "Alex"})).unwrap(), + "Output name: first_name not resolved" + ); + } + + #[test] + fn test_identifiers_starting_with_numbers() { + let mut r = Registry::new(); + + assert!(r + .register_template_string("r1", "{{#if 0a}}true{{/if}}") + .is_ok()); + let r1 = r.render("r1", &json!({"0a": true})).unwrap(); + assert_eq!(r1, "true"); + + assert!(r.register_template_string("r2", "{{eq 1a 1}}").is_ok()); + let r2 = r.render("r2", &json!({"1a": 2, "a": 1})).unwrap(); + assert_eq!(r2, "false"); + + assert!(r .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? .is_ok()); - let r3 = r - .render("r3", &json!({"0": true, "1a": true, "2_2": true})) - .unwrap(); - assert_eq!( - r3, - "0: true \n1a: true resolved from context\n2_2: true resolved from context" - ); - - // these should all be errors: - assert!(r.register_template_string("r4", "{{eq 1}}").is_ok()); - assert!(r.register_template_string("r5", "{{eq a1}}").is_ok()); - assert!(r.register_template_string("r6", "{{eq 1a}}").is_ok()); - assert!(r.render("r4", &()).is_err()); - assert!(r.render("r5", &()).is_err()); - assert!(r.render("r6", &()).is_err()); + let r3 = r + .render("r3", &json!({"0": true, "1a": true, "2_2": true})) + .unwrap(); + assert_eq!( + r3, + "0: true \n1a: true resolved from context\n2_2: true resolved from context" + ); + + // these should all be errors: + assert!(r.register_template_string("r4", "{{eq 1}}").is_ok()); + assert!(r.register_template_string("r5", "{{eq a1}}").is_ok()); + assert!(r.register_template_string("r6", "{{eq 1a}}").is_ok()); + assert!(r.render("r4", &()).is_err()); + assert!(r.render("r5", &()).is_err()); + assert!(r.render("r6", &()).is_err()); + } } diff --git a/vendor/handlebars/src/template.rs b/vendor/handlebars/src/template.rs index 617f47711..4bde14859 100644 --- a/vendor/handlebars/src/template.rs +++ b/vendor/handlebars/src/template.rs @@ -288,11 +288,33 @@ impl Template { Parameter::Path(Path::new(param_span.as_str(), path_segs)) } Rule::literal => { - let s = param_span.as_str(); - if let Ok(json) = Json::from_str(s) { + // Parse the parameter as a JSON literal + let param_literal = it.next().unwrap(); + let json_result = match param_literal.as_rule() { + Rule::string_literal + if it.peek().unwrap().as_rule() == Rule::string_inner_single_quote => + { + // ...unless the parameter is a single-quoted string. + // In that case, transform it to a double-quoted string + // and then parse it as a JSON literal. + let string_inner_single_quote = it.next().unwrap(); + let double_quoted = format!( + "\"{}\"", + string_inner_single_quote + .as_str() + .replace("\\'", "'") + .replace('"', "\\\"") + ); + Json::from_str(&double_quoted) + } + _ => Json::from_str(param_span.as_str()), + }; + if let Ok(json) = json_result { Parameter::Literal(json) } else { - Parameter::Name(s.to_owned()) + return Err(TemplateError::of(TemplateErrorReason::InvalidParam( + param_span.as_str().to_owned(), + ))); } } Rule::subexpression => { @@ -508,8 +530,8 @@ impl Template { } } - pub(crate) fn compile2<'a>( - source: &'a str, + pub(crate) fn compile2( + source: &str, options: TemplateOptions, ) -> Result<Template, TemplateError> { let mut helper_stack: VecDeque<HelperTemplate> = VecDeque::new(); @@ -1010,7 +1032,7 @@ mod test { let terr = Template::compile(source).unwrap_err(); - assert!(matches!(terr.reason, TemplateErrorReason::InvalidSyntax)); + assert!(matches!(terr.reason(), TemplateErrorReason::InvalidSyntax)); assert_eq!(terr.line_no.unwrap(), 4); assert_eq!(terr.column_no.unwrap(), 5); } @@ -1126,16 +1148,10 @@ mod test { let sources = ["{{invalid", "{{{invalid", "{{invalid}", "{{!hello"]; for s in sources.iter() { let result = Template::compile(s.to_owned()); - if let Err(e) = result { - match e.reason { - TemplateErrorReason::InvalidSyntax => {} - _ => { - panic!("Unexpected error type {}", e); - } - } - } else { - panic!("Undetected error"); - } + assert!(matches!( + *result.unwrap_err().reason(), + TemplateErrorReason::InvalidSyntax + )); } } @@ -1316,6 +1332,6 @@ mod test { let s = "{{#>(X)}}{{/X}}"; let result = Template::compile(s); assert!(result.is_err()); - assert_eq!("decorator \"Subexpression(Subexpression { element: Expression(HelperTemplate { name: Path(Relative(([Named(\\\"X\\\")], \\\"X\\\"))), params: [], hash: {}, block_param: None, template: None, inverse: None, block: false }) })\" was opened, but \"X\" is closing", format!("{}", result.unwrap_err().reason)); + assert_eq!("decorator \"Subexpression(Subexpression { element: Expression(HelperTemplate { name: Path(Relative(([Named(\\\"X\\\")], \\\"X\\\"))), params: [], hash: {}, block_param: None, template: None, inverse: None, block: false }) })\" was opened, but \"X\" is closing", format!("{}", result.unwrap_err().reason())); } } |