Skip to content

Commit a9bf11b

Browse files
Merge pull request #216 from GuillaumeGomez/improve-filesizeformat
Relax `filesizeformat` filter requirements
2 parents a8e9222 + 2062ce4 commit a9bf11b

File tree

12 files changed

+104
-31
lines changed

12 files changed

+104
-31
lines changed

rinja/Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ rustdoc-args = ["--generate-link-to-definition", "--cfg=docsrs"]
2121
maintenance = { status = "actively-developed" }
2222

2323
[features]
24-
default = ["config", "humansize", "urlencode"]
24+
default = ["config", "urlencode"]
2525
full = ["default", "code-in-doc", "serde_json"]
2626
code-in-doc = ["rinja_derive/code-in-doc"]
2727
config = ["rinja_derive/config"]
28-
humansize = ["rinja_derive/humansize", "dep:humansize"]
2928
serde_json = ["rinja_derive/serde_json", "dep:serde", "dep:serde_json"]
3029
urlencode = ["rinja_derive/urlencode", "dep:percent-encoding"]
3130

@@ -39,7 +38,6 @@ with-warp = ["rinja_derive/with-warp"]
3938
[dependencies]
4039
rinja_derive = { version = "=0.3.5", path = "../rinja_derive" }
4140

42-
humansize = { version = "2", optional = true }
4341
num-traits = { version = "0.2.6", optional = true }
4442
percent-encoding = { version = "2.1.0", optional = true }
4543
serde = { version = "1.0", optional = true }

rinja/src/filters/humansize.rs

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use std::convert::Infallible;
22
use std::fmt;
3+
use std::mem::MaybeUninit;
4+
use std::str::from_utf8_unchecked;
35

4-
use humansize::{DECIMAL, ISizeFormatter, ToF64};
6+
use super::FastWritable;
57

68
/// Returns adequate string representation (in KB, ..) of number of bytes
79
///
@@ -21,24 +23,95 @@ use humansize::{DECIMAL, ISizeFormatter, ToF64};
2123
/// assert_eq!(tmpl.to_string(), "Filesize: 1.23 MB.");
2224
/// ```
2325
#[inline]
24-
pub fn filesizeformat(b: &impl ToF64) -> Result<FilesizeFormatFilter, Infallible> {
25-
Ok(FilesizeFormatFilter(b.to_f64()))
26+
pub fn filesizeformat(b: f32) -> Result<FilesizeFormatFilter, Infallible> {
27+
Ok(FilesizeFormatFilter(b))
2628
}
2729

2830
#[derive(Debug, Clone, Copy)]
29-
pub struct FilesizeFormatFilter(f64);
31+
pub struct FilesizeFormatFilter(f32);
3032

3133
impl fmt::Display for FilesizeFormatFilter {
34+
#[inline]
3235
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33-
f.write_fmt(format_args!("{}", ISizeFormatter::new(self.0, &DECIMAL)))
36+
self.write_into(f)
3437
}
3538
}
3639

