Skip to content

Commit b874647

Browse files
committed
Add support for Option<T> attributes
Introduces the `attr=[value]` syntax that assumes `value` is an `Option<T>`. Renders `attr="value"` for `Some(value)` and entirely omits the attribute for `None`. Implements and therefore closes #283.
1 parent 057a231 commit b874647

File tree

6 files changed

+79
-5
lines changed

6 files changed

+79
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- Update to support axum 0.2
66
[#303](https://github.com/lambda-fairy/maud/pull/303)
7+
- Add support for `Option<T>` attributes using the `attr=[value]` syntax.
8+
[#306](https://github.com/lambda-fairy/maud/pull/306)
79

810
## [0.22.3] - 2021-09-27
911

docs/content/elements-attributes.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,26 @@ html! {
101101
# ;
102102
```
103103

104+
## Optional attributes: `title=[Some("value")]`
105+
106+
Add optional attributes to an element using `attr=[value]` syntax, with *square*
107+
brackets. These are only rendered if the value is `Some<T>`, and entirely
108+
omitted if the value is `None`.
109+
110+
```rust
111+
# let _ = maud::
112+
html! {
113+
p title=[Some("Good password")] { "Correct horse" }
114+
115+
@let value = Some(42);
116+
input value=[value];
117+
118+
@let title: Option<&str> = None;
119+
p title=[title] { "Battery staple" }
120+
}
121+
# ;
122+
```
123+
104124
## Empty attributes: `checked`
105125

106126
Declare an empty attribute by omitting the value.

maud/tests/basic_syntax.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,40 @@ fn empty_attributes_question_mark() {
118118
assert_eq!(result.into_string(), "<input checked disabled>");
119119
}
120120

121+
#[test]
122+
fn optional_attribute_some() {
123+
let result = html! { input value=[Some("value")]; };
124+
assert_eq!(result.into_string(), r#"<input value="value">"#);
125+
}
126+
127+
#[test]
128+
fn optional_attribute_none() {
129+
let result = html! { input value=[None as Option<&str>]; };
130+
assert_eq!(result.into_string(), r#"<input>"#);
131+
}
132+
133+
#[test]
134+
fn optional_attribute_non_string_some() {
135+
let result = html! { input value=[Some(42)]; };
136+
assert_eq!(result.into_string(), r#"<input value="42">"#);
137+
}
138+
139+
#[test]
140+
fn optional_attribute_variable() {
141+
let result = html! {
142+
@let x = Some(42);
143+
input value=[x];
144+
};
145+
assert_eq!(result.into_string(), r#"<input value="42">"#);
146+
}
147+
148+
#[test]
149+
fn optional_attribute_inner_value_evaluated_only_once() {
150+
let mut count = 0;
151+
html! { input value=[{ count += 1; Some("picklebarrelkumquat") }]; };
152+
assert_eq!(count, 1);
153+
}
154+
121155
#[test]
122156
fn colons_in_names() {
123157
let result = html! { pon-pon:controls-alpha { a on:click="yay()" { "Yay!" } } };

maud_macros/src/ast.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,15 @@ impl Attribute {
170170
#[derive(Debug)]
171171
pub enum AttrType {
172172
Normal { value: Markup },
173+
Optional { toggler: Toggler },
173174
Empty { toggler: Option<Toggler> },
174175
}
175176

176177
impl AttrType {
177178
fn span(&self) -> Option<SpanRange> {
178179
match *self {
179180
AttrType::Normal { ref value } => Some(value.span()),
181+
AttrType::Optional { ref toggler } => Some(toggler.span()),
180182
AttrType::Empty { ref toggler } => toggler.as_ref().map(Toggler::span),
181183
}
182184
}

maud_macros/src/generate.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ impl Generator {
124124
self.markup(value, build);
125125
build.push_str("\"");
126126
}
127+
AttrType::Optional { toggler } => build.push_tokens({
128+
// `inner_value` is the unpacked Some() from `toggler.cond`, see below.
129+
let markup = Markup::Splice { expr: quote!(inner_value), outer_span: toggler.cond_span };
130+
let mut build = self.builder();
131+
build.push_str(" ");
132+
self.name(name, &mut build);
133+
build.push_str("=\"");
134+
self.markup(markup, &mut build);
135+
build.push_str("\"");
136+
let body = build.finish();
137+
let cond = toggler.cond;
138+
quote!(if let Some(inner_value) = #cond { #body })
139+
}),
127140
AttrType::Empty { toggler: None } => {
128141
build.push_str(" ");
129142
self.name(name, build);

maud_macros/src/parse.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -571,13 +571,16 @@ impl Parser {
571571
// Parse a value under an attribute context
572572
assert!(self.current_attr.is_none());
573573
self.current_attr = Some(ast::name_to_string(name.clone()));
574-
let value = self.markup();
574+
let attr_type = match self.attr_toggler() {
575+
Some(toggler) => ast::AttrType::Optional { toggler },
576+
None => {
577+
let value = self.markup();
578+
ast::AttrType::Normal { value }
579+
}
580+
};
575581
self.current_attr = None;
576582
attrs.push(ast::Attr::Attribute {
577-
attribute: ast::Attribute {
578-
name,
579-
attr_type: ast::AttrType::Normal { value },
580-
},
583+
attribute: ast::Attribute { name, attr_type },
581584
});
582585
}
583586
// Empty attribute (legacy syntax)

0 commit comments

Comments
 (0)