Repository: mitsuhiko/minijinja
Pull request #41 (2022-01-03 15:14:46)

Added round and abs filters


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 42fc428..12edc61 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ All notable changes to MiniJinja are documented here.
 - Removed value internal distinction between maps and structs.
 - Added `list` filter.
 - Added `first` and `last` filters.
+- Added `round` and `abs` filters.
 - Implemented integer division operator (``//``) and changed division to always
   return floats like documented and to match the Jinja2 implementation.  To
   make this more convenient whole integer floats are now handled like integers in
diff --git a/minijinja/src/filters.rs b/minijinja/src/filters.rs
index 5c22f93..aa264c3 100644
--- a/minijinja/src/filters.rs
+++ b/minijinja/src/filters.rs
@@ -111,6 +111,8 @@ pub(crate) fn get_builtin_filters() -> BTreeMap<&'static str, BoxedFilter> {
         rv.insert("trim", BoxedFilter::new(trim));
         rv.insert("join", BoxedFilter::new(join));
         rv.insert("default", BoxedFilter::new(default));
+        rv.insert("round", BoxedFilter::new(round));
+        rv.insert("abs", BoxedFilter::new(abs));
         rv.insert("first", BoxedFilter::new(first));
         rv.insert("last", BoxedFilter::new(last));
         rv.insert("d", BoxedFilter::new(default));
@@ -310,6 +312,36 @@ mod builtins {
         })
     }
 
+    /// Returns the absolute value of a number.
+    #[cfg_attr(docsrs, doc(cfg(feature = "builtin_filters")))]
+    pub fn abs(_: &State, value: Value) -> Result<Value, Error> {
+        match value.0 {
+            ValueRepr::I64(x) => Ok(Value::from(x.abs())),
+            ValueRepr::I128(x) => Ok(Value::from(x.abs())),
+            ValueRepr::F64(x) => Ok(Value::from(x.abs())),
+            _ => Err(Error::new(
+                ErrorKind::ImpossibleOperation,
+                "cannot round value",
+            )),
+        }
+    }
+
+    /// Round the number to a given precision.
+    #[cfg_attr(docsrs, doc(cfg(feature = "builtin_filters")))]
+    pub fn round(_: &State, value: Value, precision: Option<i32>) -> Result<Value, Error> {
+        match value.0 {
+            ValueRepr::I64(_) | ValueRepr::I128(_) => Ok(value),
+            ValueRepr::F64(val) => {
+                let x = 10f64.powi(precision.unwrap_or(0));
+                Ok(Value::from((x * val).round() / x))
+            }
+            _ => Err(Error::new(
+                ErrorKind::ImpossibleOperation,
+                "cannot round value",
+            )),
+        }
+    }
+
     /// Returns the first item from a list.
     #[cfg_attr(docsrs, doc(cfg(feature = "builtin_filters")))]
     pub fn first(_: &State, value: Value) -> Result<Value, Error> {
diff --git a/minijinja/src/value.rs b/minijinja/src/value.rs
index ab821f2..30222aa 100644
--- a/minijinja/src/value.rs
+++ b/minijinja/src/value.rs
@@ -486,7 +486,13 @@ impl fmt::Display for Value {
             ValueRepr::Bool(val) => write!(f, "{}", val),
             ValueRepr::U64(val) => write!(f, "{}", val),
             ValueRepr::I64(val) => write!(f, "{}", val),
-            ValueRepr::F64(val) => write!(f, "{:.1}", val),
+            ValueRepr::F64(val) => {
+                let mut num = val.to_string();
+                if !num.contains('.') {
+                    num.push_str(".0");
+                }
+                write!(f, "{}", num)
+            }
             ValueRepr::Char(val) => write!(f, "{}", val),
             ValueRepr::None => write!(f, "none"),
             ValueRepr::I128(val) => write!(f, "{}", val),
@@ -1899,3 +1905,9 @@ fn test_key_interning() {
         }
     }
 }
+
+#[test]
+fn test_float_to_string() {
+    assert_eq!(Value::from(42.4242f64).to_string(), "42.4242");
+    assert_eq!(Value::from(42.0f32).to_string(), "42.0");
+}


Test patch:
diff --git a/minijinja/tests/inputs/filters.txt b/minijinja/tests/inputs/filters.txt
index 9f5787e..b46d37f 100644
--- a/minijinja/tests/inputs/filters.txt
+++ b/minijinja/tests/inputs/filters.txt
@@ -48,3 +48,8 @@ batch: {{ range(10)|batch(3) }}
 batch-fill: {{ range(10)|batch(3, '-') }}
 slice: {{ range(10)|slice(3) }}
 slice-fill: {{ range(10)|slice(3, '-') }}
+int-abs: {{ -42|abs }}
+float-abs: {{ -42.5|abs }}
+int-round: {{ 42|round }}
+float-round: {{ 42.5|round }}
+float-round-prec2: {{ 42.512345|round(2) }}
diff --git a/minijinja/tests/snapshots/test_templates__vm@debug.txt.snap b/minijinja/tests/snapshots/test_templates__vm@debug.txt.snap
index ddd7ef2..afce011 100644
--- a/minijinja/tests/snapshots/test_templates__vm@debug.txt.snap
+++ b/minijinja/tests/snapshots/test_templates__vm@debug.txt.snap
@@ -49,6 +49,7 @@ State {
             "undefined",
         ],
         filters: [
+            "abs",
             "batch",
             "count",
             "d",
@@ -64,6 +65,7 @@ State {
             "lower",
             "replace",
             "reverse",
+            "round",
             "safe",
             "slice",
             "title",
diff --git a/minijinja/tests/snapshots/test_templates__vm@filters.txt.snap b/minijinja/tests/snapshots/test_templates__vm@filters.txt.snap
index 2b9f85d..37c7ac3 100644
--- a/minijinja/tests/snapshots/test_templates__vm@filters.txt.snap
+++ b/minijinja/tests/snapshots/test_templates__vm@filters.txt.snap
@@ -49,4 +49,9 @@ batch: [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
 batch-fill: [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, "-", "-"]]
 slice: [[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]
 slice-fill: [[0, 1, 2, 3], [4, 5, 6, "-"], [7, 8, 9, "-"]]
+int-abs: 42
+float-abs: 42.5
+int-round: 42
+float-round: 43.0
+float-round-prec2: 42.51