From 84a6bc8440f0eb2695bd8e88ded0f3f3c9b08f78 Mon Sep 17 00:00:00 2001 From: vallentin Date: Sun, 21 Apr 2024 04:02:25 +0200 Subject: [PATCH 1/3] Implemented display_some and display_some_or filters --- askama/src/filters/mod.rs | 48 +++++++++++++++++++++++++++++++++++++++ askama_derive/src/lib.rs | 2 ++ 2 files changed, 50 insertions(+) diff --git a/askama/src/filters/mod.rs b/askama/src/filters/mod.rs index 04a6948c3..ed036eb0b 100644 --- a/askama/src/filters/mod.rs +++ b/askama/src/filters/mod.rs @@ -336,6 +336,54 @@ pub fn wordcount(s: T) -> Result { Ok(s.split_whitespace().count()) } +pub struct DisplaySome<'a, T>(Option<&'a T>); + +impl fmt::Display for DisplaySome<'_, T> +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(val) = self.0 { + write!(f, "{val}")?; + } + Ok(()) + } +} + +pub fn display_some(value: &Option) -> Result> +where + T: fmt::Display, +{ + Ok(DisplaySome(value.as_ref())) +} + +pub struct DisplaySomeOr<'a, T, U>(Option<&'a T>, U); + +impl fmt::Display for DisplaySomeOr<'_, T, U> +where + T: fmt::Display, + U: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(val) = self.0 { + write!(f, "{val}") + } else { + write!(f, "{}", self.1) + } + } +} + +pub fn display_some_or<'a, T, U>( + value: &'a Option, + otherwise: U, +) -> Result> +where + T: fmt::Display, + U: fmt::Display + 'a, +{ + Ok(DisplaySomeOr(value.as_ref(), otherwise)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 2f1626a92..2e158de25 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -142,6 +142,8 @@ const BUILT_IN_FILTERS: &[&str] = &[ "abs", "capitalize", "center", + "display_some", + "display_some_or", "e", "escape", "filesizeformat", From 21ae712414933103daa783d55dfcc817c9646e5f Mon Sep 17 00:00:00 2001 From: vallentin Date: Sun, 21 Apr 2024 04:11:24 +0200 Subject: [PATCH 2/3] Added display_some and display_some_or tests --- askama/src/filters/mod.rs | 39 +++++++++++++++++++++ testing/tests/filters.rs | 73 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/askama/src/filters/mod.rs b/askama/src/filters/mod.rs index ed036eb0b..8df725da8 100644 --- a/askama/src/filters/mod.rs +++ b/askama/src/filters/mod.rs @@ -630,4 +630,43 @@ mod tests { assert_eq!(wordcount("foo").unwrap(), 1); assert_eq!(wordcount("foo bar").unwrap(), 2); } + + #[test] + fn test_display_some() { + assert_eq!(display_some(&None::).unwrap().to_string(), ""); + assert_eq!(display_some(&None::).unwrap().to_string(), ""); + + assert_eq!( + display_some(&Some("hello world")).unwrap().to_string(), + "hello world" + ); + assert_eq!(display_some(&Some(123)).unwrap().to_string(), "123"); + } + + #[test] + fn test_display_some_or() { + assert_eq!( + display_some_or(&None::, "default") + .unwrap() + .to_string(), + "default" + ); + assert_eq!( + display_some_or(&None::, "default") + .unwrap() + .to_string(), + "default" + ); + + assert_eq!( + display_some_or(&Some("hello world"), "default") + .unwrap() + .to_string(), + "hello world" + ); + assert_eq!( + display_some_or(&Some(123), "default").unwrap().to_string(), + "123" + ); + } } diff --git a/testing/tests/filters.rs b/testing/tests/filters.rs index 4fe5c62fd..ca9b65a47 100644 --- a/testing/tests/filters.rs +++ b/testing/tests/filters.rs @@ -323,3 +323,76 @@ fn test_let_borrow() { }; assert_eq!(template.render().unwrap(), "hello") } + +#[derive(askama::Template)] +#[template( + source = "|{{ a|display_some }}|{{ b|display_some }}|{{ c|display_some }}|", + ext = "html" +)] +struct DisplaySome { + a: Option, + b: Option, + c: Option, +} + +#[test] +fn test_display_some() { + let template = DisplaySome { + a: None, + b: None, + c: None, + }; + assert_eq!(template.render().unwrap(), "||||"); + + let template = DisplaySome { + a: Some(String::from("Hello World")), + b: Some(12345), + c: Some(true), + }; + assert_eq!(template.render().unwrap(), "|Hello World|12345|true|"); +} + +#[derive(askama::Template)] +#[template( + source = "|{{ a|display_some_or(\"default\") }}|{{ b|display_some_or(0) }}|{{ c|display_some_or(\"none\") }}|", + ext = "html" +)] +struct DisplaySomeOr { + a: Option, + b: Option, + c: Option, +} + +#[test] +fn test_display_some_or() { + let template = DisplaySomeOr { + a: None, + b: None, + c: None, + }; + assert_eq!(template.render().unwrap(), "|default|0|none|"); + + let template = DisplaySomeOr { + a: Some(String::from("Hello World")), + b: Some(12345), + c: Some(true), + }; + assert_eq!(template.render().unwrap(), "|Hello World|12345|true|"); +} + +#[derive(askama::Template)] +#[template( + source = "\ + {% set val = Some(\"Hello World\") %}|{{ val|display_some }}|\ + {% set val = Some(123) %}{{ val|display_some }}|\ + {% set val = Some(true) %}{{ val|display_some }}|\ + ", + ext = "html" +)] +struct DisplaySomeUsingSet; + +#[test] +fn test_display_some_using_set() { + let template = DisplaySomeUsingSet; + assert_eq!(template.render().unwrap(), "|Hello World|123|true|"); +} From e0eac97efda99eabbad2b53d38cf6be76e63f849 Mon Sep 17 00:00:00 2001 From: vallentin Date: Sun, 21 Apr 2024 04:33:38 +0200 Subject: [PATCH 3/3] Updated docs and book to include display_some and display_some_or --- askama/src/filters/mod.rs | 10 +++++++++ book/src/filters.md | 42 +++++++++++++++++++++++++++++++++++++ book/src/template_syntax.md | 14 +++++++++++++ 3 files changed, 66 insertions(+) diff --git a/askama/src/filters/mod.rs b/askama/src/filters/mod.rs index 8df725da8..3c475bc12 100644 --- a/askama/src/filters/mod.rs +++ b/askama/src/filters/mod.rs @@ -350,6 +350,11 @@ where } } +/// See [`display_some` in the Askama book] for more information. +/// +/// See also [`display_some_or`]. +/// +/// [`display_some` in the Askama book]: https://djc.github.io/askama/filters.html#display_some pub fn display_some(value: &Option) -> Result> where T: fmt::Display, @@ -373,6 +378,11 @@ where } } +/// See [`display_some_or` in the Askama book] for more information. +/// +/// See also [`display_some`]. +/// +/// [`display_some_or` in the Askama book]: https://djc.github.io/askama/filters.html#display_some_or pub fn display_some_or<'a, T, U>( value: &'a Option, otherwise: U, diff --git a/book/src/filters.md b/book/src/filters.md index c444abbe2..45a2d9aba 100644 --- a/book/src/filters.md +++ b/book/src/filters.md @@ -24,6 +24,8 @@ Enable it with Cargo features (see below for more information). [`as_ref`][#as_ref], [`capitalize`][#capitalize], [`center`][#center], + [`display_some`][#display_some], + [`display_some_or`][#display_some_or], [`escape|e`][#escape], [`filesizeformat`][#filesizeformat], [`fmt`][#fmt], @@ -108,6 +110,46 @@ Output: - a - ``` +### display_some + +[#display_some]: #display_some + +The `display_some` filter is essentially a shorthand for: + +```text +{% if let Some(value) = value %}{{ value }}{% endif %} +``` + +It can be used like this: + +```text +{{ title|display_some }} +``` + +Where `title` can be any `Option` as long as `T` implements [`fmt::Display`]. + +### display_some_or + +[#display_some_or]: #display_some_or + +The `display_some_or` filter is similar to `display_some`, but allows providing +a default value to render for `None`. In short, instead of the following: + +```text +{% if let Some(value) = value %}{{ value }}{% else %}My default title{% endif %} +``` + +Then `display_some_or` can be used like this: + +```text +{{ title|display_some_or("My default title") }} +``` + +Where `title` can be any `Option` as long as `T` implements [`fmt::Display`]. +While the `default` value can be any `U` implementing [`fmt::Display`]. + +[`fmt::Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html + ### escape | e [#escape]: #escape--e diff --git a/book/src/template_syntax.md b/book/src/template_syntax.md index fcfe7388f..1f96a08d4 100644 --- a/book/src/template_syntax.md +++ b/book/src/template_syntax.md @@ -464,6 +464,20 @@ mirror Rust's [`if let` expressions]: {% endif %} ``` +See also the [`display_some`] and [`display_some_or`] filters, which +can be used to simplify _"render `Some` or nothing/default"_. + +```text +{{ title|display_some }} + +{{ title|display_some_or("My default title") }} +``` + +_Assuming `title` is `Option`._ + +[`display_some`]: filters.html#display_some +[`display_some_or`]: filters.html#display_some_or + [`if let` expressions]: https://doc.rust-lang.org/reference/expressions/if-expr.html#if-let-expressions ### Match