summaryrefslogtreecommitdiffstats
path: root/vendor/handlebars/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/handlebars/src')
-rw-r--r--vendor/handlebars/src/context.rs19
-rw-r--r--vendor/handlebars/src/decorators/mod.rs12
-rw-r--r--vendor/handlebars/src/error.rs98
-rw-r--r--vendor/handlebars/src/grammar.pest27
-rw-r--r--vendor/handlebars/src/grammar.rs29
-rw-r--r--vendor/handlebars/src/helpers/helper_each.rs94
-rw-r--r--vendor/handlebars/src/helpers/helper_if.rs40
-rw-r--r--vendor/handlebars/src/helpers/helper_lookup.rs12
-rw-r--r--vendor/handlebars/src/helpers/helper_with.rs15
-rw-r--r--vendor/handlebars/src/helpers/mod.rs3
-rw-r--r--vendor/handlebars/src/helpers/scripting.rs14
-rw-r--r--vendor/handlebars/src/json/path.rs4
-rw-r--r--vendor/handlebars/src/lib.rs21
-rw-r--r--vendor/handlebars/src/macros.rs7
-rw-r--r--vendor/handlebars/src/output.rs6
-rw-r--r--vendor/handlebars/src/partial.rs363
-rw-r--r--vendor/handlebars/src/registry.rs272
-rw-r--r--vendor/handlebars/src/render.rs93
-rw-r--r--vendor/handlebars/src/support.rs58
-rw-r--r--vendor/handlebars/src/template.rs233
20 files changed, 1068 insertions, 352 deletions
diff --git a/vendor/handlebars/src/context.rs b/vendor/handlebars/src/context.rs
index 10e15fd90..86f99ba4d 100644
--- a/vendor/handlebars/src/context.rs
+++ b/vendor/handlebars/src/context.rs
@@ -45,7 +45,7 @@ fn parse_json_visitor<'a, 'reg>(
for path_seg in relative_path {
match path_seg {
PathSeg::Named(the_path) => {
- if let Some((holder, base_path)) = get_in_block_params(&block_contexts, the_path) {
+ if let Some((holder, base_path)) = get_in_block_params(block_contexts, the_path) {
with_block_param = Some((holder, base_path));
}
break;
@@ -171,10 +171,7 @@ impl Context {
block_contexts: &VecDeque<BlockContext<'reg>>,
) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
// always use absolute at the moment until we get base_value lifetime issue fixed
- let resolved_visitor = parse_json_visitor(&relative_path, block_contexts, true);
-
- // debug logging
- debug!("Accessing context value: {:?}", resolved_visitor);
+ let resolved_visitor = parse_json_visitor(relative_path, block_contexts, true);
match resolved_visitor {
ResolvedPath::AbsolutePath(paths) => {
@@ -223,6 +220,12 @@ impl Context {
}
}
+impl From<Json> for Context {
+ fn from(data: Json) -> Context {
+ Context { data }
+ }
+}
+
#[cfg(test)]
mod test {
use crate::block::{BlockContext, BlockParams};
@@ -359,9 +362,9 @@ mod test {
#[test]
fn test_key_name_with_this() {
- let m = btreemap! {
- "this_name".to_string() => "the_value".to_string()
- };
+ let m = json!({
+ "this_name": "the_value"
+ });
let ctx = Context::wraps(&m).unwrap();
assert_eq!(
navigate_from_root(&ctx, "this_name").unwrap().render(),
diff --git a/vendor/handlebars/src/decorators/mod.rs b/vendor/handlebars/src/decorators/mod.rs
index b8bad900f..bd2d23458 100644
--- a/vendor/handlebars/src/decorators/mod.rs
+++ b/vendor/handlebars/src/decorators/mod.rs
@@ -105,9 +105,9 @@ mod test {
.register_template_string("t0", "{{*foo}}".to_string())
.unwrap();
- let data = btreemap! {
- "hello".to_string() => "world".to_string()
- };
+ let data = json!({
+ "hello": "world"
+ });
assert!(handlebars.render("t0", &data).is_err());
@@ -132,9 +132,9 @@ mod test {
.register_template_string("t0", "{{hello}}{{*foo}}{{hello}}".to_string())
.unwrap();
- let data = btreemap! {
- "hello".to_string() => "world".to_string()
- };
+ let data = json!({
+ "hello": "world"
+ });
handlebars.register_decorator(
"foo",
diff --git a/vendor/handlebars/src/error.rs b/vendor/handlebars/src/error.rs
index f4721623f..618d68e4f 100644
--- a/vendor/handlebars/src/error.rs
+++ b/vendor/handlebars/src/error.rs
@@ -1,10 +1,13 @@
-use std::error::Error;
-use std::fmt;
+// use std::backtrace::Backtrace;
+use std::error::Error as StdError;
+use std::fmt::{self, Write};
use std::io::Error as IOError;
use std::num::ParseIntError;
use std::string::FromUtf8Error;
use serde_json::error::Error as SerdeError;
+use thiserror::Error;
+
#[cfg(feature = "dir_source")]
use walkdir::Error as WalkdirError;
@@ -12,14 +15,16 @@ use walkdir::Error as WalkdirError;
use rhai::{EvalAltResult, ParseError};
/// Error when rendering data on template.
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Error)]
pub struct RenderError {
pub desc: String,
pub template_name: Option<String>,
pub line_no: Option<usize>,
pub column_no: Option<usize>,
- cause: Option<Box<dyn Error + Send + Sync + 'static>>,
+ #[source]
+ cause: Option<Box<dyn StdError + Send + Sync + 'static>>,
unimplemented: bool,
+ // backtrace: Backtrace,
}
impl fmt::Display for RenderError {
@@ -38,14 +43,6 @@ impl fmt::Display for RenderError {
}
}
-impl Error for RenderError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- self.cause
- .as_ref()
- .map(|e| e.as_ref() as &(dyn Error + 'static))
- }
-}
-
impl From<IOError> for RenderError {
fn from(e: IOError) -> RenderError {
RenderError::from_error("Cannot generate output.", e)
@@ -115,7 +112,7 @@ impl RenderError {
pub fn from_error<E>(error_info: &str, cause: E) -> RenderError
where
- E: Error + Send + Sync + 'static,
+ E: StdError + Send + Sync + 'static,
{
let mut e = RenderError::new(error_info);
e.cause = Some(Box::new(cause));
@@ -129,39 +126,31 @@ impl RenderError {
}
}
-quick_error! {
/// Template parsing error
- #[derive(Debug)]
- pub enum TemplateErrorReason {
- MismatchingClosedHelper(open: String, closed: String) {
- display("helper {:?} was opened, but {:?} is closing",
- open, closed)
- }
- MismatchingClosedDecorator(open: String, closed: String) {
- display("decorator {:?} was opened, but {:?} is closing",
- open, closed)
- }
- InvalidSyntax {
- display("invalid handlebars syntax.")
- }
- InvalidParam (param: String) {
- display("invalid parameter {:?}", param)
- }
- NestedSubexpression {
- display("nested subexpression is not supported")
- }
- IoError(err: IOError, name: String) {
- display("Template \"{}\": {}", name, err)
- }
- #[cfg(feature = "dir_source")]
- WalkdirError(err: WalkdirError) {
- display("Walk dir error: {}", err)
- }
- }
+#[derive(Debug, Error)]
+pub enum TemplateErrorReason {
+ #[error("helper {0:?} was opened, but {1:?} is closing")]
+ MismatchingClosedHelper(String, String),
+ #[error("decorator {0:?} was opened, but {1:?} is closing")]
+ MismatchingClosedDecorator(String, String),
+ #[error("invalid handlebars syntax.")]
+ InvalidSyntax,
+ #[error("invalid parameter {0:?}")]
+ InvalidParam(String),
+ #[error("nested subexpression is not supported")]
+ NestedSubexpression,
+ #[error("Template \"{1}\": {0}")]
+ IoError(IOError, String),
+ #[cfg(feature = "dir_source")]
+ #[error("Walk dir error: {err}")]
+ WalkdirError {
+ #[from]
+ err: WalkdirError,
+ },
}
/// Error on parsing template.
-#[derive(Debug)]
+#[derive(Debug, Error)]
pub struct TemplateError {
pub reason: TemplateErrorReason,
pub template_name: Option<String>,
@@ -194,8 +183,6 @@ impl TemplateError {
}
}
-impl Error for TemplateError {}
-
impl From<(IOError, String)> for TemplateError {
fn from(err_info: (IOError, String)) -> TemplateError {
let (e, name) = err_info;
@@ -206,7 +193,7 @@ impl From<(IOError, String)> for TemplateError {
#[cfg(feature = "dir_source")]
impl From<WalkdirError> for TemplateError {
fn from(e: WalkdirError) -> TemplateError {
- TemplateError::of(TemplateErrorReason::WalkdirError(e))
+ TemplateError::of(TemplateErrorReason::from(e))
}
}
@@ -218,7 +205,7 @@ fn template_segment(template_str: &str, line: usize, col: usize) -> String {
let mut buf = String::new();
for (line_count, line_content) in template_str.lines().enumerate() {
if line_count >= line_start && line_count <= line_end {
- buf.push_str(&format!("{:4} | {}\n", line_count, line_content));
+ let _ = writeln!(&mut buf, "{:4} | {}", line_count, line_content);
if line_count == line - 1 {
buf.push_str(" |");
for c in 0..line_content.len() {
@@ -257,16 +244,11 @@ impl fmt::Display for TemplateError {
}
#[cfg(feature = "script_helper")]
-quick_error! {
- #[derive(Debug)]
- pub enum ScriptError {
- IoError(err: IOError) {
- from()
- source(err)
- }
- ParseError(err: ParseError) {
- from()
- source(err)
- }
- }
+#[derive(Debug, Error)]
+pub enum ScriptError {
+ #[error(transparent)]
+ IoError(#[from] IOError),
+
+ #[error(transparent)]
+ ParseError(#[from] ParseError),
}
diff --git a/vendor/handlebars/src/grammar.pest b/vendor/handlebars/src/grammar.pest
index 250d9d213..ac6776ee2 100644
--- a/vendor/handlebars/src/grammar.pest
+++ b/vendor/handlebars/src/grammar.pest
@@ -14,14 +14,20 @@ literal = { string_literal |
null_literal = @{ "null" ~ !symbol_char }
boolean_literal = @{ ("true"|"false") ~ !symbol_char }
-number_literal = @{ "-"? ~ ASCII_DIGIT+ ~ "."? ~ ASCII_DIGIT* ~ ("E" ~ "-"? ~ ASCII_DIGIT+)? }
-json_char = {
+number_literal = @{ "-"? ~ ASCII_DIGIT+ ~ "."? ~ ASCII_DIGIT* ~ ("E" ~ "-"? ~ ASCII_DIGIT+)? ~ !symbol_char }
+json_char_double_quote = {
!("\"" | "\\") ~ ANY
| "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
| "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
}
-string_inner = @{ json_char* }
-string_literal = ${ "\"" ~ string_inner ~ "\"" }
+json_char_single_quote = {
+ !("'" | "\\") ~ ANY
+ | "\\" ~ ("'" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
+ | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
+}
+string_inner_double_quote = @{ json_char_double_quote* }
+string_inner_single_quote = @{ json_char_single_quote* }
+string_literal = ${ ("\"" ~ string_inner_double_quote ~ "\"") | ("'" ~ string_inner_single_quote ~ "'") }
array_literal = { "[" ~ literal? ~ ("," ~ literal)* ~ "]" }
object_literal = { "{" ~ (string_literal ~ ":" ~ literal)?
~ ("," ~ string_literal ~ ":" ~ literal)* ~ "}" }
@@ -50,12 +56,17 @@ pro_whitespace_omitter = { "~" }
expression = { !invert_tag ~ "{{" ~ pre_whitespace_omitter? ~
((identifier ~ (hash|param)+) | name )
~ pro_whitespace_omitter? ~ "}}" }
-html_expression_triple_bracket = _{ "{{{" ~ pre_whitespace_omitter? ~
- ((identifier ~ (hash|param)+) | name ) ~
- pro_whitespace_omitter? ~ "}}}" }
+html_expression_triple_bracket_legacy = _{ "{{{" ~ pre_whitespace_omitter? ~
+ ((identifier ~ (hash|param)+) | name ) ~
+ pro_whitespace_omitter? ~ "}}}" }
+html_expression_triple_bracket = _{ "{{" ~ pre_whitespace_omitter? ~ "{" ~
+ ((identifier ~ (hash|param)+) | name ) ~
+ "}" ~ pro_whitespace_omitter? ~ "}}" }
+
amp_expression = _{ "{{" ~ pre_whitespace_omitter? ~ "&" ~ name ~
pro_whitespace_omitter? ~ "}}" }
-html_expression = { html_expression_triple_bracket | amp_expression }
+html_expression = { (html_expression_triple_bracket_legacy | html_expression_triple_bracket)
+ | amp_expression }
decorator_expression = { "{{" ~ pre_whitespace_omitter? ~ "*" ~ exp_line ~
pro_whitespace_omitter? ~ "}}" }
diff --git a/vendor/handlebars/src/grammar.rs b/vendor/handlebars/src/grammar.rs
index 1fd292ce1..6e61cd4e4 100644
--- a/vendor/handlebars/src/grammar.rs
+++ b/vendor/handlebars/src/grammar.rs
@@ -4,26 +4,6 @@
#[grammar = "grammar.pest"]
pub struct HandlebarsParser;
-#[inline]
-pub(crate) fn whitespace_matcher(c: char) -> bool {
- c == ' ' || c == '\t'
-}
-
-#[inline]
-pub(crate) fn newline_matcher(c: char) -> bool {
- c == '\n' || c == '\r'
-}
-
-pub(crate) fn ends_with_empty_line(text: &str) -> bool {
- text.trim_end_matches(whitespace_matcher)
- .ends_with(newline_matcher)
-}
-
-pub(crate) fn starts_with_empty_line(text: &str) -> bool {
- text.trim_start_matches(whitespace_matcher)
- .starts_with(newline_matcher)
-}
-
#[cfg(test)]
mod test {
use super::{HandlebarsParser, Rule};
@@ -191,6 +171,7 @@ mod test {
"{{exp 1}}",
"{{exp \"literal\"}}",
"{{exp \"literal with space\"}}",
+ "{{exp 'literal with space'}}",
r#"{{exp "literal with escape \\\\"}}"#,
"{{exp ref}}",
"{{exp (sub)}}",
@@ -199,6 +180,8 @@ mod test {
"{{exp {}}}",
"{{exp key=1}}",
"{{exp key=ref}}",
+ "{{exp key='literal with space'}}",
+ "{{exp key=\"literal with space\"}}",
"{{exp key=(sub)}}",
"{{exp key=(sub 0)}}",
"{{exp key=(sub 0 key=1)}}",
@@ -225,6 +208,12 @@ mod test {
"{{&html}}",
"{{{html 1}}}",
"{{{html p=true}}}",
+ "{{{~ html}}}",
+ "{{{html ~}}}",
+ "{{{~ html ~}}}",
+ "{{~{ html }~}}",
+ "{{~{ html }}}",
+ "{{{ html }~}}",
];
for i in s.iter() {
assert_rule!(Rule::html_expression, i);
diff --git a/vendor/handlebars/src/helpers/helper_each.rs b/vendor/handlebars/src/helpers/helper_each.rs
index 4b76e7ce7..2be28dc9c 100644
--- a/vendor/handlebars/src/helpers/helper_each.rs
+++ b/vendor/handlebars/src/helpers/helper_each.rs
@@ -18,7 +18,7 @@ fn update_block_context<'reg>(
is_first: bool,
value: &Json,
) {
- if let Some(ref p) = base_path {
+ if let Some(p) = base_path {
if is_first {
*block.base_path_mut() = copy_on_push_vec(p, relative_path);
} else if let Some(ptr) = block.base_path_mut().last_mut() {
@@ -83,7 +83,7 @@ impl HelperDef for EachHelper {
Json::Array(ref list)
if !list.is_empty() || (list.is_empty() && h.inverse().is_none()) =>
{
- let block_context = create_block(&value);
+ let block_context = create_block(value);
rc.push_block(block_context);
let len = list.len();
@@ -100,8 +100,8 @@ impl HelperDef for EachHelper {
block.set_local_var("last", to_json(is_last));
block.set_local_var("index", index.clone());
- update_block_context(block, array_path, i.to_string(), is_first, &v);
- set_block_param(block, h, array_path, &index, &v)?;
+ update_block_context(block, array_path, i.to_string(), is_first, v);
+ set_block_param(block, h, array_path, &index, v)?;
}
t.render(r, ctx, rc, out)?;
@@ -113,28 +113,28 @@ impl HelperDef for EachHelper {
Json::Object(ref obj)
if !obj.is_empty() || (obj.is_empty() && h.inverse().is_none()) =>
{
- let block_context = create_block(&value);
+ let block_context = create_block(value);
rc.push_block(block_context);
- let mut is_first = true;
+ let len = obj.len();
+
let obj_path = value.context_path();
- for (k, v) in obj.iter() {
+ for (i, (k, v)) in obj.iter().enumerate() {
if let Some(ref mut block) = rc.block_mut() {
- let key = to_json(k);
+ let is_first = i == 0usize;
+ let is_last = i == len - 1;
+ let key = to_json(k);
block.set_local_var("first", to_json(is_first));
+ block.set_local_var("last", to_json(is_last));
block.set_local_var("key", key.clone());
- update_block_context(block, obj_path, k.to_string(), is_first, &v);
- set_block_param(block, h, obj_path, &key, &v)?;
+ update_block_context(block, obj_path, k.to_string(), is_first, v);
+ set_block_param(block, h, obj_path, &key, v)?;
}
t.render(r, ctx, rc, out)?;
-
- if is_first {
- is_first = false;
- }
}
rc.pop_block();
@@ -159,7 +159,6 @@ pub static EACH_HELPER: EachHelper = EachHelper;
#[cfg(test)]
mod test {
- use crate::json::value::to_json;
use crate::registry::Registry;
use serde_json::value::Value as Json;
use std::collections::BTreeMap;
@@ -188,7 +187,10 @@ mod test {
)
.is_ok());
assert!(handlebars
- .register_template_string("t1", "{{#each this}}{{@first}}|{{@key}}:{{this}}|{{/each}}")
+ .register_template_string(
+ "t1",
+ "{{#each this}}{{@first}}|{{@last}}|{{@key}}:{{this}}|{{/each}}"
+ )
.is_ok());
let r0 = handlebars.render("t0", &vec![1u16, 2u16, 3u16]);
@@ -199,9 +201,13 @@ mod test {
let mut m: BTreeMap<String, u16> = BTreeMap::new();
m.insert("ftp".to_string(), 21);
+ m.insert("gopher".to_string(), 70);
m.insert("http".to_string(), 80);
let r1 = handlebars.render("t1", &m);
- assert_eq!(r1.ok().unwrap(), "true|ftp:21|false|http:80|".to_string());
+ assert_eq!(
+ r1.ok().unwrap(),
+ "true|false|ftp:21|false|false|gopher:70|false|true|http:80|".to_string()
+ );
}
#[test]
@@ -303,15 +309,15 @@ mod test {
assert!(handlebars
.register_template_string("t0", "{{#each a}}1{{else}}empty{{/each}}")
.is_ok());
- let m1 = btreemap! {
- "a".to_string() => Vec::<String>::new(),
- };
+ let m1 = json!({
+ "a": []
+ });
let r0 = handlebars.render("t0", &m1).unwrap();
assert_eq!(r0, "empty");
- let m2 = btreemap! {
- "b".to_string() => Vec::<String>::new()
- };
+ let m2 = json!({
+ "b": []
+ });
let r1 = handlebars.render("t0", &m2).unwrap();
assert_eq!(r1, "empty");
}
@@ -322,9 +328,9 @@ mod test {
assert!(handlebars
.register_template_string("t0", "{{#each a as |i|}}{{i}}{{/each}}")
.is_ok());
- let m1 = btreemap! {
- "a".to_string() => vec![1,2,3,4,5]
- };
+ let m1 = json!({
+ "a": [1,2,3,4,5]
+ });
let r0 = handlebars.render("t0", &m1).unwrap();
assert_eq!(r0, "12345");
}
@@ -337,10 +343,10 @@ mod test {
{{/each}}";
assert!(handlebars.register_template_string("t0", template).is_ok());
- let m = btreemap! {
- "ftp".to_string() => 21,
- "http".to_string() => 80
- };
+ let m = json!({
+ "ftp": 21,
+ "http": 80
+ });
let r0 = handlebars.render("t0", &m);
assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
}
@@ -354,10 +360,10 @@ mod test {
assert!(handlebars.register_template_string("t0", template).is_ok());
- let m = btreemap! {
- "ftp".to_string() => 21,
- "http".to_string() => 80
- };
+ let m = json!({
+ "ftp": 21,
+ "http": 80
+ });
let r0 = handlebars.render("t0", &m);
assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
}
@@ -371,12 +377,12 @@ mod test {
)
.is_ok());
- let data = btreemap! {
- "a".to_string() => to_json(&btreemap! {
- "b".to_string() => vec![btreemap!{"c".to_string() => vec![1]}]
- }),
- "d".to_string() => to_json(&1)
- };
+ let data = json!({
+ "a": {
+ "b": [{"c": [1]}]
+ },
+ "d": 1
+ });
let r0 = handlebars.render("t0", &data);
assert_eq!(r0.ok().unwrap(), "1".to_string());
@@ -389,10 +395,10 @@ mod test {
{{#if @first}}template<{{/if}}{{this}}{{#if @last}}>{{else}},{{/if}}\
{{/each}}{{/each}}";
assert!(handlebars.register_template_string("t0", template).is_ok());
- let data = btreemap! {
- "typearg".to_string() => vec!["T".to_string()],
- "variant".to_string() => vec!["1".to_string(), "2".to_string()]
- };
+ let data = json!({
+ "typearg": ["T"],
+ "variant": ["1", "2"]
+ });
let r0 = handlebars.render("t0", &data);
assert_eq!(r0.ok().unwrap(), "template<T>template<T>".to_string());
}
diff --git a/vendor/handlebars/src/helpers/helper_if.rs b/vendor/handlebars/src/helpers/helper_if.rs
index 5a6e42fc0..342c74567 100644
--- a/vendor/handlebars/src/helpers/helper_if.rs
+++ b/vendor/handlebars/src/helpers/helper_if.rs
@@ -36,7 +36,7 @@ impl HelperDef for IfHelper {
let tmpl = if value { h.template() } else { h.inverse() };
match tmpl {
- Some(ref t) => t.render(r, ctx, rc, out),
+ Some(t) => t.render(r, ctx, rc, out),
None => Ok(()),
}
}
@@ -137,6 +137,12 @@ mod test {
);
assert_eq!(
+ "yes\r\n",
+ hbs.render_template("{{#if a}}\r\nyes\r\n{{/if}}\r\n", &json!({"a": true}))
+ .unwrap()
+ );
+
+ assert_eq!(
"x\ny",
hbs.render_template("{{#if a}}x{{/if}}\ny", &json!({"a": true}))
.unwrap()
@@ -147,5 +153,37 @@ mod test {
hbs.render_template("{{#if a}}\nx\n{{^}}\ny\n{{/if}}\nz", &json!({"a": false}))
.unwrap()
);
+
+ assert_eq!(
+ r#"yes
+ foo
+ bar
+ baz"#,
+ hbs.render_template(
+ r#"yes
+ {{#if true}}
+ foo
+ bar
+ {{/if}}
+ baz"#,
+ &json!({})
+ )
+ .unwrap()
+ );
+
+ assert_eq!(
+ r#" foo
+ bar
+ baz"#,
+ hbs.render_template(
+ r#" {{#if true}}
+ foo
+ bar
+ {{/if}}
+ baz"#,
+ &json!({})
+ )
+ .unwrap()
+ );
}
}
diff --git a/vendor/handlebars/src/helpers/helper_lookup.rs b/vendor/handlebars/src/helpers/helper_lookup.rs
index bf887debe..8662d55a0 100644
--- a/vendor/handlebars/src/helpers/helper_lookup.rs
+++ b/vendor/handlebars/src/helpers/helper_lookup.rs
@@ -52,8 +52,6 @@ pub static LOOKUP_HELPER: LookupHelper = LookupHelper;
mod test {
use crate::registry::Registry;
- use std::collections::BTreeMap;
-
#[test]
fn test_lookup() {
let mut handlebars = Registry::new();
@@ -67,13 +65,11 @@ mod test {
.register_template_string("t2", "{{lookup kk \"a\"}}")
.is_ok());
- let mut m: BTreeMap<String, Vec<u16>> = BTreeMap::new();
- m.insert("v1".to_string(), vec![1u16, 2u16, 3u16]);
- m.insert("v2".to_string(), vec![9u16, 8u16, 7u16]);
+ let m = json!({"v1": [1,2,3], "v2": [9,8,7]});
- let m2 = btreemap! {
- "kk".to_string() => btreemap!{"a".to_string() => "world".to_string()}
- };
+ let m2 = json!({
+ "kk": {"a": "world"}
+ });
let r0 = handlebars.render("t0", &m);
assert_eq!(r0.ok().unwrap(), "987".to_string());
diff --git a/vendor/handlebars/src/helpers/helper_with.rs b/vendor/handlebars/src/helpers/helper_with.rs
index c4d31cd0e..2ea6bd4f6 100644
--- a/vendor/handlebars/src/helpers/helper_with.rs
+++ b/vendor/handlebars/src/helpers/helper_with.rs
@@ -25,7 +25,7 @@ impl HelperDef for WithHelper {
.ok_or_else(|| RenderError::new("Param not found for helper \"with\""))?;
if param.value().is_truthy(false) {
- let mut block = create_block(&param);
+ let mut block = create_block(param);
if let Some(block_param) = h.block_param() {
let mut params = BlockParams::new();
@@ -60,7 +60,6 @@ pub static WITH_HELPER: WithHelper = WithHelper;
#[cfg(test)]
mod test {
- use crate::json::value::to_json;
use crate::registry::Registry;
#[derive(Serialize)]
@@ -211,12 +210,12 @@ mod test {
assert!(handlebars
.register_template_string("t0", "{{#with a}}{{#with b}}{{../../d}}{{/with}}{{/with}}")
.is_ok());
- let data = btreemap! {
- "a".to_string() => to_json(&btreemap! {
- "b".to_string() => vec![btreemap!{"c".to_string() => vec![1]}]
- }),
- "d".to_string() => to_json(1)
- };
+ let data = json!({
+ "a": {
+ "b": [{"c": [1]}]
+ },
+ "d": 1
+ });
let r0 = handlebars.render("t0", &data);
assert_eq!(r0.ok().unwrap(), "1".to_string());
diff --git a/vendor/handlebars/src/helpers/mod.rs b/vendor/handlebars/src/helpers/mod.rs
index bfd50c1f4..ff5fa2495 100644
--- a/vendor/handlebars/src/helpers/mod.rs
+++ b/vendor/handlebars/src/helpers/mod.rs
@@ -32,7 +32,8 @@ pub type HelperResult = Result<(), RenderError>;
/// ```
/// use handlebars::*;
///
-/// fn upper(h: &Helper<'_, '_>, _: &Handlebars<'_>, _: &Context, rc: &mut RenderContext<'_, '_>, out: &mut Output)
+/// fn upper(h: &Helper<'_, '_>, _: &Handlebars<'_>, _: &Context, rc:
+/// &mut RenderContext<'_, '_>, out: &mut dyn Output)
/// -> HelperResult {
/// // get parameter from helper or throw an error
/// let param = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
diff --git a/vendor/handlebars/src/helpers/scripting.rs b/vendor/handlebars/src/helpers/scripting.rs
index cec3b9763..abd567ae9 100644
--- a/vendor/handlebars/src/helpers/scripting.rs
+++ b/vendor/handlebars/src/helpers/scripting.rs
@@ -98,10 +98,16 @@ mod test {
let ast = engine.compile(&script).unwrap();
let params = vec![PathAndJson::new(None, ScopedJson::Derived(json!(true)))];
- let hash = btreemap! {
- "me" => PathAndJson::new(None, ScopedJson::Derived(json!("no"))),
- "you" => PathAndJson::new(None, ScopedJson::Derived(json!("yes"))),
- };
+
+ let mut hash = BTreeMap::new();
+ hash.insert(
+ "me",
+ PathAndJson::new(None, ScopedJson::Derived(json!("no"))),
+ );
+ hash.insert(
+ "you",
+ PathAndJson::new(None, ScopedJson::Derived(json!("yes"))),
+ );
let result = call_script_helper(&params, &hash, &engine, &ast)
.unwrap()
diff --git a/vendor/handlebars/src/json/path.rs b/vendor/handlebars/src/json/path.rs
index 8270371b2..17a7a91ee 100644
--- a/vendor/handlebars/src/json/path.rs
+++ b/vendor/handlebars/src/json/path.rs
@@ -6,7 +6,7 @@ use pest::Parser;
use crate::error::RenderError;
use crate::grammar::{HandlebarsParser, Rule};
-#[derive(PartialEq, Clone, Debug)]
+#[derive(PartialEq, Eq, Clone, Debug)]
pub enum PathSeg {
Named(String),
Ruled(Rule),
@@ -16,7 +16,7 @@ pub enum PathSeg {
///
/// It can be either a local variable like `@first`, `../@index`,
/// or a normal relative path like `a/b/c`.
-#[derive(PartialEq, Clone, Debug)]
+#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Path {
Relative((Vec<PathSeg>, String)),
Local((usize, String, String)),
diff --git a/vendor/handlebars/src/lib.rs b/vendor/handlebars/src/lib.rs
index 7e0aac830..1f9ab1ed3 100644
--- a/vendor/handlebars/src/lib.rs
+++ b/vendor/handlebars/src/lib.rs
@@ -1,4 +1,4 @@
-#![doc(html_root_url = "https://docs.rs/handlebars/4.1.0")]
+#![doc(html_root_url = "https://docs.rs/handlebars/4.3.3")]
#![cfg_attr(docsrs, feature(doc_cfg))]
//! # Handlebars
//!
@@ -73,7 +73,7 @@
//! Every time I look into a templating system, I will investigate its
//! support for [template inheritance][t].
//!
-//! [t]: https://docs.djangoproject.com/en/1.9/ref/templates/language/#template-inheritance
+//! [t]: https://docs.djangoproject.com/en/3.2/ref/templates/language/#template-inheritance
//!
//! Template include is not sufficient for template reuse. In most cases
//! you will need a skeleton of page as parent (header, footer, etc.), and
@@ -155,7 +155,7 @@
//! use handlebars::Handlebars;
//! use std::collections::BTreeMap;
//!
-//! # fn main() -> Result<(), Box<Error>> {
+//! # fn main() -> Result<(), Box<dyn Error>> {
//! let mut handlebars = Handlebars::new();
//! let source = "hello {{world}}";
//!
@@ -166,6 +166,14 @@
//! # }
//! ```
//!
+//! #### Additional features for loading template from
+//!
+//! * Feature `dir_source` enables template loading
+//! `register_templates_directory` from given directory.
+//! * Feature `rust-embed` enables template loading
+//! `register_embed_templates` from embedded resources in rust struct
+//! generated with `RustEmbed`.
+//!
//! ### Rendering Something
//!
//! Since handlebars is originally based on JavaScript type system. It supports dynamic features like duck-typing, truthy/falsey values. But for a static language like Rust, this is a little difficult. As a solution, we are using the `serde_json::value::Value` internally for data rendering.
@@ -188,7 +196,7 @@
//! age: i16,
//! }
//!
-//! # fn main() -> Result<(), Box<Error>> {
+//! # fn main() -> Result<(), Box<dyn Error>> {
//! let source = "Hello, {{name}}";
//!
//! let mut handlebars = Handlebars::new();
@@ -367,13 +375,8 @@
#[macro_use]
extern crate log;
-#[cfg(test)]
-#[macro_use]
-extern crate maplit;
#[macro_use]
extern crate pest_derive;
-#[macro_use]
-extern crate quick_error;
#[cfg(test)]
#[macro_use]
extern crate serde_derive;
diff --git a/vendor/handlebars/src/macros.rs b/vendor/handlebars/src/macros.rs
index 14cb0152c..bd1eab0bb 100644
--- a/vendor/handlebars/src/macros.rs
+++ b/vendor/handlebars/src/macros.rs
@@ -38,7 +38,7 @@
#[macro_export]
macro_rules! handlebars_helper {
- ($struct_name:ident: |$($name:ident: $tpe:tt),*
+ ($struct_name:ident: |$($name:ident: $tpe:tt$(<$($gen:ty),+>)?),*
$($(,)?{$($hash_name:ident: $hash_tpe:tt=$dft_val:literal),*})?
$($(,)?*$args:ident)?
$($(,)?**$kwargs:ident)?|
@@ -71,11 +71,11 @@ macro_rules! handlebars_helper {
stringify!($struct_name), stringify!($name),
)))
.and_then(|x|
- handlebars_helper!(@as_json_value x, $tpe)
+ handlebars_helper!(@as_json_value x, $tpe$(<$($gen),+>)?)
.ok_or_else(|| $crate::RenderError::new(&format!(
"`{}` helper: Couldn't convert parameter {} to type `{}`. \
It's {:?} as JSON. Got these params: {:?}",
- stringify!($struct_name), stringify!($name), stringify!($tpe),
+ stringify!($struct_name), stringify!($name), stringify!($tpe$(<$($gen),+>)?),
x, h.params(),
)))
)?;
@@ -117,6 +117,7 @@ macro_rules! handlebars_helper {
(@as_json_value $x:ident, bool) => { $x.as_bool() };
(@as_json_value $x:ident, null) => { $x.as_null() };
(@as_json_value $x:ident, Json) => { Some($x) };
+ (@as_json_value $x:ident, $tpe:tt$(<$($gen:ty),+>)?) => { serde_json::from_value::<$tpe$(<$($gen),+>)?>($x.clone()).ok() };
}
#[cfg(feature = "no_logging")]
diff --git a/vendor/handlebars/src/output.rs b/vendor/handlebars/src/output.rs
index f1c5865a5..67e62b849 100644
--- a/vendor/handlebars/src/output.rs
+++ b/vendor/handlebars/src/output.rs
@@ -46,3 +46,9 @@ impl StringOutput {
String::from_utf8(self.buf)
}
}
+
+impl Default for StringOutput {
+ fn default() -> Self {
+ StringOutput::new()
+ }
+}
diff --git a/vendor/handlebars/src/partial.rs b/vendor/handlebars/src/partial.rs
index a472d5d14..bcf9803fd 100644
--- a/vendor/handlebars/src/partial.rs
+++ b/vendor/handlebars/src/partial.rs
@@ -20,7 +20,7 @@ fn find_partial<'reg: 'rc, 'rc: 'a, 'a>(
d: &Decorator<'reg, 'rc>,
name: &str,
) -> Result<Option<Cow<'a, Template>>, RenderError> {
- if let Some(ref partial) = rc.get_partial(name) {
+ if let Some(partial) = rc.get_partial(name) {
return Ok(Some(Cow::Borrowed(partial)));
}
@@ -52,29 +52,38 @@ pub fn expand_partial<'reg: 'rc, 'rc>(
return Err(RenderError::new("Cannot include self in >"));
}
- // if tname == PARTIAL_BLOCK
let partial = find_partial(rc, r, d, tname)?;
if let Some(t) = partial {
// clone to avoid lifetime issue
// FIXME refactor this to avoid
let mut local_rc = rc.clone();
+
+ // if tname == PARTIAL_BLOCK
let is_partial_block = tname == PARTIAL_BLOCK;
+ // add partial block depth there are consecutive partial
+ // blocks in the stack.
if is_partial_block {
local_rc.inc_partial_block_depth();
+ } else {
+ // depth cannot be lower than 0, which is guaranted in the
+ // `dec_partial_block_depth` method
+ local_rc.dec_partial_block_depth();
}
+ let mut block = None;
let mut block_created = false;
- if let Some(ref base_path) = d.param(0).and_then(|p| p.context_path()) {
+ // create context if param given
+ if let Some(base_path) = d.param(0).and_then(|p| p.context_path()) {
// path given, update base_path
- let mut block = BlockContext::new();
- *block.base_path_mut() = base_path.to_vec();
- block_created = true;
- local_rc.push_block(block);
- } else if !d.hash().is_empty() {
- let mut block = BlockContext::new();
+ let mut block_inner = BlockContext::new();
+ *block_inner.base_path_mut() = base_path.to_vec();
+ block = Some(block_inner);
+ }
+
+ if !d.hash().is_empty() {
// hash given, update base_value
let hash_ctx = d
.hash()
@@ -86,9 +95,25 @@ pub fn expand_partial<'reg: 'rc, 'rc>(
local_rc.evaluate2(ctx, &Path::current())?.as_json(),
&hash_ctx,
);
- block.set_base_value(merged_context);
+
+ 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);
+ }
+ }
+
+ 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;
- local_rc.push_block(block);
+ // 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
@@ -96,6 +121,9 @@ pub fn expand_partial<'reg: 'rc, 'rc>(
local_rc.push_partial_block(pb);
}
+ // indent
+ local_rc.set_indent_string(d.indent());
+
let result = t.render(r, ctx, &mut local_rc, out);
// cleanup
@@ -103,10 +131,6 @@ pub fn expand_partial<'reg: 'rc, 'rc>(
local_rc.pop_block();
}
- if is_partial_block {
- local_rc.dec_partial_block_depth();
- }
-
if d.template().is_some() {
local_rc.pop_partial_block();
}
@@ -174,10 +198,7 @@ mod test {
"include navbar".to_string()
);
assert_eq!(
- handlebars
- .render("t6", &btreemap! {"a".to_string() => "2".to_string()})
- .ok()
- .unwrap(),
+ handlebars.render("t6", &json!({"a": "2"})).ok().unwrap(),
"2".to_string()
);
assert_eq!(
@@ -258,6 +279,19 @@ mod test {
}
#[test]
+ fn teset_partial_context_with_both_hash_and_param() {
+ let mut hbs = Registry::new();
+ hbs.register_template_string("one", "This is a test. {{> two this name=\"fred\" }}")
+ .unwrap();
+ hbs.register_template_string("two", "Lets test {{name}} and {{root_name}}")
+ .unwrap();
+ assert_eq!(
+ "This is a test. Lets test fred and tom",
+ hbs.render("one", &json!({"root_name": "tom"})).unwrap()
+ );
+ }
+
+ #[test]
fn test_partial_subexpression_context_hash() {
let mut hbs = Registry::new();
hbs.register_template_string("one", "This is a test. {{> (x @root) name=\"fred\" }}")
@@ -297,7 +331,7 @@ mod test {
}
#[test]
- fn test_nested_partials() {
+ fn test_nested_partial_block() {
let mut handlebars = Registry::new();
let template1 = "<outer>{{> @partial-block }}</outer>";
let template2 = "{{#> t1 }}<inner>{{> @partial-block }}</inner>{{/ t1 }}";
@@ -330,4 +364,293 @@ mod test {
"fruit: carrot,fruit: tomato,"
);
}
+
+ #[test]
+ fn line_stripping_with_inline_and_partial() {
+ let tpl0 = r#"{{#*inline "foo"}}foo
+{{/inline}}
+{{> foo}}
+{{> foo}}
+{{> foo}}"#;
+ let tpl1 = r#"{{#*inline "foo"}}foo{{/inline}}
+{{> foo}}
+{{> foo}}
+{{> foo}}"#;
+
+ let hbs = Registry::new();
+ assert_eq!(
+ r#"foo
+foo
+foo
+"#,
+ hbs.render_template(tpl0, &json!({})).unwrap()
+ );
+ assert_eq!(
+ r#"
+foofoofoo"#,
+ hbs.render_template(tpl1, &json!({})).unwrap()
+ );
+ }
+
+ #[test]
+ fn test_partial_indent() {
+ let outer = r#" {{> inner inner_solo}}
+
+{{#each inners}}
+ {{> inner}}
+{{/each}}
+
+ {{#each inners}}
+ {{> inner}}
+ {{/each}}
+"#;
+ let inner = r#"name: {{name}}
+"#;
+
+ let mut hbs = Registry::new();
+
+ hbs.register_template_string("inner", inner).unwrap();
+ hbs.register_template_string("outer", outer).unwrap();
+
+ let result = hbs
+ .render(
+ "outer",
+ &json!({
+ "inner_solo": {"name": "inner_solo"},
+ "inners": [
+ {"name": "hello"},
+ {"name": "there"}
+ ]
+ }),
+ )
+ .unwrap();
+
+ assert_eq!(
+ result,
+ r#" name: inner_solo
+
+ name: hello
+ name: there
+
+ name: hello
+ name: there
+"#
+ );
+ }
+ // Rule::partial_expression should not trim leading indent by default
+
+ #[test]
+ fn test_partial_prevent_indent() {
+ let outer = r#" {{> inner inner_solo}}
+
+{{#each inners}}
+ {{> inner}}
+{{/each}}
+
+ {{#each inners}}
+ {{> inner}}
+ {{/each}}
+"#;
+ let inner = r#"name: {{name}}
+"#;
+
+ let mut hbs = Registry::new();
+ hbs.set_prevent_indent(true);
+
+ hbs.register_template_string("inner", inner).unwrap();
+ hbs.register_template_string("outer", outer).unwrap();
+
+ let result = hbs
+ .render(
+ "outer",
+ &json!({
+ "inner_solo": {"name": "inner_solo"},
+ "inners": [
+ {"name": "hello"},
+ {"name": "there"}
+ ]
+ }),
+ )
+ .unwrap();
+
+ assert_eq!(
+ result,
+ r#" name: inner_solo
+
+ name: hello
+ name: there
+
+ name: hello
+ name: there
+"#
+ );
+ }
+
+ #[test]
+ fn test_nested_partials() {
+ let mut hb = Registry::new();
+ hb.register_template_string("partial", "{{> @partial-block}}")
+ .unwrap();
+ hb.register_template_string(
+ "index",
+ r#"{{#>partial}}
+ Yo
+ {{#>partial}}
+ Yo 2
+ {{/partial}}
+{{/partial}}"#,
+ )
+ .unwrap();
+ assert_eq!(
+ r#" Yo
+ Yo 2
+"#,
+ hb.render("index", &()).unwrap()
+ );
+
+ hb.register_template_string("partial2", "{{> @partial-block}}")
+ .unwrap();
+ let r2 = hb
+ .render_template(
+ r#"{{#> partial}}
+{{#> partial2}}
+:(
+{{/partial2}}
+{{/partial}}"#,
+ &(),
+ )
+ .unwrap();
+ assert_eq!(":(\n", r2);
+ }
+
+ #[test]
+ fn test_partial_context_issue_495() {
+ let mut hb = Registry::new();
+ hb.register_template_string(
+ "t1",
+ r#"{{~#*inline "displayName"~}}
+Template:{{name}}
+{{/inline}}
+{{#each data as |name|}}
+Name:{{name}}
+{{>displayName name="aaaa"}}
+{{/each}}"#,
+ )
+ .unwrap();
+
+ hb.register_template_string(
+ "t1",
+ r#"{{~#*inline "displayName"~}}
+Template:{{this}}
+{{/inline}}
+{{#each data as |name|}}
+Name:{{name}}
+{{>displayName}}
+{{/each}}"#,
+ )
+ .unwrap();
+
+ let data = json!({
+ "data": ["hudel", "test"]
+ });
+
+ assert_eq!(
+ r#"Name:hudel
+Template:hudel
+Name:test
+Template:test
+"#,
+ hb.render("t1", &data).unwrap()
+ );
+ }
+
+ #[test]
+ fn test_multiline_partial_indent() {
+ let mut hb = Registry::new();
+
+ hb.register_template_string(
+ "t1",
+ r#"{{#*inline "thepartial"}}
+ inner first line
+ inner second line
+{{/inline}}
+ {{> thepartial}}
+outer third line"#,
+ )
+ .unwrap();
+ assert_eq!(
+ r#" inner first line
+ inner second line
+outer third line"#,
+ hb.render("t1", &()).unwrap()
+ );
+
+ hb.register_template_string(
+ "t2",
+ r#"{{#*inline "thepartial"}}inner first line
+inner second line
+{{/inline}}
+ {{> thepartial}}
+outer third line"#,
+ )
+ .unwrap();
+ assert_eq!(
+ r#" inner first line
+ inner second line
+outer third line"#,
+ hb.render("t2", &()).unwrap()
+ );
+
+ hb.register_template_string(
+ "t3",
+ r#"{{#*inline "thepartial"}}{{a}}{{/inline}}
+ {{> thepartial}}
+outer third line"#,
+ )
+ .unwrap();
+ assert_eq!(
+ r#"
+ inner first line
+ inner second lineouter third line"#,
+ hb.render("t3", &json!({"a": "inner first line\ninner second line"}))
+ .unwrap()
+ );
+
+ hb.register_template_string(
+ "t4",
+ r#"{{#*inline "thepartial"}}
+ inner first line
+ inner second line
+{{/inline}}
+ {{~> thepartial}}
+outer third line"#,
+ )
+ .unwrap();
+ assert_eq!(
+ r#" inner first line
+ inner second line
+outer third line"#,
+ hb.render("t4", &()).unwrap()
+ );
+
+ let mut hb2 = Registry::new();
+ hb2.set_prevent_indent(true);
+
+ hb2.register_template_string(
+ "t1",
+ r#"{{#*inline "thepartial"}}
+ inner first line
+ inner second line
+{{/inline}}
+ {{> thepartial}}
+outer third line"#,
+ )
+ .unwrap();
+ assert_eq!(
+ r#" inner first line
+ inner second line
+outer third line"#,
+ hb2.render("t1", &()).unwrap()
+ )
+ }
}
diff --git a/vendor/handlebars/src/registry.rs b/vendor/handlebars/src/registry.rs
index e7f5f1cfd..438f8573c 100644
--- a/vendor/handlebars/src/registry.rs
+++ b/vendor/handlebars/src/registry.rs
@@ -17,12 +17,10 @@ use crate::output::{Output, StringOutput, WriteOutput};
use crate::render::{RenderContext, Renderable};
use crate::sources::{FileSource, Source};
use crate::support::str::{self, StringWriter};
-use crate::template::Template;
+use crate::template::{Template, TemplateOptions};
#[cfg(feature = "dir_source")]
-use std::path;
-#[cfg(feature = "dir_source")]
-use walkdir::{DirEntry, WalkDir};
+use walkdir::WalkDir;
#[cfg(feature = "script_helper")]
use rhai::Engine;
@@ -30,6 +28,9 @@ use rhai::Engine;
#[cfg(feature = "script_helper")]
use crate::helpers::scripting::ScriptHelper;
+#[cfg(feature = "rust-embed")]
+use rust_embed::RustEmbed;
+
/// This type represents an *escape fn*, that is a function whose purpose it is
/// to escape potentially problematic characters in a string.
///
@@ -62,6 +63,7 @@ pub struct Registry<'reg> {
escape_fn: EscapeFn,
strict_mode: bool,
dev_mode: bool,
+ prevent_indent: bool,
#[cfg(feature = "script_helper")]
pub(crate) engine: Arc<Engine>,
@@ -90,21 +92,6 @@ impl<'reg> Default for Registry<'reg> {
}
}
-#[cfg(feature = "dir_source")]
-fn filter_file(entry: &DirEntry, suffix: &str) -> bool {
- let path = entry.path();
-
- // ignore hidden files, emacs buffers and files with wrong suffix
- !path.is_file()
- || path
- .file_name()
- .map(|s| {
- let ds = s.to_string_lossy();
- ds.starts_with('.') || ds.starts_with('#') || !ds.ends_with(suffix)
- })
- .unwrap_or(true)
-}
-
#[cfg(feature = "script_helper")]
fn rhai_engine() -> Engine {
Engine::new()
@@ -120,6 +107,7 @@ impl<'reg> Registry<'reg> {
escape_fn: Arc::new(html_escape),
strict_mode: false,
dev_mode: false,
+ prevent_indent: false,
#[cfg(feature = "script_helper")]
engine: Arc::new(rhai_engine()),
#[cfg(feature = "script_helper")]
@@ -178,7 +166,7 @@ impl<'reg> Registry<'reg> {
/// Return dev mode state, default is false
///
/// With dev mode turned on, handlebars enables a set of development
- /// firendly features, that may affect its performance.
+ /// friendly features, that may affect its performance.
pub fn dev_mode(&self) -> bool {
self.dev_mode
}
@@ -186,9 +174,30 @@ impl<'reg> Registry<'reg> {
/// Enable or disable dev mode
///
/// With dev mode turned on, handlebars enables a set of development
- /// firendly features, that may affect its performance.
+ /// friendly features, that may affect its performance.
+ ///
+ /// **Note that you have to enable dev mode before adding templates to
+ /// the registry**. Otherwise it won't take effect at all.
pub fn set_dev_mode(&mut self, enabled: bool) {
self.dev_mode = enabled;
+
+ // clear template source when disabling dev mode
+ if !enabled {
+ self.template_sources.clear();
+ }
+ }
+
+ /// Enable or disable indent for partial include tag `{{>}}`
+ ///
+ /// By default handlebars keeps indent whitespaces for partial
+ /// include tag, to change this behaviour, set this toggle to `true`.
+ pub fn set_prevent_indent(&mut self, enable: bool) {
+ self.prevent_indent = enable;
+ }
+
+ /// Return state for `prevent_indent` option, default to `false`.
+ pub fn prevent_indent(&self) -> bool {
+ self.prevent_indent
}
/// Register a `Template`
@@ -196,6 +205,9 @@ impl<'reg> Registry<'reg> {
/// This is infallible since the template has already been parsed and
/// insert cannot fail. If there is an existing template with this name it
/// will be overwritten.
+ ///
+ /// Dev mode doesn't apply for pre-compiled template because it's lifecycle
+ /// is not managed by the registry.
pub fn register_template(&mut self, name: &str, tpl: Template) {
self.templates.insert(name.to_string(), tpl);
}
@@ -211,7 +223,13 @@ impl<'reg> Registry<'reg> {
where
S: AsRef<str>,
{
- let template = Template::compile_with_name(tpl_str, name.to_owned())?;
+ let template = Template::compile2(
+ tpl_str.as_ref(),
+ TemplateOptions {
+ name: Some(name.to_owned()),
+ prevent_indent: self.prevent_indent,
+ },
+ )?;
self.register_template(name, template);
Ok(())
}
@@ -227,7 +245,10 @@ impl<'reg> Registry<'reg> {
self.register_template_string(name, partial_str)
}
- /// Register a template from a path
+ /// Register a template from a path on file system
+ ///
+ /// If dev mode is enabled, the registry will keep reading the template file
+ /// from file system everytime it's visited.
pub fn register_template_file<P>(
&mut self,
name: &str,
@@ -262,11 +283,14 @@ impl<'reg> Registry<'reg> {
///
/// This method is not available by default.
/// You will need to enable the `dir_source` feature to use it.
+ ///
+ /// When dev_mode enabled, like `register_template_file`, templates is reloaded
+ /// from file system everytime it's visied.
#[cfg(feature = "dir_source")]
#[cfg_attr(docsrs, doc(cfg(feature = "dir_source")))]
pub fn register_templates_directory<P>(
&mut self,
- tpl_extension: &'static str,
+ tpl_extension: &str,
dir_path: P,
) -> Result<(), TemplateError>
where
@@ -274,40 +298,88 @@ impl<'reg> Registry<'reg> {
{
let dir_path = dir_path.as_ref();
- let prefix_len = if dir_path
- .to_string_lossy()
- .ends_with(|c| c == '\\' || c == '/')
- // `/` will work on windows too so we still need to check
- {
- dir_path.to_string_lossy().len()
- } else {
- dir_path.to_string_lossy().len() + 1
- };
+ // 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(|e| e.is_ok() && !filter_file(e.as_ref().unwrap(), tpl_extension));
+ .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)
+ })
+ // Rejects any hidden or temporary files.
+ .filter(|tpl_path| {
+ tpl_path
+ .file_stem()
+ .map(|stem| stem.to_string_lossy())
+ .map(|stem| !(stem.starts_with('.') || stem.starts_with('#')))
+ .unwrap_or(false)
+ })
+ .filter_map(|tpl_path| {
+ tpl_path
+ .strip_prefix(dir_path)
+ .ok()
+ .map(|tpl_canonical_name| {
+ tpl_canonical_name
+ .with_extension("")
+ .components()
+ .map(|component| component.as_os_str().to_string_lossy())
+ .collect::<Vec<_>>()
+ .join("/")
+ })
+ .map(|tpl_canonical_name| (tpl_canonical_name, tpl_path))
+ });
+
+ for (tpl_canonical_name, tpl_path) in dir_iter {
+ self.register_template_file(&tpl_canonical_name, &tpl_path)?;
+ }
- for entry in dir_iter {
- let entry = entry?;
+ Ok(())
+ }
- let tpl_path = entry.path();
- let tpl_file_path = entry.path().to_string_lossy();
+ /// Register templates using a
+ /// [RustEmbed](https://github.com/pyros2097/rust-embed) type
+ ///
+ /// File names from embed struct are used as template name.
+ ///
+ /// ```skip
+ /// #[derive(RustEmbed)]
+ /// #[folder = "templates"]
+ /// struct Assets;
+ ///
+ /// let mut hbs = Handlebars::new();
+ /// hbs.register_embed_templates::<Assets>();
+ /// ```
+ ///
+ #[cfg(feature = "rust-embed")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "rust-embed")))]
+ pub fn register_embed_templates<E>(&mut self) -> Result<(), TemplateError>
+ where
+ E: RustEmbed,
+ {
+ for item in E::iter() {
+ let file_name = item.as_ref();
+ if let Some(file) = E::get(file_name) {
+ let data = file.data;
- let tpl_name = &tpl_file_path[prefix_len..tpl_file_path.len() - tpl_extension.len()];
- // replace platform path separator with our internal one
- let tpl_canonical_name = tpl_name.replace(path::MAIN_SEPARATOR, "/");
- self.register_template_file(&tpl_canonical_name, &tpl_path)?;
+ let tpl_content = String::from_utf8_lossy(data.as_ref());
+ self.register_template_string(file_name, tpl_content)?;
+ }
}
-
Ok(())
}
/// Remove a template from the registry
pub fn unregister_template(&mut self, name: &str) {
self.templates.remove(name);
+ self.template_sources.remove(name);
}
/// Register a helper
@@ -335,7 +407,6 @@ impl<'reg> Registry<'reg> {
/// (value * 100).to_string() + label
/// ```
///
- ///
#[cfg(feature = "script_helper")]
#[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))]
pub fn register_script_helper(&mut self, name: &str, script: &str) -> Result<(), ScriptError> {
@@ -347,6 +418,9 @@ impl<'reg> Registry<'reg> {
}
/// Register a [rhai](https://docs.rs/rhai/) script from file
+ ///
+ /// When dev mode is enable, script file is reloaded from original file
+ /// everytime it is called.
#[cfg(feature = "script_helper")]
#[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))]
pub fn register_script_helper_file<P>(
@@ -365,6 +439,22 @@ impl<'reg> Registry<'reg> {
self.register_script_helper(name, &script)
}
+ /// Borrow a read-only reference to current rhai engine
+ #[cfg(feature = "script_helper")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))]
+ pub fn engine(&self) -> &Engine {
+ self.engine.as_ref()
+ }
+
+ /// Set a custom rhai engine for the registry.
+ ///
+ /// *Note that* you need to set custom engine before adding scripts.
+ #[cfg(feature = "script_helper")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "script_helper")))]
+ pub fn set_engine(&mut self, engine: Engine) {
+ self.engine = Arc::new(engine);
+ }
+
/// Register a decorator
pub fn register_decorator(
&mut self,
@@ -389,7 +479,7 @@ impl<'reg> Registry<'reg> {
/// Get a reference to the current *escape fn*.
pub fn get_escape_fn(&self) -> &dyn Fn(&str) -> String {
- &*self.escape_fn
+ self.escape_fn.as_ref()
}
/// Return `true` if a template is registered for the given name
@@ -411,7 +501,15 @@ impl<'reg> Registry<'reg> {
let r = source
.load()
.map_err(|e| TemplateError::from((e, name.to_owned())))
- .and_then(|tpl_str| Template::compile_with_name(tpl_str, name.to_owned()))
+ .and_then(|tpl_str| {
+ Template::compile2(
+ tpl_str.as_ref(),
+ TemplateOptions {
+ name: Some(name.to_owned()),
+ prevent_indent: self.prevent_indent,
+ },
+ )
+ })
.map(Cow::Owned)
.map_err(RenderError::from);
Some(r)
@@ -433,6 +531,7 @@ impl<'reg> Registry<'reg> {
}
/// Return a registered helper
+ #[inline]
pub(crate) fn get_or_load_helper(
&'reg self,
name: &str,
@@ -460,6 +559,7 @@ impl<'reg> Registry<'reg> {
}
/// Return a registered decorator
+ #[inline]
pub(crate) fn get_decorator(
&self,
name: &str,
@@ -468,6 +568,10 @@ impl<'reg> Registry<'reg> {
}
/// Return all templates registered
+ ///
+ /// **Note that** in dev mode, the template returned from this method may
+ /// not reflect its latest state. This method doesn't try to reload templates
+ /// from its source.
pub fn get_templates(&self) -> &HashMap<String, Template> {
&self.templates
}
@@ -475,6 +579,7 @@ impl<'reg> Registry<'reg> {
/// Unregister all templates
pub fn clear_templates(&mut self) {
self.templates.clear();
+ self.template_sources.clear();
}
#[inline]
@@ -489,7 +594,7 @@ impl<'reg> Registry<'reg> {
{
self.get_or_load_template(name).and_then(|t| {
let mut render_context = RenderContext::new(t.name.as_ref());
- t.render(self, &ctx, &mut render_context, output)
+ t.render(self, ctx, &mut render_context, output)
})
}
@@ -516,7 +621,7 @@ impl<'reg> Registry<'reg> {
output.into_string().map_err(RenderError::from)
}
- /// Render a registered template and write some data to the `std::io::Write`
+ /// Render a registered template and write data to the `std::io::Write`
pub fn render_to_write<T, W>(&self, name: &str, data: &T, writer: W) -> Result<(), RenderError>
where
T: Serialize,
@@ -527,6 +632,21 @@ impl<'reg> Registry<'reg> {
self.render_to_output(name, &ctx, &mut output)
}
+ /// Render a registered template using reusable `Context`, and write data to
+ /// the `std::io::Write`
+ pub fn render_with_context_to_write<W>(
+ &self,
+ name: &str,
+ ctx: &Context,
+ writer: W,
+ ) -> Result<(), RenderError>
+ where
+ W: Write,
+ {
+ let mut output = WriteOutput::new(writer);
+ self.render_to_output(name, ctx, &mut output)
+ }
+
/// Render a template string using current registry without registering it
pub fn render_template<T>(&self, template_string: &str, data: &T) -> Result<String, RenderError>
where
@@ -537,23 +657,52 @@ impl<'reg> Registry<'reg> {
Ok(writer.into_string())
}
- /// Render a template string using reused context data
+ /// Render a template string using reusable context data
pub fn render_template_with_context(
&self,
template_string: &str,
ctx: &Context,
) -> Result<String, RenderError> {
- let tpl = Template::compile(template_string)?;
+ let tpl = Template::compile2(
+ template_string,
+ TemplateOptions {
+ prevent_indent: self.prevent_indent,
+ ..Default::default()
+ },
+ )?;
let mut out = StringOutput::new();
{
let mut render_context = RenderContext::new(None);
- tpl.render(self, &ctx, &mut render_context, &mut out)?;
+ tpl.render(self, ctx, &mut render_context, &mut out)?;
}
out.into_string().map_err(RenderError::from)
}
+ /// Render a template string using resuable context, and write data into
+ /// `std::io::Write`
+ pub fn render_template_with_context_to_write<W>(
+ &self,
+ template_string: &str,
+ ctx: &Context,
+ writer: W,
+ ) -> Result<(), RenderError>
+ where
+ W: Write,
+ {
+ let tpl = Template::compile2(
+ template_string,
+ TemplateOptions {
+ prevent_indent: self.prevent_indent,
+ ..Default::default()
+ },
+ )?;
+ let mut render_context = RenderContext::new(None);
+ let mut out = WriteOutput::new(writer);
+ tpl.render(self, ctx, &mut render_context, &mut out)
+ }
+
/// Render a template string using current registry without registering it
pub fn render_template_to_write<T, W>(
&self,
@@ -565,11 +714,8 @@ impl<'reg> Registry<'reg> {
T: Serialize,
W: Write,
{
- let tpl = Template::compile(template_string)?;
let ctx = Context::wraps(data)?;
- let mut render_context = RenderContext::new(None);
- let mut out = WriteOutput::new(writer);
- tpl.render(self, &ctx, &mut render_context, &mut out)
+ self.render_template_with_context_to_write(template_string, &ctx, writer)
}
}
@@ -944,8 +1090,7 @@ mod test {
.unwrap();
assert_eq!(
"0123",
- reg.render_with_context("t0", &Context::wraps(&data).unwrap())
- .unwrap()
+ reg.render_with_context("t0", &Context::from(data)).unwrap()
);
}
@@ -1089,4 +1234,17 @@ mod test {
dir.close().unwrap();
}
+
+ #[test]
+ #[cfg(feature = "script_helper")]
+ fn test_engine_access() {
+ use rhai::Engine;
+
+ let mut registry = Registry::new();
+ let mut eng = Engine::new();
+ eng.set_max_string_size(1000);
+ registry.set_engine(eng);
+
+ assert_eq!(1000, registry.engine().max_string_size());
+ }
}
diff --git a/vendor/handlebars/src/render.rs b/vendor/handlebars/src/render.rs
index 188ea221a..036352b3a 100644
--- a/vendor/handlebars/src/render.rs
+++ b/vendor/handlebars/src/render.rs
@@ -14,6 +14,7 @@ use crate::json::value::{JsonRender, PathAndJson, ScopedJson};
use crate::output::{Output, StringOutput};
use crate::partial;
use crate::registry::Registry;
+use crate::support;
use crate::template::TemplateElement::*;
use crate::template::{
BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
@@ -47,10 +48,11 @@ pub struct RenderContextInner<'reg: 'rc, 'rc> {
/// root template name
root_template: Option<&'reg String>,
disable_escape: bool,
+ indent_string: Option<&'reg String>,
}
impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
- /// Create a render context from a `Write`
+ /// Create a render context
pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> {
let inner = Rc::new(RenderContextInner {
partials: BTreeMap::new(),
@@ -60,6 +62,7 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
current_template: None,
root_template,
disable_escape: false,
+ indent_string: None,
});
let mut blocks = VecDeque::with_capacity(5);
@@ -73,7 +76,6 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
}
}
- // TODO: better name
pub(crate) fn new_for_block(&self) -> RenderContext<'reg, 'rc> {
let inner = self.inner.clone();
@@ -101,6 +103,10 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
self.blocks.pop_front();
}
+ pub(crate) fn clear_blocks(&mut self) {
+ self.blocks.clear();
+ }
+
/// Borrow a reference to current block context
pub fn block(&self) -> Option<&BlockContext<'reg>> {
self.blocks.front()
@@ -190,7 +196,19 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
}
pub(crate) fn dec_partial_block_depth(&mut self) {
- self.inner_mut().partial_block_depth -= 1;
+ let depth = &mut self.inner_mut().partial_block_depth;
+ if *depth > 0 {
+ *depth -= 1;
+ }
+ }
+
+ pub(crate) fn set_indent_string(&mut self, indent: Option<&'reg String>) {
+ self.inner_mut().indent_string = indent;
+ }
+
+ #[inline]
+ pub(crate) fn get_indent_string(&self) -> Option<&'reg String> {
+ self.inner.indent_string
}
/// Remove a registered partial
@@ -201,7 +219,7 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
fn get_local_var(&self, level: usize, name: &str) -> Option<&Json> {
self.blocks
.get(level)
- .and_then(|blk| blk.get_local_var(&name))
+ .and_then(|blk| blk.get_local_var(name))
}
/// Test if given template name is current template.
@@ -275,9 +293,10 @@ impl<'reg, 'rc> fmt::Debug for RenderContextInner<'reg, 'rc> {
f.debug_struct("RenderContextInner")
.field("partials", &self.partials)
.field("partial_block_stack", &self.partial_block_stack)
+ .field("partial_block_depth", &self.partial_block_depth)
.field("root_template", &self.root_template)
.field("current_template", &self.current_template)
- .field("disable_eacape", &self.disable_escape)
+ .field("disable_escape", &self.disable_escape)
.finish()
}
}
@@ -435,6 +454,7 @@ pub struct Decorator<'reg, 'rc> {
params: Vec<PathAndJson<'reg, 'rc>>,
hash: BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>,
template: Option<&'reg Template>,
+ indent: Option<&'reg String>,
}
impl<'reg: 'rc, 'rc> Decorator<'reg, 'rc> {
@@ -463,6 +483,7 @@ impl<'reg: 'rc, 'rc> Decorator<'reg, 'rc> {
params: pv,
hash: hm,
template: dt.template.as_ref(),
+ indent: dt.indent.as_ref(),
})
}
@@ -495,6 +516,10 @@ impl<'reg: 'rc, 'rc> Decorator<'reg, 'rc> {
pub fn template(&self) -> Option<&'reg Template> {
self.template
}
+
+ pub fn indent(&self) -> Option<&'reg String> {
+ self.indent
+ }
}
/// Render trait
@@ -747,6 +772,20 @@ pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: S
}
}
+#[inline]
+fn indent_aware_write(
+ v: &str,
+ rc: &mut RenderContext<'_, '_>,
+ out: &mut dyn Output,
+) -> Result<(), RenderError> {
+ if let Some(indent) = rc.get_indent_string() {
+ out.write(support::str::with_indent(v, indent).as_ref())?;
+ } else {
+ out.write(v.as_ref())?;
+ }
+ Ok(())
+}
+
impl Renderable for TemplateElement {
fn render<'reg: 'rc, 'rc>(
&'reg self,
@@ -755,11 +794,8 @@ impl Renderable for TemplateElement {
rc: &mut RenderContext<'reg, 'rc>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
- match *self {
- RawString(ref v) => {
- out.write(v.as_ref())?;
- Ok(())
- }
+ match self {
+ RawString(ref v) => indent_aware_write(v.as_ref(), rc, out),
Expression(ref ht) | HtmlExpression(ref ht) => {
let is_html_expression = matches!(self, HtmlExpression(_));
if is_html_expression {
@@ -789,8 +825,7 @@ impl Renderable for TemplateElement {
} else {
let rendered = context_json.value().render();
let output = do_escape(registry, rc, rendered);
- out.write(output.as_ref())?;
- Ok(())
+ indent_aware_write(output.as_ref(), rc, out)
}
}
} else {
@@ -1117,3 +1152,37 @@ fn test_zero_args_heler() {
"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());
+}
diff --git a/vendor/handlebars/src/support.rs b/vendor/handlebars/src/support.rs
index bd5564d32..b02aca6db 100644
--- a/vendor/handlebars/src/support.rs
+++ b/vendor/handlebars/src/support.rs
@@ -57,6 +57,64 @@ pub mod str {
output
}
+ /// add indent for lines but last
+ pub fn with_indent(s: &str, indent: &str) -> String {
+ let mut output = String::new();
+
+ let mut it = s.chars().peekable();
+ while let Some(c) = it.next() {
+ output.push(c);
+ // check if c is not the last character, we don't append
+ // indent for last line break
+ if c == '\n' && it.peek().is_some() {
+ output.push_str(indent);
+ }
+ }
+
+ output
+ }
+
+ #[inline]
+ pub(crate) fn whitespace_matcher(c: char) -> bool {
+ c == ' ' || c == '\t'
+ }
+
+ #[inline]
+ pub(crate) fn newline_matcher(c: char) -> bool {
+ c == '\n' || c == '\r'
+ }
+
+ #[inline]
+ pub(crate) fn strip_first_newline(s: &str) -> &str {
+ if let Some(s) = s.strip_prefix("\r\n") {
+ s
+ } else if let Some(s) = s.strip_prefix('\n') {
+ s
+ } else {
+ s
+ }
+ }
+
+ pub(crate) fn find_trailing_whitespace_chars(s: &str) -> Option<&str> {
+ let trimmed = s.trim_end_matches(whitespace_matcher);
+ if trimmed.len() == s.len() {
+ None
+ } else {
+ Some(&s[trimmed.len()..])
+ }
+ }
+
+ pub(crate) fn ends_with_empty_line(text: &str) -> bool {
+ let s = text.trim_end_matches(whitespace_matcher);
+ // also matches when text is just whitespaces
+ s.ends_with(newline_matcher) || s.is_empty()
+ }
+
+ pub(crate) fn starts_with_empty_line(text: &str) -> bool {
+ text.trim_start_matches(whitespace_matcher)
+ .starts_with(newline_matcher)
+ }
+
#[cfg(test)]
mod test {
use crate::support::str::StringWriter;
diff --git a/vendor/handlebars/src/template.rs b/vendor/handlebars/src/template.rs
index 87f853799..617f47711 100644
--- a/vendor/handlebars/src/template.rs
+++ b/vendor/handlebars/src/template.rs
@@ -9,23 +9,39 @@ use pest::{Parser, Position, Span};
use serde_json::value::Value as Json;
use crate::error::{TemplateError, TemplateErrorReason};
-use crate::grammar::{self, HandlebarsParser, Rule};
+use crate::grammar::{HandlebarsParser, Rule};
use crate::json::path::{parse_json_path_from_iter, Path};
+use crate::support;
use self::TemplateElement::*;
-#[derive(PartialEq, Clone, Debug)]
+#[derive(PartialEq, Eq, Clone, Debug)]
pub struct TemplateMapping(pub usize, pub usize);
/// A handlebars template
-#[derive(PartialEq, Clone, Debug, Default)]
+#[derive(PartialEq, Eq, Clone, Debug, Default)]
pub struct Template {
pub name: Option<String>,
pub elements: Vec<TemplateElement>,
pub mapping: Vec<TemplateMapping>,
}
-#[derive(PartialEq, Clone, Debug)]
+#[derive(Default)]
+pub(crate) struct TemplateOptions {
+ pub(crate) prevent_indent: bool,
+ pub(crate) name: Option<String>,
+}
+
+impl TemplateOptions {
+ fn name(&self) -> String {
+ self.name
+ .as_ref()
+ .cloned()
+ .unwrap_or_else(|| "Unnamed".to_owned())
+ }
+}
+
+#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Subexpression {
// we use box here avoid resursive struct definition
pub element: Box<TemplateElement>,
@@ -84,13 +100,13 @@ impl Subexpression {
}
}
-#[derive(PartialEq, Clone, Debug)]
+#[derive(PartialEq, Eq, Clone, Debug)]
pub enum BlockParam {
Single(Parameter),
Pair((Parameter, Parameter)),
}
-#[derive(PartialEq, Clone, Debug)]
+#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ExpressionSpec {
pub name: Parameter,
pub params: Vec<Parameter>,
@@ -100,7 +116,7 @@ pub struct ExpressionSpec {
pub omit_pro_ws: bool,
}
-#[derive(PartialEq, Clone, Debug)]
+#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Parameter {
// for helper name only
Name(String),
@@ -110,7 +126,7 @@ pub enum Parameter {
Subexpression(Subexpression),
}
-#[derive(PartialEq, Clone, Debug)]
+#[derive(PartialEq, Eq, Clone, Debug)]
pub struct HelperTemplate {
pub name: Parameter,
pub params: Vec<Parameter>,
@@ -122,6 +138,18 @@ pub struct HelperTemplate {
}
impl HelperTemplate {
+ pub fn new(exp: ExpressionSpec, block: bool) -> HelperTemplate {
+ HelperTemplate {
+ name: exp.name,
+ params: exp.params,
+ hash: exp.hash,
+ block_param: exp.block_param,
+ block,
+ template: None,
+ inverse: None,
+ }
+ }
+
// test only
pub(crate) fn with_path(path: Path) -> HelperTemplate {
HelperTemplate {
@@ -140,12 +168,26 @@ impl HelperTemplate {
}
}
-#[derive(PartialEq, Clone, Debug)]
+#[derive(PartialEq, Eq, Clone, Debug)]
pub struct DecoratorTemplate {
pub name: Parameter,
pub params: Vec<Parameter>,
pub hash: HashMap<String, Parameter>,
pub template: Option<Template>,
+ // for partial indent
+ pub indent: Option<String>,
+}
+
+impl DecoratorTemplate {
+ pub fn new(exp: ExpressionSpec) -> DecoratorTemplate {
+ DecoratorTemplate {
+ name: exp.name,
+ params: exp.params,
+ hash: exp.hash,
+ template: None,
+ indent: None,
+ }
+ }
}
impl Parameter {
@@ -380,34 +422,40 @@ impl Template {
fn remove_previous_whitespace(template_stack: &mut VecDeque<Template>) {
let t = template_stack.front_mut().unwrap();
- if let Some(el) = t.elements.last_mut() {
- if let RawString(ref mut text) = el {
- *text = text.trim_end().to_owned();
- }
+ if let Some(RawString(ref mut text)) = t.elements.last_mut() {
+ *text = text.trim_end().to_owned();
}
}
+ // in handlebars, the whitespaces around statement are
+ // automatically trimed.
+ // this function checks if current span has both leading and
+ // trailing whitespaces, which we treat as a standalone statement.
+ //
+ //
fn process_standalone_statement(
template_stack: &mut VecDeque<Template>,
source: &str,
current_span: &Span<'_>,
+ prevent_indent: bool,
) -> bool {
- let with_trailing_newline = grammar::starts_with_empty_line(&source[current_span.end()..]);
+ let with_trailing_newline =
+ support::str::starts_with_empty_line(&source[current_span.end()..]);
if with_trailing_newline {
let with_leading_newline =
- grammar::ends_with_empty_line(&source[..current_span.start()]);
+ support::str::ends_with_empty_line(&source[..current_span.start()]);
- if with_leading_newline {
+ // prevent_indent: a special toggle for partial expression
+ // (>) that leading whitespaces are kept
+ if prevent_indent && with_leading_newline {
let t = template_stack.front_mut().unwrap();
// check the last element before current
- if let Some(el) = t.elements.last_mut() {
- if let RawString(ref mut text) = el {
- // trim leading space for standalone statement
- *text = text
- .trim_end_matches(grammar::whitespace_matcher)
- .to_owned();
- }
+ if let Some(RawString(ref mut text)) = t.elements.last_mut() {
+ // trim leading space for standalone statement
+ *text = text
+ .trim_end_matches(support::str::whitespace_matcher)
+ .to_owned();
}
}
@@ -453,17 +501,17 @@ impl Template {
if trim_start {
RawString(s.trim_start().to_owned())
} else if trim_start_line {
- RawString(
- s.trim_start_matches(grammar::whitespace_matcher)
- .trim_start_matches(grammar::newline_matcher)
- .to_owned(),
- )
+ let s = s.trim_start_matches(support::str::whitespace_matcher);
+ RawString(support::str::strip_first_newline(s).to_owned())
} else {
RawString(s)
}
}
- pub fn compile<'a>(source: &'a str) -> Result<Template, TemplateError> {
+ pub(crate) fn compile2<'a>(
+ source: &'a str,
+ options: TemplateOptions,
+ ) -> Result<Template, TemplateError> {
let mut helper_stack: VecDeque<HelperTemplate> = VecDeque::new();
let mut decorator_stack: VecDeque<DecoratorTemplate> = VecDeque::new();
let mut template_stack: VecDeque<Template> = VecDeque::new();
@@ -472,14 +520,16 @@ impl Template {
// flag for newline removal of standalone statements
// this option is marked as true when standalone statement is detected
// then the leading whitespaces and newline of next rawstring will be trimed
- let mut trim_line_requiered = false;
+ let mut trim_line_required = false;
let parser_queue = HandlebarsParser::parse(Rule::handlebars, source).map_err(|e| {
let (line_no, col_no) = match e.line_col {
LineColLocation::Pos(line_col) => line_col,
LineColLocation::Span(line_col, _) => line_col,
};
- TemplateError::of(TemplateErrorReason::InvalidSyntax).at(source, line_no, col_no)
+ TemplateError::of(TemplateErrorReason::InvalidSyntax)
+ .at(source, line_no, col_no)
+ .in_template(options.name())
})?;
// dbg!(parser_queue.clone().flatten());
@@ -515,7 +565,7 @@ impl Template {
&source[prev_end..span.start()],
None,
false,
- trim_line_requiered,
+ trim_line_required,
),
line_no,
col_no,
@@ -528,12 +578,15 @@ impl Template {
&source[prev_end..span.start()],
None,
false,
- trim_line_requiered,
+ trim_line_required,
),
line_no,
col_no,
);
}
+
+ // reset standalone statement marker
+ trim_line_required = false;
}
let (line_no, col_no) = span.start_pos().line_col();
@@ -555,14 +608,14 @@ impl Template {
&source[start..span.end()],
Some(pair.clone()),
omit_pro_ws,
- trim_line_requiered,
+ trim_line_required,
),
line_no,
col_no,
);
// reset standalone statement marker
- trim_line_requiered = false;
+ trim_line_required = false;
}
Rule::helper_block_start
| Rule::raw_block_start
@@ -572,24 +625,11 @@ impl Template {
match rule {
Rule::helper_block_start | Rule::raw_block_start => {
- let helper_template = HelperTemplate {
- name: exp.name,
- params: exp.params,
- hash: exp.hash,
- block_param: exp.block_param,
- block: true,
- template: None,
- inverse: None,
- };
+ let helper_template = HelperTemplate::new(exp.clone(), true);
helper_stack.push_front(helper_template);
}
Rule::decorator_block_start | Rule::partial_block_start => {
- let decorator = DecoratorTemplate {
- name: exp.name,
- params: exp.params,
- hash: exp.hash,
- template: None,
- };
+ let decorator = DecoratorTemplate::new(exp.clone());
decorator_stack.push_front(decorator);
}
_ => unreachable!(),
@@ -602,10 +642,11 @@ impl Template {
// standalone statement check, it also removes leading whitespaces of
// previous rawstring when standalone statement detected
- trim_line_requiered = Template::process_standalone_statement(
+ trim_line_required = Template::process_standalone_statement(
&mut template_stack,
source,
&span,
+ true,
);
let t = template_stack.front_mut().unwrap();
@@ -623,10 +664,11 @@ impl Template {
// standalone statement check, it also removes leading whitespaces of
// previous rawstring when standalone statement detected
- trim_line_requiered = Template::process_standalone_statement(
+ trim_line_required = Template::process_standalone_statement(
&mut template_stack,
source,
&span,
+ true,
);
let t = template_stack.pop_front().unwrap();
@@ -640,7 +682,7 @@ impl Template {
span.as_str(),
Some(pair.clone()),
omit_pro_ws,
- trim_line_requiered,
+ trim_line_required,
),
line_no,
col_no,
@@ -664,15 +706,7 @@ impl Template {
match rule {
Rule::expression | Rule::html_expression => {
- let helper_template = HelperTemplate {
- name: exp.name,
- params: exp.params,
- hash: exp.hash,
- block_param: exp.block_param,
- block: false,
- template: None,
- inverse: None,
- };
+ let helper_template = HelperTemplate::new(exp.clone(), false);
let el = if rule == Rule::expression {
Expression(Box::new(helper_template))
} else {
@@ -682,12 +716,30 @@ impl Template {
t.push_element(el, line_no, col_no);
}
Rule::decorator_expression | Rule::partial_expression => {
- let decorator = DecoratorTemplate {
- name: exp.name,
- params: exp.params,
- hash: exp.hash,
- template: None,
- };
+ // do not auto trim ident spaces for
+ // partial_expression(>)
+ let prevent_indent = rule != Rule::partial_expression;
+ trim_line_required = Template::process_standalone_statement(
+ &mut template_stack,
+ source,
+ &span,
+ prevent_indent,
+ );
+
+ // indent for partial expression >
+ let mut indent = None;
+ if rule == Rule::partial_expression
+ && !options.prevent_indent
+ && !exp.omit_pre_ws
+ {
+ indent = support::str::find_trailing_whitespace_chars(
+ &source[..span.start()],
+ );
+ }
+
+ let mut decorator = DecoratorTemplate::new(exp.clone());
+ decorator.indent = indent.map(|s| s.to_owned());
+
let el = if rule == Rule::decorator_expression {
DecoratorExpression(Box::new(decorator))
} else {
@@ -699,10 +751,11 @@ impl Template {
Rule::helper_block_end | Rule::raw_block_end => {
// standalone statement check, it also removes leading whitespaces of
// previous rawstring when standalone statement detected
- trim_line_requiered = Template::process_standalone_statement(
+ trim_line_required = Template::process_standalone_statement(
&mut template_stack,
source,
&span,
+ true,
);
let mut h = helper_stack.pop_front().unwrap();
@@ -723,16 +776,18 @@ impl Template {
exp.name.debug_name(),
),
)
- .at(source, line_no, col_no));
+ .at(source, line_no, col_no)
+ .in_template(options.name()));
}
}
Rule::decorator_block_end | Rule::partial_block_end => {
// standalone statement check, it also removes leading whitespaces of
// previous rawstring when standalone statement detected
- trim_line_requiered = Template::process_standalone_statement(
+ trim_line_required = Template::process_standalone_statement(
&mut template_stack,
source,
&span,
+ true,
);
let mut d = decorator_stack.pop_front().unwrap();
@@ -753,17 +808,19 @@ impl Template {
exp.name.debug_name(),
),
)
- .at(source, line_no, col_no));
+ .at(source, line_no, col_no)
+ .in_template(options.name()));
}
}
_ => unreachable!(),
}
}
Rule::hbs_comment_compact => {
- trim_line_requiered = Template::process_standalone_statement(
+ trim_line_required = Template::process_standalone_statement(
&mut template_stack,
source,
&span,
+ true,
);
let text = span
@@ -774,10 +831,11 @@ impl Template {
t.push_element(Comment(text.to_owned()), line_no, col_no);
}
Rule::hbs_comment => {
- trim_line_requiered = Template::process_standalone_statement(
+ trim_line_required = Template::process_standalone_statement(
&mut template_stack,
source,
&span,
+ true,
);
let text = span
@@ -802,27 +860,36 @@ impl Template {
let t = template_stack.front_mut().unwrap();
t.push_element(RawString(text.to_owned()), line_no, col_no);
}
- let root_template = template_stack.pop_front().unwrap();
+ let mut root_template = template_stack.pop_front().unwrap();
+ root_template.name = options.name;
return Ok(root_template);
}
}
}
+ // These two compile functions are kept for compatibility with 4.x
+ // Template APIs in case that some developers are using them
+ // without registry.
+
+ pub fn compile(source: &str) -> Result<Template, TemplateError> {
+ Self::compile2(source, TemplateOptions::default())
+ }
+
pub fn compile_with_name<S: AsRef<str>>(
source: S,
name: String,
) -> Result<Template, TemplateError> {
- match Template::compile(source.as_ref()) {
- Ok(mut t) => {
- t.name = Some(name);
- Ok(t)
- }
- Err(e) => Err(e.in_template(name)),
- }
+ Self::compile2(
+ source.as_ref(),
+ TemplateOptions {
+ name: Some(name),
+ ..Default::default()
+ },
+ )
}
}
-#[derive(PartialEq, Clone, Debug)]
+#[derive(PartialEq, Eq, Clone, Debug)]
pub enum TemplateElement {
RawString(String),
HtmlExpression(Box<HelperTemplate>),