40+
impl FastWritable for FilesizeFormatFilter {
41+
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> fmt::Result {
42+
if self.0 < 1e3 {
43+
(self.0 as u32).write_into(dest)?;
44+
dest.write_str(" B")
45+
} else if let Some((prefix, factor)) = SI_PREFIXES
46+
.iter()
47+
.copied()
48+
.find_map(|(prefix_factor, max)| (self.0 < max).then_some(prefix_factor))
49+
{
50+
// u32 is big enough to hold the number 999_999
51+
let scaled = (self.0 * factor) as u32;
52+
(scaled / 100).write_into(dest)?;
53+
dest.write_str(format_frac(&mut MaybeUninit::uninit(), prefix, scaled))
54+
} else {
55+
too_big(self.0, dest)
56+
}
57+
}
58+
}
59+
60+
/// Formats `buffer` to contain the decimal point, decimal places and unit
61+
fn format_frac(buffer: &mut MaybeUninit<[u8; 8]>, prefix: u8, scaled: u32) -> &str {
62+
// LLVM generates better byte code for register sized buffers, so we add some NULs
63+
let buffer = buffer.write(*b"..0 kB\0\0");
64+
buffer[4] = prefix;
65+
66+
let frac = scaled % 100;
67+
let buffer = if frac == 0 {
68+
&buffer[3..6]
69+
} else if frac % 10 == 0 {
70+
// the decimal separator '.' is already contained in buffer[1]
71+
buffer[2] = b'0' + (frac / 10) as u8;
72+
&buffer[1..6]
73+
} else {
74+
// the decimal separator '.' is already contained in buffer[0]
75+
buffer[1] = b'0' + (frac / 10) as u8;
76+
buffer[2] = b'0' + (frac % 10) as u8;
77+
&buffer[0..6]
78+
};
79+
// SAFETY: we know that the buffer contains only ASCII data
80+
unsafe { from_utf8_unchecked(buffer) }
81+
}
82+
83+
#[cold]
84+
fn too_big<W: fmt::Write + ?Sized>(value: f32, dest: &mut W) -> fmt::Result {
85+
// the value exceeds 999 QB, so we omit the decimal places
86+
write!(dest, "{:.0} QB", value / 1e30)
87+
}
88+
89+
/// `((si_prefix, factor), limit)`, the factor is offset by 10**2 to account for 2 decimal places
90+
const SI_PREFIXES: &[((u8, f32), f32)] = &[
91+
((b'k', 1e-1), 1e6),
92+
((b'M', 1e-4), 1e9),
93+
((b'G', 1e-7), 1e12),
94+
((b'T', 1e-10), 1e15),
95+
((b'P', 1e-13), 1e18),
96+
((b'E', 1e-16), 1e21),
97+
((b'Z', 1e-19), 1e24),
98+
((b'Y', 1e-22), 1e27),
99+
((b'R', 1e-25), 1e30),
100+
((b'Q', 1e-28), 1e33),
101+
];
102+
37103
#[test]
104+
#[allow(clippy::needless_borrows_for_generic_args)]
38105
fn test_filesizeformat() {
39-
assert_eq!(filesizeformat(&0).unwrap().to_string(), "0 B");
40-
assert_eq!(filesizeformat(&999u64).unwrap().to_string(), "999 B");
41-
assert_eq!(filesizeformat(&1000i32).unwrap().to_string(), "1 kB");
42-
assert_eq!(filesizeformat(&1023).unwrap().to_string(), "1.02 kB");
43-
assert_eq!(filesizeformat(&1024usize).unwrap().to_string(), "1.02 kB");
106+
assert_eq!(filesizeformat(0.).unwrap().to_string(), "0 B");
107+
assert_eq!(filesizeformat(999.).unwrap().to_string(), "999 B");
108+
assert_eq!(filesizeformat(1000.).unwrap().to_string(), "1 kB");
109+
assert_eq!(filesizeformat(1023.).unwrap().to_string(), "1.02 kB");
110+
assert_eq!(filesizeformat(1024.).unwrap().to_string(), "1.02 kB");
111+
assert_eq!(filesizeformat(1100.).unwrap().to_string(), "1.1 kB");
112+
assert_eq!(filesizeformat(9_499_014.).unwrap().to_string(), "9.49 MB");
113+
assert_eq!(
114+
filesizeformat(954_548_589.2).unwrap().to_string(),
115+
"954.54 MB"
116+
);
44117
}

rinja/src/filters/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
1313
mod builtin;
1414
mod escape;
15-
#[cfg(feature = "humansize")]
1615
mod humansize;
1716
#[cfg(feature = "serde_json")]
1817
mod json;
@@ -27,7 +26,6 @@ pub use self::escape::{
2726
AutoEscape, AutoEscaper, Escaper, FastWritable, Html, HtmlSafe, HtmlSafeOutput, MaybeSafe,
2827
Safe, Text, Unsafe, Writable, WriteWritable, e, escape, safe,
2928
};
30-
#[cfg(feature = "humansize")]
3129
pub use self::humansize::filesizeformat;
3230
#[cfg(feature = "serde_json")]
3331
pub use self::json::{AsIndent, json, json_pretty};

rinja/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pub use rinja_derive::Template;
7272
#[doc(hidden)]
7373
pub use crate as shared;
7474
pub use crate::error::{Error, Result};
75+
pub use crate::helpers::PrimitiveType;
7576

