Repository: mitsuhiko/minijinja
Pull request #556 (2024-08-12 10:08:52)

Added filesizeformat filter


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9efd578..6ce2f4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ All notable changes to MiniJinja are documented here.
 - Fixes a bug where some enums did not deserialize correctly when
   used with `ViaDeserialize`.  #554
 - Implemented `IntoDeserializer` for `Value` and `&Value`.  #555
+- Added `filesizeformat` to minijinja-contrib.  #556
 
 ## 2.1.2
 
diff --git a/minijinja-contrib/src/filters/mod.rs b/minijinja-contrib/src/filters/mod.rs
index ec4ee1f..a2647aa 100644
--- a/minijinja-contrib/src/filters/mod.rs
+++ b/minijinja-contrib/src/filters/mod.rs
@@ -85,3 +85,39 @@ pub fn random(state: &minijinja::State, seq: Value) -> Result<Value, Error> {
         ))
     }
 }
+
+/// Formats the value like a "human-readable" file size.
+///
+/// For example. 13 kB, 4.1 MB, 102 Bytes, etc.  Per default decimal prefixes are
+/// used (Mega, Giga, etc.),  if the second parameter is set to true
+/// the binary prefixes are used (Mebi, Gibi).
+pub fn filesizeformat(value: f64, binary: Option<bool>) -> String {
+    const BIN_PREFIXES: &[&str] = &["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
+    const SI_PREFIXES: &[&str] = &["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
+    let (prefixes, base) = if binary.unwrap_or(false) {
+        (BIN_PREFIXES, 1024.0)
+    } else {
+        (SI_PREFIXES, 1000.0)
+    };
+
+    if value == 1.0 {
+        return "1 Byte".into();
+    }
+    let (sign, value) = if value < 0.0 {
+        ("-", -value)
+    } else {
+        ("", value)
+    };
+
+    if value < base {
+        format!("{}{} Bytes", sign, value)
+    } else {
+        for (idx, prefix) in prefixes.iter().enumerate() {
+            let unit = base.powf(idx as f64 + 2.0);
+            if value < unit || idx == prefixes.len() - 1 {
+                return format!("{}{:.1} {}", sign, base * value / unit, prefix);
+            }
+        }
+        unreachable!();
+    }
+}
diff --git a/minijinja-contrib/src/lib.rs b/minijinja-contrib/src/lib.rs
index 694cad4..c8b6d67 100644
--- a/minijinja-contrib/src/lib.rs
+++ b/minijinja-contrib/src/lib.rs
@@ -33,6 +33,7 @@ pub mod globals;
 /// with the environment.
 pub fn add_to_environment(env: &mut Environment) {
     env.add_filter("pluralize", filters::pluralize);
+    env.add_filter("filesizeformat", filters::filesizeformat);
     #[cfg(feature = "datetime")]
     {
         env.add_filter("datetimeformat", filters::datetimeformat);


Test patch:
diff --git a/minijinja-contrib/tests/filters.rs b/minijinja-contrib/tests/filters.rs
index 860f63f..2edbdba 100644
--- a/minijinja-contrib/tests/filters.rs
+++ b/minijinja-contrib/tests/filters.rs
@@ -104,3 +104,25 @@ fn test_random() {
     insta::assert_snapshot!(render!(in env, r"{% set RAND_SEED = 42 %}{{ [1, 2, 3, 4]|random }}"), @"2");
     insta::assert_snapshot!(render!(in env, r"{% set RAND_SEED = 42 %}{{ 'HelloWorld'|random }}"), @"e");
 }
+
+#[test]
+fn test_filesizeformat() {
+    use minijinja::render;
+    use minijinja_contrib::filters::filesizeformat;
+
+    let mut env = Environment::new();
+    env.add_filter("filesizeformat", filesizeformat);
+
+    insta::assert_snapshot!(render!(in env, r"{{ 0.5|filesizeformat }}"), @"0.5 Bytes");
+    insta::assert_snapshot!(render!(in env, r"{{ 1|filesizeformat }}"), @"1 Byte");
+    insta::assert_snapshot!(render!(in env, r"{{ -1|filesizeformat }}"), @"-1 Bytes");
+    insta::assert_snapshot!(render!(in env, r"{{ 1024|filesizeformat }}"), @"1.0 kB");
+    insta::assert_snapshot!(render!(in env, r"{{ 1024|filesizeformat(true) }}"), @"1.0 KiB");
+    insta::assert_snapshot!(render!(in env, r"{{ 1000|filesizeformat }}"), @"1.0 kB");
+    insta::assert_snapshot!(render!(in env, r"{{ 1000|filesizeformat(true) }}"), @"1000 Bytes");
+    insta::assert_snapshot!(render!(in env, r"{{ (1024 * 1024 * 1024)|filesizeformat }}"), @"1.1 GB");
+    insta::assert_snapshot!(render!(in env, r"{{ (1024 * 1024 * 1024)|filesizeformat(true) }}"), @"1.0 GiB");
+    insta::assert_snapshot!(render!(in env, r"{{ (1024 * 1024 * 1024 * 1024 * 1024)|filesizeformat }}"), @"1.1 PB");
+    insta::assert_snapshot!(render!(in env, r"{{ (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)|filesizeformat }}"), @"1.2 YB");
+    insta::assert_snapshot!(render!(in env, r"{{ (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)|filesizeformat }}"), @"1267650.6 YB");
+}