From 42c4cb1d877d696bb97aa2e9402429dad42d7d66 Mon Sep 17 00:00:00 2001
From: Andrew Dona-Couch <hi@andrewcou.ch>
Date: Wed, 8 Jul 2020 17:03:02 +0000
Subject: Add fmt filter that swaps the first two arguments to format!().

This allows a more natural filter usage: `{{ val | fmt("{:?}") }}`
as well as enabling convenient filter composition:
`{{ price | to_f64 | fmt("${:.2}") | center }}`
---
 askama_shared/src/filters/mod.rs | 24 +++++++++++++++++++++++-
 askama_shared/src/generator.rs   | 18 ++++++++++++++++++
 2 files changed, 41 insertions(+), 1 deletion(-)

(limited to 'askama_shared')

diff --git a/askama_shared/src/filters/mod.rs b/askama_shared/src/filters/mod.rs
index 2604ed9..7253e4b 100644
--- a/askama_shared/src/filters/mod.rs
+++ b/askama_shared/src/filters/mod.rs
@@ -58,13 +58,14 @@ const ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
 // Askama or should refer to a local `filters` module. It should contain all the
 // filters shipped with Askama, even the optional ones (since optional inclusion
 // in the const vector based on features seems impossible right now).
-pub const BUILT_IN_FILTERS: [&str; 24] = [
+pub const BUILT_IN_FILTERS: [&str; 25] = [
     "abs",
     "capitalize",
     "center",
     "e",
     "escape",
     "filesizeformat",
+    "fmt",
     "format",
     "indent",
     "into_f64",
@@ -137,12 +138,33 @@ pub fn urlencode(s: &dyn fmt::Display) -> Result<String> {
     Ok(utf8_percent_encode(&s, ENCODE_SET).to_string())
 }
 
+/// Formats arguments according to the specified format
+///
+/// The *second* argument to this filter must be a string literal (as in normal
+/// Rust). The two arguments are passed through to the `format!()`
+/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
+/// the Askama code generator, but the order is swapped to support filter
+/// composition.
+///
+/// ```ignore
+/// {{ value | fmt("{:?}") }}
+/// ```
+///
+/// Compare with [format](./fn.format.html).
+pub fn fmt() {}
+
 /// Formats arguments according to the specified format
 ///
 /// The first argument to this filter must be a string literal (as in normal
 /// Rust). All arguments are passed through to the `format!()`
 /// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
 /// the Askama code generator.
+///
+/// ```ignore
+/// {{ "{:?}{:?}" | format(value, other_value) }}
+/// ```
+///
+/// Compare with [fmt](./fn.fmt.html).
 pub fn format() {}
 
 /// Replaces line breaks in plain text with appropriate HTML
diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs
index 82f68ff..e45acb6 100644
--- a/askama_shared/src/generator.rs
+++ b/askama_shared/src/generator.rs
@@ -999,6 +999,9 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
         if name == "format" {
             self._visit_format_filter(buf, args);
             return DisplayWrap::Unwrapped;
+        } else if name == "fmt" {
+            self._visit_fmt_filter(buf, args);
+            return DisplayWrap::Unwrapped;
         } else if name == "join" {
             self._visit_join_filter(buf, args);
             return DisplayWrap::Unwrapped;
@@ -1038,6 +1041,21 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
         buf.write(")");
     }
 
+    fn _visit_fmt_filter(&mut self, buf: &mut Buffer, args: &[Expr]) {
+        buf.write("format!(");
+        if let Some(Expr::StrLit(v)) = args.get(1) {
+            self.visit_str_lit(buf, v);
+            buf.write(", ");
+        } else {
+            panic!("invalid expression type for fmt filter");
+        }
+        self._visit_args(buf, &args[0..1]);
+        if args.len() > 2 {
+            panic!("only two arguments allowed to fmt filter");
+        }
+        buf.write(")");
+    }
+
     // Force type coercion on first argument to `join` filter (see #39).
     fn _visit_join_filter(&mut self, buf: &mut Buffer, args: &[Expr]) {
         buf.write("::askama::filters::join((&");
-- 
cgit