7677
/// Main `Template` trait; implementations are generally derived
7778
///

rinja_actix/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ default = ["rinja/default"]
2424
full = ["rinja/full"]
2525
code-in-doc = ["rinja/code-in-doc"]
2626
config = ["rinja/config"]
27-
humansize = ["rinja/humansize"]
2827
serde_json = ["rinja/serde_json"]
2928
urlencode = ["rinja/urlencode"]
3029

rinja_axum/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ default = ["rinja/default"]
2424
full = ["rinja/full"]
2525
code-in-doc = ["rinja/code-in-doc"]
2626
config = ["rinja/config"]
27-
humansize = ["rinja/humansize"]
2827
serde_json = ["rinja/serde_json"]
2928
urlencode = ["rinja/urlencode"]
3029

rinja_derive/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ proc-macro = true
2020
[features]
2121
code-in-doc = ["dep:pulldown-cmark"]
2222
config = ["dep:serde", "dep:basic-toml", "parser/config"]
23-
humansize = []
2423
urlencode = []
2524
serde_json = []
2625
with-actix-web = []

rinja_derive/src/generator.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,21 +1657,15 @@ impl<'a> Generator<'a> {
16571657
buf: &mut Buffer,
16581658
name: &str,
16591659
args: &[WithSpan<'_, Expr<'_>>],
1660-
node: &WithSpan<'_, T>,
1660+
_node: &WithSpan<'_, T>,
16611661
) -> Result<DisplayWrap, CompileError> {
1662-
if cfg!(not(feature = "humansize")) {
1663-
return Err(ctx.generate_error(
1664-
&format!("the `{name}` filter requires the `humansize` feature to be enabled"),
1665-
node,
1666-
));
1667-
}
1668-
16691662
// All filters return numbers, and any default formatted number is HTML safe.
16701663
buf.write(format_args!(
1671-
"rinja::filters::HtmlSafeOutput(rinja::filters::{name}(",
1664+
"rinja::filters::HtmlSafeOutput(rinja::filters::{name}(\
1665+
rinja::helpers::get_primitive_value(&("
16721666
));
16731667
self._visit_args(ctx, buf, args)?;
1674-
buf.write(")?)");
1668+
buf.write(")) as rinja::helpers::core::primitive::f32)?)");
16751669
Ok(DisplayWrap::Unwrapped)
16761670
}
16771671

rinja_derive_standalone/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ __standalone = []
2323

2424
code-in-doc = ["dep:pulldown-cmark"]
2525
config = ["dep:serde", "dep:basic-toml", "parser/config"]
26-
humansize = []
2726
urlencode = []
2827
serde_json = []
2928
with-actix-web = []

rinja_rocket/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ default = ["rinja/default"]
2424
full = ["rinja/full"]
2525
code-in-doc = ["rinja/code-in-doc"]
2626
config = ["rinja/config"]
27-
humansize = ["rinja/humansize"]
2827
serde_json = ["rinja/serde_json"]
2928
urlencode = ["rinja/urlencode"]
3029

rinja_warp/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ default = ["rinja/default"]
2424
full = ["rinja/full"]
2525
code-in-doc = ["rinja/code-in-doc"]
2626
config = ["rinja/config"]
27-
humansize = ["rinja/humansize"]
2827
serde_json = ["rinja/serde_json"]
2928
urlencode = ["rinja/urlencode"]
3029

testing/tests/filters.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,3 +427,18 @@ fn test_linebreaks() {
427427
"<p>&#60;script&#62;<br/>alert(&#39;Hello, world!&#39;)<br/>&#60;/script&#62;</p>",
428428
);
429429
}
430+
431+
// Regression tests for <https://github.com/rinja-rs/rinja/issues/215>.
432+
#[test]
433+
fn test_filesizeformat() {
434+
#[derive(Template)]
435+
#[template(
436+
source = r#"{% if let Some(x) = s %}{{x|filesizeformat}}{% endif %}"#,
437+
ext = "html"
438+
)]
439+
struct S {
440+
s: Option<u32>,
441+
}
442+
443+
assert_eq!(S { s: Some(12) }.render().unwrap(), "12 B");
444+
}

0 commit comments

Comments
 (0)