From 80853d52c840e92595d04eb13c7999d4179657a1 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Mon, 19 Nov 2018 19:37:06 +0100 Subject: [PATCH] Remove make_* functions And add a currently failing test that needs to pass for 1.0 --- CHANGELOG.md | 1 + examples/basic/main.rs | 7 +- src/builtins/filters/mod.rs | 5 +- src/builtins/functions.rs | 27 ++++---- src/builtins/testers.rs | 24 ++++--- src/lib.rs | 2 +- src/renderer/tests/basic.rs | 44 ++++++++---- src/tera.rs | 132 ++++++++++++++++-------------------- 8 files changed, 128 insertions(+), 114 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c17c9d46..aa8ca53a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Now requires Rust 1.30 - Removed error-chain errors and added rich Error enum instead +- Filter, Tester and Function are now traits and now take borrowed values instead of owned ## 0.11.20 (2018-11-14) diff --git a/examples/basic/main.rs b/examples/basic/main.rs index 791b9b5c5..f3bcb0059 100644 --- a/examples/basic/main.rs +++ b/examples/basic/main.rs @@ -6,9 +6,8 @@ extern crate serde_json; use std::collections::HashMap; -use tera::{Tera, Context, make_filter, Result}; -use serde_json::value::{Value, to_value}; - +use serde_json::value::{to_value, Value}; +use tera::{Context, Result, Tera}; lazy_static! { pub static ref TEMPLATES: Tera = { @@ -20,7 +19,7 @@ lazy_static! { } }; tera.autoescape_on(vec!["html", ".sql"]); - tera.register_filter("do_nothing", &make_filter(do_nothing_filter)); + tera.register_filter("do_nothing", do_nothing_filter); tera }; } diff --git a/src/builtins/filters/mod.rs b/src/builtins/filters/mod.rs index f65453ecd..10e4a7ccd 100644 --- a/src/builtins/filters/mod.rs +++ b/src/builtins/filters/mod.rs @@ -15,7 +15,10 @@ pub trait Filter: Sync + Send { fn filter(&self, value: &Value, args: &HashMap) -> Result; } -impl Filter for F where F: Fn(&Value, &HashMap) -> Result + Sync + Send { +impl Filter for F +where + F: Fn(&Value, &HashMap) -> Result + Sync + Send, +{ fn filter(&self, value: &Value, args: &HashMap) -> Result { self(value, args) } diff --git a/src/builtins/functions.rs b/src/builtins/functions.rs index 9587a82ef..716c81a92 100644 --- a/src/builtins/functions.rs +++ b/src/builtins/functions.rs @@ -11,7 +11,10 @@ pub trait Function: Sync + Send { fn call(&self, args: &HashMap) -> Result; } -impl Function for F where F: Fn(&HashMap) -> Result + Sync + Send { +impl Function for F +where + F: Fn(&HashMap) -> Result + Sync + Send, +{ fn call(&self, args: &HashMap) -> Result { self(args) } @@ -23,9 +26,9 @@ pub fn range(args: &HashMap) -> Result { Ok(v) => v, Err(_) => { return Err(Error::msg(format!( - "Global function `range` received start={} but `start` can only be a number", - val - ))) + "Global function `range` received start={} but `start` can only be a number", + val + ))) } }, None => 0, @@ -35,9 +38,9 @@ pub fn range(args: &HashMap) -> Result { Ok(v) => v, Err(_) => { return Err(Error::msg(format!( - "Global function `range` received step_by={} but `step` can only be a number", - val - ))) + "Global function `range` received step_by={} but `step` can only be a number", + val + ))) } }, None => 1, @@ -53,9 +56,7 @@ pub fn range(args: &HashMap) -> Result { } }, None => { - return Err(Error::msg( - "Global function `range` was called without a `end` argument", - )) + return Err(Error::msg("Global function `range` was called without a `end` argument")) } }; @@ -89,7 +90,8 @@ pub fn now(args: &HashMap) -> Result { Some(val) => match from_value::(val.clone()) { Ok(v) => v, Err(_) => return Err(Error::msg(format!( - "Global function `now` received timestamp={} but `timestamp` can only be a boolean", val + "Global function `now` received timestamp={} but `timestamp` can only be a boolean", + val ))), }, None => false, @@ -115,7 +117,8 @@ pub fn throw(args: &HashMap) -> Result { Some(val) => match from_value::(val.clone()) { Ok(v) => Err(Error::msg(v)), Err(_) => Err(Error::msg(format!( - "Global function `throw` received message={} but `message` can only be a string", val + "Global function `throw` received message={} but `message` can only be a string", + val ))), }, None => Err(Error::msg("Global function `throw` was called without a `message` argument")), diff --git a/src/builtins/testers.rs b/src/builtins/testers.rs index da054557c..8968b9aed 100644 --- a/src/builtins/testers.rs +++ b/src/builtins/testers.rs @@ -9,7 +9,10 @@ pub trait Test: Sync + Send { fn test(&self, value: Option<&Value>, args: &[Value]) -> Result; } -impl Test for F where F: Fn(Option<&Value>, &[Value]) -> Result + Sync + Send { +impl Test for F +where + F: Fn(Option<&Value>, &[Value]) -> Result + Sync + Send, +{ fn test(&self, value: Option<&Value>, args: &[Value]) -> Result { self(value, args) } @@ -259,17 +262,17 @@ mod tests { &[to_value("hello").unwrap()], ) .unwrap()); - assert!(!starting_with(Some(&to_value("hello").unwrap()), &[to_value("hi").unwrap()],) - .unwrap()); + assert!( + !starting_with(Some(&to_value("hello").unwrap()), &[to_value("hi").unwrap()],).unwrap() + ); } #[test] fn test_ending_with() { - assert!(ending_with( - Some(&to_value("helloworld").unwrap()), - &[to_value("world").unwrap()], - ) - .unwrap()); + assert!( + ending_with(Some(&to_value("helloworld").unwrap()), &[to_value("world").unwrap()],) + .unwrap() + ); assert!( !ending_with(Some(&to_value("hello").unwrap()), &[to_value("hi").unwrap()],).unwrap() ); @@ -315,7 +318,8 @@ mod tests { assert_eq!(matching(Some(&container), &[needle]).unwrap(), expected); } - assert!(matching(Some(&to_value("").unwrap()), &[to_value("(Invalid regex").unwrap()]) - .is_err()); + assert!( + matching(Some(&to_value("").unwrap()), &[to_value("(Invalid regex").unwrap()]).is_err() + ); } } diff --git a/src/lib.rs b/src/lib.rs index 122b37145..2c81574ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ pub use errors::{Error, ErrorKind, Result}; pub use serde_json::value::{from_value, to_value, Map, Number, Value}; #[doc(hidden)] pub use template::Template; -pub use tera::{Tera, make_test, make_function, make_filter}; +pub use tera::Tera; pub use utils::escape_html; // Exposes the AST if one needs it but changing the AST is not considered diff --git a/src/renderer/tests/basic.rs b/src/renderer/tests/basic.rs index 497b83712..8c6d7accf 100644 --- a/src/renderer/tests/basic.rs +++ b/src/renderer/tests/basic.rs @@ -4,18 +4,24 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use serde_json::Value; +use builtins::functions::Function; use context::Context; use errors::Result; -use tera::{Tera, make_function}; -use builtins::functions::Function; +use tera::{Tera}; use super::Review; fn render_template(content: &str, context: &Context) -> Result { let mut tera = Tera::default(); tera.add_raw_template("hello.html", content).unwrap(); - tera.register_function("get_number", &make_function(|_: &HashMap| Ok(Value::Number(10.into())))); - tera.register_function("get_string", &make_function(|_: &HashMap| Ok(Value::String("Hello".to_string())))); + tera.register_function( + "get_number", + |_: &HashMap| Ok(Value::Number(10.into())), + ); + tera.register_function( + "get_string", + |_: &HashMap| Ok(Value::String("Hello".to_string())), + ); tera.render("hello.html", context) } @@ -655,21 +661,31 @@ fn can_use_concat_to_push_to_array() { assert_eq!(result.unwrap(), "[0, 1, 2, 3, 4]"); } -#[test] -fn stateful_global_fn() { - struct Next(AtomicUsize); +struct Next(AtomicUsize); - impl Function for Next { - fn call(&self, _args: &HashMap) -> Result { - Ok(Value::Number(self.0.fetch_add(1, Ordering::Relaxed).into())) - } +impl Function for Next { + fn call(&self, _args: &HashMap) -> Result { + Ok(Value::Number(self.0.fetch_add(1, Ordering::Relaxed).into())) } +} +lazy_static! { + static ref NEXT_GLOBAL: Next = Next(AtomicUsize::new(1)); +} + +#[test] +fn stateful_global_fn() { let mut tera = Tera::default(); - tera.add_raw_template("fn.html", "

