From 36c1ead7e86ed3812632391a6b43a490405758a7 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Thu, 28 May 2020 18:56:08 -0500 Subject: [PATCH 1/2] Added the dbg and ormat filters --- src/builtins/filters/common.rs | 6 +++ src/builtins/filters/number.rs | 99 ++++++++++++++++++++++++++++++++++ src/tera.rs | 2 + 3 files changed, 107 insertions(+) diff --git a/src/builtins/filters/common.rs b/src/builtins/filters/common.rs index ea3fc1487..cefd9ef8e 100644 --- a/src/builtins/filters/common.rs +++ b/src/builtins/filters/common.rs @@ -145,6 +145,12 @@ pub fn as_str(value: &Value, _: &HashMap) -> Result { to_value(&value.render()).map_err(Error::json) } +/// Prints the passed value to stdout before returning it +pub fn dbg(value: &Value, _: &HashMap) -> Result { + println!("{:#?}", value); + Ok(value.clone()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/builtins/filters/number.rs b/src/builtins/filters/number.rs index c8d5ed10a..a274fc420 100644 --- a/src/builtins/filters/number.rs +++ b/src/builtins/filters/number.rs @@ -74,6 +74,105 @@ pub fn filesizeformat(value: &Value, _: &HashMap) -> Result `2A`) +/// * `:x` Lower hex (`42` => `2a`) +/// * `:o` Octal (`42` => `52`) +/// * `:b` Binary (`42` => `101010`) +/// * `:E` Upper exponent (`42.0` => `4.2E1`) +/// * `:e` Lower exponent (`42.0` => `4.2e1`) +/// +/// Additionally, the `#` modifier can be passed to some formatters as well: +/// * `:#X` Upper hex (`42` => `0x2A`) +/// * `:#x` Lower hex (`42` => `0x2a`) +/// * `:#o` Octal (`42` => `0o52`) +/// * `:#b` Binary (`42` => `0b101010`) +pub fn format(value: &Value, args: &HashMap) -> Result { + let fmt = if let Some(fmt) = args.get("fmt") { + try_get_value!("format", "fmt", String, fmt) + } else { + return Err(Error::msg("Filter `format` expected an arg called `fmt`")); + }; + let mut chars = fmt.chars(); + + if !matches!(chars.next(), Some(':')) { + return Err(Error::msg("Format specifiers for the `format` filter must start with `:`")); + } + + let mut spec = chars.next().ok_or_else(|| { + Error::msg("Format specifiers for the `format` filter must have more than one character") + })?; + + let alternative = if spec == '#' { + spec = chars.next().ok_or_else(|| { + Error::msg("Format strings for the `format` filter with a modifier must have a format specifier") + })?; + true + } else { + false + }; + + macro_rules! unwrap_integers { + ($val:expr, if $alt:ident { $alt_fmt:expr } else { $fmt:expr }, $err:expr) => { + if let Some(uint) = $val.as_u64() { + if $alt { + format!($alt_fmt, uint) + } else { + format!($fmt, uint) + } + } else if let Some(int) = $val.as_i64() { + if $alt { + format!($alt_fmt, int) + } else { + format!($fmt, int) + } + } else { + return Err($err); + } + }; + } + + let value = match spec { + 'X' => unwrap_integers!( + value, + if alternative { "{:#X}" } else { "{:X}" }, + Error::msg("`:X` only takes integer values") + ), + 'x' => unwrap_integers!( + value, + if alternative { "{:#x}" } else { "{:x}" }, + Error::msg("`:x` only takes integer values") + ), + 'o' => unwrap_integers!( + value, + if alternative { "{:#o}" } else { "{:o}" }, + Error::msg("`:o` only takes integer values") + ), + 'b' => unwrap_integers!( + value, + if alternative { "{:#b}" } else { "{:b}" }, + Error::msg("`:b` only takes integer values") + ), + + 'E' => { + let float = try_get_value!("format", "value", f64, value); + format!("{:E}", float) + } + 'e' => { + let float = try_get_value!("format", "value", f64, value); + format!("{:e}", float) + } + + unrecognized => { + return Err(Error::msg(format!("Unrecognized format specifier: `:{}`", unrecognized))) + } + }; + + Ok(Value::String(value)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/tera.rs b/src/tera.rs index 1df6d247f..bdb80e2a3 100644 --- a/src/tera.rs +++ b/src/tera.rs @@ -546,6 +546,7 @@ impl Tera { self.register_filter("pluralize", number::pluralize); self.register_filter("round", number::round); + self.register_filter("format", number::format); #[cfg(feature = "builtins")] self.register_filter("filesizeformat", number::filesizeformat); @@ -556,6 +557,7 @@ impl Tera { 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("dbg", common::dbg); self.register_filter("get", object::get); } From 8bf0cf886779ef7fa195f8de9802ec478a445715 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sun, 31 May 2020 11:39:07 -0500 Subject: [PATCH 2/2] Added tests --- src/builtins/filters/number.rs | 41 +++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/builtins/filters/number.rs b/src/builtins/filters/number.rs index a274fc420..1a620ea97 100644 --- a/src/builtins/filters/number.rs +++ b/src/builtins/filters/number.rs @@ -176,7 +176,7 @@ pub fn format(value: &Value, args: &HashMap) -> Result { #[cfg(test)] mod tests { use super::*; - use serde_json::value::to_value; + use serde_json::{json, value::to_value}; use std::collections::HashMap; #[test] @@ -280,4 +280,43 @@ mod tests { assert!(result.is_ok()); assert_eq!(result.unwrap(), to_value("117.74 MB").unwrap()); } + + #[test] + fn formatting() { + let values = [ + (json!(42), ":X", "2A"), + (json!(42), ":x", "2a"), + (json!(42), ":o", "52"), + (json!(42), ":b", "101010"), + (json!(42.0), ":E", "4.2E1"), + (json!(42.0), ":e", "4.2e1"), + (json!(42), ":#X", "0x2A"), + (json!(42), ":#x", "0x2a"), + (json!(42), ":#o", "0o52"), + (json!(42), ":#b", "0b101010"), + ]; + let mut args = HashMap::new(); + + for (value, fmt, expected) in values.iter() { + args.insert(String::from("fmt"), json!(fmt)); + + let result = format(value, &args); + assert!(result.is_ok()); + assert_eq!(json!(expected), result.unwrap()); + } + } + + #[test] + fn fmt_required() { + let args = HashMap::new(); + assert!(format(&json!("It don't matter"), &args).is_err()); + } + + #[test] + fn unrecognized_formatter() { + let mut args = HashMap::new(); + args.insert(String::from("fmt"), json!("I do not exist")); + + assert!(format(&json!("It don't matter"), &args).is_err()); + } }