From 93900fb6232f6d3e944e7ab5758824ca8ce36554 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 25 Jun 2021 04:07:52 -0400 Subject: [PATCH] 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 --- Cargo.lock | 28 ++++++- components/site/src/tpls.rs | 4 + components/templates/Cargo.toml | 1 + components/templates/src/filters.rs | 77 ++++++++++++++++++- .../documentation/templates/overview.md | 16 ++++ 5 files changed, 123 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bcaef05f..c7713075 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/components/site/src/tpls.rs b/components/site/src/tpls.rs index f4daee70..7b4a1409 100644 --- a/components/site/src/tpls.rs +++ b/components/site/src/tpls.rs @@ -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", diff --git a/components/templates/Cargo.toml b/components/templates/Cargo.toml index da64f4a0..66260818 100644 --- a/components/templates/Cargo.toml +++ b/components/templates/Cargo.toml @@ -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" } diff --git a/components/templates/src/filters.rs b/components/templates/src/filters.rs index 0e465540..3595cde0 100644 --- a/components/templates/src/filters.rs +++ b/components/templates/src/filters.rs @@ -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( 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>(default_language: S) -> Self { + Self { default_language: default_language.into() } + } +} + +impl TeraFilter for NumFormatFilter { + fn filter(&self, value: &Value, args: &HashMap) -> TeraResult { + 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()); + } + } } diff --git a/docs/content/documentation/templates/overview.md b/docs/content/documentation/templates/overview.md index 6223b7bf..79ae66de 100644 --- a/docs/content/documentation/templates/overview.md +++ b/docs/content/documentation/templates/overview.md @@ -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 }} + +``` + +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") }} + +``` ## Built-in functions