Repository: mitsuhiko/minijinja
Pull request #625 (2024-10-31 12:14:43)

Implement sameas test
This adds the missing `sameas` test.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a88e198..1364f8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ All notable changes to MiniJinja are documented here.
 - Added type hints for the Python binding and relaxed maturin constraint.  #590
 - `minijinja-cli` now allows the template name to be set to an empty
   string when `--template` is used, to allow suppliying a data file.  #624
+- Added the missing `sameas` filter from Jinja2.  #625
 
 ## 2.4.0
 
diff --git a/minijinja/src/defaults.rs b/minijinja/src/defaults.rs
index ffc7ba3..20894dd 100644
--- a/minijinja/src/defaults.rs
+++ b/minijinja/src/defaults.rs
@@ -143,6 +143,7 @@ pub(crate) fn get_builtin_tests() -> BTreeMap<Cow<'static, str>, BoxedTest> {
         rv.insert("endingwith".into(), BoxedTest::new(tests::is_endingwith));
         rv.insert("lower".into(), BoxedTest::new(tests::is_lower));
         rv.insert("upper".into(), BoxedTest::new(tests::is_upper));
+        rv.insert("sameas".into(), BoxedTest::new(tests::is_sameas));
 
         // operators
         let is_eq = BoxedTest::new(tests::is_eq);


Test patch:
diff --git a/minijinja/src/tests.rs b/minijinja/src/tests.rs
index f3528a8..088b6f7 100644
--- a/minijinja/src/tests.rs
+++ b/minijinja/src/tests.rs
@@ -565,6 +565,36 @@ mod builtins {
     pub fn is_upper(name: &str) -> bool {
         name.chars().all(|x| x.is_uppercase())
     }
+
+    /// Checks if two values are identical.
+    ///
+    /// This primarily exists for compatibilith with Jinja2.  It can be seen as a much
+    /// stricter comparison than a regular comparison.  The main difference is that
+    /// values that have the same structure but a different internal object will not
+    /// compare equal.
+    ///
+    /// ```jinja
+    /// {{ [1, 2, 3] is sameas([1, 2, 3]) }}
+    ///     -> false
+    ///
+    /// {{ false is sameas(false) }}
+    ///     -> true
+    /// ```
+    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
+    #[cfg(feature = "builtins")]
+    pub fn is_sameas(value: &Value, other: &Value) -> bool {
+        match (value.as_object(), other.as_object()) {
+            (Some(a), Some(b)) => a.is_same_object(b),
+            (None, Some(_)) | (Some(_), None) => false,
+            (None, None) => {
+                if value.kind() != other.kind() || value.is_integer() != other.is_integer() {
+                    false
+                } else {
+                    value == other
+                }
+            }
+        }
+    }
 }
 
 #[cfg(feature = "builtins")]
diff --git a/minijinja/tests/inputs/tests.txt b/minijinja/tests/inputs/tests.txt
index 788eeb2..725a9ee 100644
--- a/minijinja/tests/inputs/tests.txt
+++ b/minijinja/tests/inputs/tests.txt
@@ -48,4 +48,7 @@ is-test: {{ 'safe' is test }} | {{ 'unknown-test' is test }}
 is-boolean: {{ true is boolean }} | {{ 42 is boolean }}
 is-divisibleby: {{ 42 is divisibleby(2) }} | {{ 41 is divisibleby(2) }}
 is-lower: {{ "foo" is lower }} | {{ "FOO" is lower }}
