Add num_format filter (#1460)

* Add `num_format` filter for displaying formatted numbers

* Register the filter

* Update docs

* Make `locale` argument required

* Revert "Make `locale` argument required"

This reverts commit 9cdbf285915f0257d1b9157572588b0ce6698a44.

* Pull the default locale from the site config

* Add note about defaults to the docs

* Add missing borrow
This commit is contained in:
Marshall Bowers 2021-06-25 04:07:52 -04:00 committed by GitHub
parent 0a7692ad85
commit 93900fb623
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 3 deletions

28
Cargo.lock generated
View file

@ -57,6 +57,15 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70033777eb8b5124a81a1889416543dddef2de240019b674c81285a2635a7e1e"
[[package]]
name = "arrayvec"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
dependencies = [
"nodrop",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
@ -1163,7 +1172,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"bitflags",
"cfg-if 1.0.0",
"ryu",
@ -1533,6 +1542,12 @@ dependencies = [
"libc",
]
[[package]]
name = "nodrop"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "nom"
version = "5.1.2"
@ -1627,6 +1642,16 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "num-format"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465"
dependencies = [
"arrayvec 0.4.12",
"itoa",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@ -2571,6 +2596,7 @@ dependencies = [
"library",
"mockito",
"nom-bibtex",
"num-format",
"rendering",
"reqwest",
"serde",

View file

@ -12,6 +12,10 @@ pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> {
site.permalinks.clone(),
)?,
);
site.tera.register_filter(
"num_format",
filters::NumFormatFilter::new(&site.config.default_language),
);
site.tera.register_function(
"get_url",

View file

@ -16,6 +16,7 @@ serde_derive = "1"
sha2 = "0.9"
url = "2"
nom-bibtex = "0.3"
num-format = "0.4"
errors = { path = "../errors" }
utils = { path = "../utils" }

View file

@ -6,7 +6,10 @@ use std::path::PathBuf;
use base64::{decode, encode};
use config::Config;
use rendering::{render_content, RenderContext};
use tera::{to_value, try_get_value, Filter as TeraFilter, Result as TeraResult, Tera, Value};
use tera::{
to_value, try_get_value, Error as TeraError, Filter as TeraFilter, Result as TeraResult, Tera,
Value,
};
use crate::load_tera;
@ -72,13 +75,43 @@ pub fn base64_decode<S: BuildHasher>(
Ok(to_value(&String::from_utf8(decode(s.as_bytes()).unwrap()).unwrap()).unwrap())
}
#[derive(Debug)]
pub struct NumFormatFilter {
default_language: String,
}
impl NumFormatFilter {
pub fn new<S: Into<String>>(default_language: S) -> Self {
Self { default_language: default_language.into() }
}
}
impl TeraFilter for NumFormatFilter {
fn filter(&self, value: &Value, args: &HashMap<String, Value>) -> TeraResult<Value> {
use num_format::{Locale, ToFormattedString};
let num = try_get_value!("num_format", "value", i64, value);
let locale = match args.get("locale") {
Some(locale) => try_get_value!("num_format", "locale", String, locale),
None => self.default_language.clone(),
};
let locale = Locale::from_name(&locale).map_err(|_| {
TeraError::msg(format!(
"Filter `num_format` was called with an invalid `locale` argument: `{}`.",
locale
))
})?;
Ok(to_value(num.to_formatted_string(&locale)).unwrap())
}
}
#[cfg(test)]
mod tests {
use std::{collections::HashMap, path::PathBuf};
use tera::{to_value, Filter};
use super::{base64_decode, base64_encode, MarkdownFilter};
use super::{base64_decode, base64_encode, MarkdownFilter, NumFormatFilter};
use config::Config;
#[test]
@ -201,4 +234,44 @@ mod tests {
assert_eq!(result.unwrap(), to_value(expected).unwrap());
}
}
#[test]
fn num_format_filter() {
let tests = vec![
(100, "100"),
(1_000, "1,000"),
(10_000, "10,000"),
(100_000, "100,000"),
(1_000_000, "1,000,000"),
];
for (input, expected) in tests {
let args = HashMap::new();
let result = NumFormatFilter::new("en").filter(&to_value(input).unwrap(), &args);
let result = dbg!(result);
assert!(result.is_ok());
assert_eq!(result.unwrap(), to_value(expected).unwrap());
}
}
#[test]
fn num_format_filter_with_locale() {
let tests = vec![
("en", 1_000_000, "1,000,000"),
("en-IN", 1_000_000, "10,00,000"),
// Note:
// U+202F is the "NARROW NO-BREAK SPACE" code point.
// When displayed to the screen, it looks like a space.
("fr", 1_000_000, "1\u{202f}000\u{202f}000"),
];
for (locale, input, expected) in tests {
let mut args = HashMap::new();
args.insert("locale".to_string(), to_value(locale).unwrap());
let result = NumFormatFilter::new("en").filter(&to_value(input).unwrap(), &args);
let result = dbg!(result);
assert!(result.is_ok());
assert_eq!(result.unwrap(), to_value(expected).unwrap());
}
}
}

View file

@ -81,6 +81,22 @@ Encode the variable to base64.
### base64_decode
Decode the variable from base64.
### num_format
Format a number into its string representation.
```jinja2
{{ 1000000 | num_format }}
<!-- 1,000,000 -->
```
By default this will format the number using the locale set by `config.default_language` in config.toml.
To format a number for a specific locale, you can use the `locale` argument and pass the name of the desired locale:
```jinja2
{{ 1000000 | num_format(locale="en-IN") }}
<!-- 10,00,000 -->
```
## Built-in functions