From b787c18f002b8d383c682db63053933c16f3b3e8 Mon Sep 17 00:00:00 2001 From: Alexandre Macabies Date: Thu, 28 Oct 2021 21:50:09 +0200 Subject: [PATCH] Add support for Option attributes Introduces the `attr=[value]` syntax that assumes `value` is an `Option`. Renders `attr="value"` for `Some(value)` and entirely omits the attribute for `None`. Implements and therefore closes #283. --- CHANGELOG.md | 2 ++ docs/content/elements-attributes.md | 20 +++++++++++++++++ maud/tests/basic_syntax.rs | 34 +++++++++++++++++++++++++++++ maud_macros/src/ast.rs | 2 ++ maud_macros/src/generate.rs | 16 ++++++++++++++ maud_macros/src/parse.rs | 13 ++++++----- 6 files changed, 82 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 073ba41c..b8667e03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Update to support axum 0.2 [#303](https://github.com/lambda-fairy/maud/pull/303) +- Add support for `Option` attributes using the `attr=[value]` syntax. + [#306](https://github.com/lambda-fairy/maud/pull/306) ## [0.22.3] - 2021-09-27 diff --git a/docs/content/elements-attributes.md b/docs/content/elements-attributes.md index 868cb250..561e851d 100644 --- a/docs/content/elements-attributes.md +++ b/docs/content/elements-attributes.md @@ -101,6 +101,26 @@ html! { # ; ``` +## Optional attributes: `title=[Some("value")]` + +Add optional attributes to an element using `attr=[value]` syntax, with *square* +brackets. These are only rendered if the value is `Some`, and entirely +omitted if the value is `None`. + +```rust +# let _ = maud:: +html! { + p title=[Some("Good password")] { "Correct horse" } + + @let value = Some(42); + input value=[value]; + + @let title: Option<&str> = None; + p title=[title] { "Battery staple" } +} +# ; +``` + ## Empty attributes: `checked` Declare an empty attribute by omitting the value. diff --git a/maud/tests/basic_syntax.rs b/maud/tests/basic_syntax.rs index e7f73ca1..64e682d2 100644 --- a/maud/tests/basic_syntax.rs +++ b/maud/tests/basic_syntax.rs @@ -118,6 +118,40 @@ fn empty_attributes_question_mark() { assert_eq!(result.into_string(), ""); } +#[test] +fn optional_attribute_some() { + let result = html! { input value=[Some("value")]; }; + assert_eq!(result.into_string(), r#""#); +} + +#[test] +fn optional_attribute_none() { + let result = html! { input value=[None as Option<&str>]; }; + assert_eq!(result.into_string(), r#""#); +} + +#[test] +fn optional_attribute_non_string_some() { + let result = html! { input value=[Some(42)]; }; + assert_eq!(result.into_string(), r#""#); +} + +#[test] +fn optional_attribute_variable() { + let result = html! { + @let x = Some(42); + input value=[x]; + }; + assert_eq!(result.into_string(), r#""#); +} + +#[test] +fn optional_attribute_inner_value_evaluated_only_once() { + let mut count = 0; + html! { input value=[{ count += 1; Some("picklebarrelkumquat") }]; }; + assert_eq!(count, 1); +} + #[test] fn colons_in_names() { let result = html! { pon-pon:controls-alpha { a on:click="yay()" { "Yay!" } } }; diff --git a/maud_macros/src/ast.rs b/maud_macros/src/ast.rs index fdf35d68..76749651 100644 --- a/maud_macros/src/ast.rs +++ b/maud_macros/src/ast.rs @@ -170,6 +170,7 @@ impl Attribute { #[derive(Debug)] pub enum AttrType { Normal { value: Markup }, + Optional { toggler: Toggler }, Empty { toggler: Option }, } @@ -177,6 +178,7 @@ impl AttrType { fn span(&self) -> Option { match *self { AttrType::Normal { ref value } => Some(value.span()), + AttrType::Optional { ref toggler } => Some(toggler.span()), AttrType::Empty { ref toggler } => toggler.as_ref().map(Toggler::span), } } diff --git a/maud_macros/src/generate.rs b/maud_macros/src/generate.rs index 9fa60fef..a51f3141 100644 --- a/maud_macros/src/generate.rs +++ b/maud_macros/src/generate.rs @@ -124,6 +124,22 @@ impl Generator { self.markup(value, build); build.push_str("\""); } + AttrType::Optional { toggler } => build.push_tokens({ + // `inner_value` is the unpacked Some() from `toggler.cond`, see below. + let markup = Markup::Splice { + expr: quote!(inner_value), + outer_span: toggler.cond_span, + }; + let mut build = self.builder(); + build.push_str(" "); + self.name(name, &mut build); + build.push_str("=\""); + self.markup(markup, &mut build); + build.push_str("\""); + let body = build.finish(); + let cond = toggler.cond; + quote!(if let Some(inner_value) = #cond { #body }) + }), AttrType::Empty { toggler: None } => { build.push_str(" "); self.name(name, build); diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs index bf78f2dd..2d3d3f3a 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros/src/parse.rs @@ -571,13 +571,16 @@ impl Parser { // Parse a value under an attribute context assert!(self.current_attr.is_none()); self.current_attr = Some(ast::name_to_string(name.clone())); - let value = self.markup(); + let attr_type = match self.attr_toggler() { + Some(toggler) => ast::AttrType::Optional { toggler }, + None => { + let value = self.markup(); + ast::AttrType::Normal { value } + } + }; self.current_attr = None; attrs.push(ast::Attr::Attribute { - attribute: ast::Attribute { - name, - attr_type: ast::AttrType::Normal { value }, - }, + attribute: ast::Attribute { name, attr_type }, }); } // Empty attribute (legacy syntax)