-is-upper: {{ "foo" is upper }} | {{ "FOO" is upper }}
\ No newline at end of file
+is-upper: {{ "foo" is upper }} | {{ "FOO" is upper }}
+seq-same-as: {{ [1, 2, 3] is sameas([1, 2, 3]) }}
+const-same-as: {{ true is sameas(true) }}
+int-same-as: {{ 1 is sameas(1.0) }}
\ No newline at end of file
diff --git a/minijinja/tests/snapshots/test_templates__vm@debug.txt.snap b/minijinja/tests/snapshots/test_templates__vm@debug.txt.snap
index 64ef19d..007fdcb 100644
--- a/minijinja/tests/snapshots/test_templates__vm@debug.txt.snap
+++ b/minijinja/tests/snapshots/test_templates__vm@debug.txt.snap
@@ -64,6 +64,7 @@ State {
             "number",
             "odd",
             "safe",
+            "sameas",
             "sequence",
             "startingwith",
             "string",
diff --git a/minijinja/tests/snapshots/test_templates__vm@tests.txt.snap b/minijinja/tests/snapshots/test_templates__vm@tests.txt.snap
index 2fe0111..4378c3d 100644
--- a/minijinja/tests/snapshots/test_templates__vm@tests.txt.snap
+++ b/minijinja/tests/snapshots/test_templates__vm@tests.txt.snap
@@ -1,6 +1,6 @@
 ---
 source: minijinja/tests/test_templates.rs
-description: "even: {{ two is even }}\nodd: {{ two is odd }}\nundefined: {{ two is undefined }}\ndefined: {{ two is defined }}\nundefined2: {{ ohwell is undefined }}\ndefined2: {{ ohwell is defined }}\nnone: {{ none is none }}\nnot-none: {{ 42 is not none }}\nnumber-int: {{ two is number }}\nnumber-float: {{ two_dot_two is number }}\ninteger-int: {{ 42 is integer }}\ninteger-float: {{ 42.0 is integer }}\nfloat-int: {{ 42 is float }}\nfloat-float: {{ 42.0 is float }}\nnot-seq: {{ two is sequence }}\nseq: {{ seq is sequence }}\nreverse-not-seq: {{ seq|reverse is sequence }}\niterable: {{ seq is iterable }}\niterable-reverse: {{ seq|reverse is iterable }}\nstring-iterable: {{ string is iterable }}\nnot-iterable: {{ two is iterable }}\nnot-map: {{ two is mapping }}\nmap: {{ map is mapping }}\nstring: {{ string is string }}\nnot-string: {{ mapping is string }}\nstarts-with-a: {{ string is startingwith('a') }}\nends-with-ha: {{ string is endingwith('ha') }}\nnot-safe: {{ \"foo\" is safe }}\nsafe: {{ \"foo\"|escape is safe }}\nis-true: {{ true is true }} | {{ 42 is true }}\nis-false: {{ false is false }} | {{ 0 is false }}\nis-filter: {{ 'escape' is filter }} | {{ 'unknown-filter' is filter }}\nis-test: {{ 'safe' is test }} | {{ 'unknown-test' is test }}\nis-boolean: {{ true is boolean }} | {{ 42 is boolean }}\nis-divisibleby: {{ 42 is divisibleby(2) }} | {{ 41 is divisibleby(2) }}\nis-lower: {{ \"foo\" is lower }} | {{ \"FOO\" is lower }}\nis-upper: {{ \"foo\" is upper }} | {{ \"FOO\" is upper }}"
+description: "even: {{ two is even }}\nodd: {{ two is odd }}\nundefined: {{ two is undefined }}\ndefined: {{ two is defined }}\nundefined2: {{ ohwell is undefined }}\ndefined2: {{ ohwell is defined }}\nnone: {{ none is none }}\nnot-none: {{ 42 is not none }}\nnumber-int: {{ two is number }}\nnumber-float: {{ two_dot_two is number }}\ninteger-int: {{ 42 is integer }}\ninteger-float: {{ 42.0 is integer }}\nfloat-int: {{ 42 is float }}\nfloat-float: {{ 42.0 is float }}\nnot-seq: {{ two is sequence }}\nseq: {{ seq is sequence }}\nreverse-not-seq: {{ seq|reverse is sequence }}\niterable: {{ seq is iterable }}\niterable-reverse: {{ seq|reverse is iterable }}\nstring-iterable: {{ string is iterable }}\nnot-iterable: {{ two is iterable }}\nnot-map: {{ two is mapping }}\nmap: {{ map is mapping }}\nstring: {{ string is string }}\nnot-string: {{ mapping is string }}\nstarts-with-a: {{ string is startingwith('a') }}\nends-with-ha: {{ string is endingwith('ha') }}\nnot-safe: {{ \"foo\" is safe }}\nsafe: {{ \"foo\"|escape is safe }}\nis-true: {{ true is true }} | {{ 42 is true }}\nis-false: {{ false is false }} | {{ 0 is false }}\nis-filter: {{ 'escape' is filter }} | {{ 'unknown-filter' is filter }}\nis-test: {{ 'safe' is test }} | {{ 'unknown-test' is test }}\nis-boolean: {{ true is boolean }} | {{ 42 is boolean }}\nis-divisibleby: {{ 42 is divisibleby(2) }} | {{ 41 is divisibleby(2) }}\nis-lower: {{ \"foo\" is lower }} | {{ \"FOO\" is lower }}\nis-upper: {{ \"foo\" is upper }} | {{ \"FOO\" is upper }}\nseq-same-as: {{ [1, 2, 3] is sameas([1, 2, 3]) }}\nconst-same-as: {{ true is sameas(true) }}\nint-same-as: {{ 1 is sameas(1.0) }}"
 info:
   two: 2
   two_dot_two: 2.2
@@ -50,3 +50,6 @@ is-boolean: true | false
 is-divisibleby: true | false
 is-lower: true | false
 is-upper: false | true
+seq-same-as: false
+const-same-as: true
+int-same-as: false