{{ get_next() }}, {{ get_next() }}, {{ get_next() }}...

").unwrap(); - tera.register_function("get_next", &make_function(Next(AtomicUsize::new(1)))); + tera.add_raw_template( + "fn.html", + "

{{ get_next() }}, {{ get_next_borrowed() }}, {{ get_next() }}...

", + ) + .unwrap(); + + tera.register_function("get_next", Next(AtomicUsize::new(1))); + tera.register_function("get_next_borrowed", &NEXT_GLOBAL); let result = tera.render("fn.html", &Context::new()); - assert_eq!(result.unwrap(), "

1, 2, 3...

".to_owned()); + assert_eq!(result.unwrap(), "

1, 1, 2...

".to_owned()); } diff --git a/src/tera.rs b/src/tera.rs index ad7684a8b..15c3a5d56 100644 --- a/src/tera.rs +++ b/src/tera.rs @@ -464,8 +464,8 @@ impl Tera { /// ```rust,ignore /// tera.register_filter("upper", &make_filter_fn(string::upper); /// ``` - pub fn register_filter(&mut self, name: &str, filter: &Arc) { - self.filters.insert(name.to_string(), filter.clone()); + pub fn register_filter(&mut self, name: &str, filter: F) { + self.filters.insert(name.to_string(), Arc::new(filter)); } #[doc(hidden)] @@ -484,8 +484,8 @@ impl Tera { /// ```rust,ignore /// tera.register_tester("odd", testers::odd); /// ``` - pub fn register_tester(&mut self, name: &str, tester: &Arc) { - self.testers.insert(name.to_string(), tester.clone()); + pub fn register_tester(&mut self, name: &str, tester: T) { + self.testers.insert(name.to_string(), Arc::new(tester)); } #[doc(hidden)] @@ -504,67 +504,67 @@ impl Tera { /// ```rust,ignore /// tera.register_function("range", range); /// ``` - pub fn register_function(&mut self, name: &str, function: &Arc) { - self.global_functions.insert(name.to_string(), function.clone()); + pub fn register_function(&mut self, name: &str, function: F) { + self.global_functions.insert(name.to_string(), Arc::new(function)); } fn register_tera_filters(&mut self) { - self.register_filter("upper", &make_filter(string::upper)); - self.register_filter("lower", &make_filter(string::lower)); - self.register_filter("trim", &make_filter(string::trim)); - self.register_filter("truncate", &make_filter(string::truncate)); - self.register_filter("wordcount", &make_filter(string::wordcount)); - self.register_filter("replace", &make_filter(string::replace)); - self.register_filter("capitalize", &make_filter(string::capitalize)); - self.register_filter("title", &make_filter(string::title)); - self.register_filter("striptags", &make_filter(string::striptags)); - self.register_filter("urlencode", &make_filter(string::urlencode)); - self.register_filter("escape", &make_filter(string::escape_html)); - self.register_filter("slugify", &make_filter(string::slugify)); - self.register_filter("addslashes", &make_filter(string::addslashes)); - self.register_filter("split", &make_filter(string::split)); - - self.register_filter("first", &make_filter(array::first)); - self.register_filter("last", &make_filter(array::last)); - self.register_filter("join", &make_filter(array::join)); - self.register_filter("sort", &make_filter(array::sort)); - self.register_filter("slice", &make_filter(array::slice)); - self.register_filter("group_by", &make_filter(array::group_by)); - self.register_filter("filter", &make_filter(array::filter)); - self.register_filter("concat", &make_filter(array::concat)); - - self.register_filter("pluralize", &make_filter(number::pluralize)); - self.register_filter("round", &make_filter(number::round)); - self.register_filter("filesizeformat", &make_filter(number::filesizeformat)); - - self.register_filter("length", &make_filter(common::length)); - self.register_filter("reverse", &make_filter(common::reverse)); - self.register_filter("date", &make_filter(common::date)); - self.register_filter("json_encode", &make_filter(common::json_encode)); - self.register_filter("as_str", &make_filter(common::as_str)); - - self.register_filter("get", &make_filter(object::get)); + self.register_filter("upper", string::upper); + self.register_filter("lower", string::lower); + self.register_filter("trim", string::trim); + self.register_filter("truncate", string::truncate); + self.register_filter("wordcount", string::wordcount); + self.register_filter("replace", string::replace); + self.register_filter("capitalize", string::capitalize); + self.register_filter("title", string::title); + self.register_filter("striptags", string::striptags); + self.register_filter("urlencode", string::urlencode); + self.register_filter("escape", string::escape_html); + self.register_filter("slugify", string::slugify); + self.register_filter("addslashes", string::addslashes); + self.register_filter("split", string::split); + + self.register_filter("first", array::first); + self.register_filter("last", array::last); + self.register_filter("join", array::join); + self.register_filter("sort", array::sort); + self.register_filter("slice", array::slice); + self.register_filter("group_by", array::group_by); + self.register_filter("filter", array::filter); + self.register_filter("concat", array::concat); + + self.register_filter("pluralize", number::pluralize); + self.register_filter("round", number::round); + self.register_filter("filesizeformat", number::filesizeformat); + + self.register_filter("length", common::length); + self.register_filter("reverse", common::reverse); + self.register_filter("date", common::date); + self.register_filter("json_encode", common::json_encode); + self.register_filter("as_str", common::as_str); + + self.register_filter("get", object::get); } fn register_tera_testers(&mut self) { - self.register_tester("defined", &make_test(testers::defined)); - self.register_tester("undefined", &make_test(testers::undefined)); - self.register_tester("odd", &make_test(testers::odd)); - self.register_tester("even", &make_test(testers::even)); - self.register_tester("string", &make_test(testers::string)); - self.register_tester("number", &make_test(testers::number)); - self.register_tester("divisibleby", &make_test(testers::divisible_by)); - self.register_tester("iterable", &make_test(testers::iterable)); - self.register_tester("starting_with", &make_test(testers::starting_with)); - self.register_tester("ending_with", &make_test(testers::ending_with)); - self.register_tester("containing", &make_test(testers::containing)); - self.register_tester("matching", &make_test(testers::matching)); + self.register_tester("defined", testers::defined); + self.register_tester("undefined", testers::undefined); + self.register_tester("odd", testers::odd); + self.register_tester("even", testers::even); + self.register_tester("string", testers::string); + self.register_tester("number", testers::number); + self.register_tester("divisibleby", testers::divisible_by); + self.register_tester("iterable", testers::iterable); + self.register_tester("starting_with", testers::starting_with); + self.register_tester("ending_with", testers::ending_with); + self.register_tester("containing", testers::containing); + self.register_tester("matching", testers::matching); } fn register_tera_functions(&mut self) { - self.register_function("range", &make_function(functions::range)); - self.register_function("now", &make_function(functions::now)); - self.register_function("throw", &make_function(functions::throw)); + self.register_function("range", functions::range); + self.register_function("now", functions::now); + self.register_function("throw", functions::throw); } /// Select which suffix(es) to automatically do HTML escaping on, @@ -710,23 +710,11 @@ impl fmt::Debug for Tera { } } -/// Helper function to create a test -pub fn make_test(t: T) -> Arc { - Arc::new(t) -} - -/// Helper function to create a function -pub fn make_function(g: G) -> Arc { - Arc::new(g) -} - -/// Helper function to create a filter -pub fn make_filter(f: F) -> Arc { - Arc::new(f) -} #[cfg(test)] mod tests { + use std::collections::HashMap; + use super::Tera; use context::Context; use serde_json::{Map as JsonObject, Value as JsonValue}; @@ -938,7 +926,7 @@ mod tests { fn test_extend_new_filter() { let mut my_tera = Tera::default(); let mut framework_tera = Tera::default(); - framework_tera.register_filter("hello", &my_tera.filters["first"]); + framework_tera.register_filter("hello", |_: &JsonValue,_: &HashMap| Ok(JsonValue::Number(10.into()))); my_tera.extend(&framework_tera).unwrap(); assert!(my_tera.filters.contains_key("hello")); } @@ -947,7 +935,7 @@ mod tests { fn test_extend_new_tester() { let mut my_tera = Tera::default(); let mut framework_tera = Tera::default(); - framework_tera.register_tester("hello", &my_tera.testers["divisibleby"]); + framework_tera.register_tester("hello", |_: Option<&JsonValue>, _: &[JsonValue]| Ok(true)); my_tera.extend(&framework_tera).unwrap(); assert!(my_tera.testers.contains_key("hello")); }