Skip to content

Commit

Permalink
Make the ? suffix for empty attributes optional (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
lambda-fairy authored Nov 11, 2020
1 parent 6ebe6b1 commit d5dec51
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 69 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- [Changed] Don't require `?` suffix for empty attributes. The old syntax is kept for backward compatibility.
[#238](https://github.com/lambda-fairy/maud/pull/238)

## [0.22.1] - 2020-11-02

- [Added] Stable support 🎉
Expand Down
10 changes: 7 additions & 3 deletions docs/content/elements-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,24 @@ html! {
}
```

## Empty attributes: `checked?`
## Empty attributes: `checked`

Declare an empty attribute using a `?` suffix: `checked?`.
Declare an empty attribute by omitting the value.

```rust
html! {
form {
input type="checkbox" name="cupcakes" checked?;
input type="checkbox" name="cupcakes" checked;
" "
label for="cupcakes" { "Do you like cupcakes?" }
}
}
```

Before version 0.22.2, Maud required a `?` suffix on empty attributes: `checked?`. This is no longer necessary ([#238]), but still supported for backward compatibility.

[#238]: https://github.com/lambda-fairy/maud/pull/238

## Classes and IDs: `.foo` `#bar`

Add classes and IDs to an element using `.foo` and `#bar` syntax. You can chain multiple classes and IDs together, and mix and match them with other attributes:
Expand Down
2 changes: 1 addition & 1 deletion docs/content/splices-toggles.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ This works on empty attributes:
```rust
let allow_editing = true;
html! {
p contenteditable?[allow_editing] {
p contenteditable[allow_editing] {
"Edit me, I "
em { "dare" }
" you."
Expand Down
24 changes: 15 additions & 9 deletions maud/tests/basic_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ fn simple_attributes() {

#[test]
fn empty_attributes() {
let result = html! { div readonly? { input type="checkbox" checked?; } };
let result = html! { div readonly { input type="checkbox" checked; } };
assert_eq!(
result.into_string(),
r#"<div readonly><input type="checkbox" checked></div>"#
Expand All @@ -87,10 +87,10 @@ fn empty_attributes() {
fn toggle_empty_attributes() {
let rocks = true;
let result = html! {
input checked?[true];
input checked?[false];
input checked?[rocks];
input checked?[!rocks];
input checked[true];
input checked[false];
input checked[rocks];
input checked[!rocks];
};
assert_eq!(
result.into_string(),
Expand All @@ -108,10 +108,16 @@ fn toggle_empty_attributes_braces() {
struct Maud {
rocks: bool,
}
let result = html! { input checked?[Maud { rocks: true }.rocks]; };
let result = html! { input checked[Maud { rocks: true }.rocks]; };
assert_eq!(result.into_string(), r#"<input checked>"#);
}

#[test]
fn empty_attributes_question_mark() {
let result = html! { input checked? disabled?[true]; };
assert_eq!(result.into_string(), "<input checked disabled>");
}

#[test]
fn colons_in_names() {
let result = html! { pon-pon:controls-alpha { a on:click="yay()" { "Yay!" } } };
Expand All @@ -133,7 +139,7 @@ fn hyphens_in_element_names() {

#[test]
fn hyphens_in_attribute_names() {
let result = html! { this sentence-is="false" of-course? {} };
let result = html! { this sentence-is="false" of-course {} };
assert_eq!(
result.into_string(),
r#"<this sentence-is="false" of-course></this>"#
Expand Down Expand Up @@ -281,7 +287,7 @@ fn div_shorthand_id() {

#[test]
fn div_shorthand_class_with_attrs() {
let result = html! { .awesome-class contenteditable? dir="rtl" #unique-id {} };
let result = html! { .awesome-class contenteditable dir="rtl" #unique-id {} };
assert_eq!(
result.into_string(),
r#"<div class="awesome-class" id="unique-id" contenteditable dir="rtl"></div>"#
Expand All @@ -290,7 +296,7 @@ fn div_shorthand_class_with_attrs() {

#[test]
fn div_shorthand_id_with_attrs() {
let result = html! { #unique-id contenteditable? dir="rtl" .awesome-class {} };
let result = html! { #unique-id contenteditable dir="rtl" .awesome-class {} };
assert_eq!(
result.into_string(),
r#"<div class="awesome-class" id="unique-id" contenteditable dir="rtl"></div>"#
Expand Down
120 changes: 64 additions & 56 deletions maud_macros/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@ impl Parser {
self.next();
}

/// Overwrites the current parser state with the given parameter.
fn commit(&mut self, attempt: Parser) {
*self = attempt;
}

/// Parses and renders multiple blocks of markup.
fn markups(&mut self) -> Vec<ast::Markup> {
let mut result = Vec::new();
Expand Down Expand Up @@ -534,60 +529,73 @@ impl Parser {
fn attrs(&mut self) -> ast::Attrs {
let mut attrs = Vec::new();
loop {
let mut attempt = self.clone();
let maybe_name = attempt.try_namespaced_name();
let token_after = attempt.next();
match (maybe_name, token_after) {
// Non-empty attribute
(Some(ref name), Some(TokenTree::Punct(ref punct))) if punct.as_char() == '=' => {
self.commit(attempt);
let value;
{
// Parse a value under an attribute context
let in_attr = mem::replace(&mut self.in_attr, true);
value = self.markup();
self.in_attr = in_attr;
if let Some(name) = self.try_namespaced_name() {
// Attribute
match self.peek() {
// Non-empty attribute
Some(TokenTree::Punct(ref punct)) if punct.as_char() == '=' => {
self.advance();
let value;
{
// Parse a value under an attribute context
let in_attr = mem::replace(&mut self.in_attr, true);
value = self.markup();
self.in_attr = in_attr;
}
attrs.push(ast::Attr::Attribute {
attribute: ast::Attribute {
name,
attr_type: ast::AttrType::Normal { value },
},
});
}
// Empty attribute (legacy syntax)
Some(TokenTree::Punct(ref punct)) if punct.as_char() == '?' => {
self.advance();
let toggler = self.attr_toggler();
attrs.push(ast::Attr::Attribute {
attribute: ast::Attribute {
name: name.clone(),
attr_type: ast::AttrType::Empty { toggler },
},
});
}
// Empty attribute (new syntax)
_ => {
let toggler = self.attr_toggler();
attrs.push(ast::Attr::Attribute {
attribute: ast::Attribute {
name: name.clone(),
attr_type: ast::AttrType::Empty { toggler },
},
});
}
attrs.push(ast::Attr::Attribute {
attribute: ast::Attribute {
name: name.clone(),
attr_type: ast::AttrType::Normal { value },
},
});
}
// Empty attribute
(Some(ref name), Some(TokenTree::Punct(ref punct))) if punct.as_char() == '?' => {
self.commit(attempt);
let toggler = self.attr_toggler();
attrs.push(ast::Attr::Attribute {
attribute: ast::Attribute {
name: name.clone(),
attr_type: ast::AttrType::Empty { toggler },
},
});
}
// Class shorthand
(None, Some(TokenTree::Punct(ref punct))) if punct.as_char() == '.' => {
self.commit(attempt);
let name = self.class_or_id_name();
let toggler = self.attr_toggler();
attrs.push(ast::Attr::Class {
dot_span: SpanRange::single_span(punct.span()),
name,
toggler,
});
}
// ID shorthand
(None, Some(TokenTree::Punct(ref punct))) if punct.as_char() == '#' => {
self.commit(attempt);
let name = self.class_or_id_name();
attrs.push(ast::Attr::Id {
hash_span: SpanRange::single_span(punct.span()),
name,
});
} else {
match self.peek() {
// Class shorthand
Some(TokenTree::Punct(ref punct)) if punct.as_char() == '.' => {
self.advance();
let name = self.class_or_id_name();
let toggler = self.attr_toggler();
attrs.push(ast::Attr::Class {
dot_span: SpanRange::single_span(punct.span()),
name,
toggler,
});
}
// ID shorthand
Some(TokenTree::Punct(ref punct)) if punct.as_char() == '#' => {
self.advance();
let name = self.class_or_id_name();
attrs.push(ast::Attr::Id {
hash_span: SpanRange::single_span(punct.span()),
name,
});
}
// If it's not a valid attribute, backtrack and bail out
_ => break,
}
// If it's not a valid attribute, backtrack and bail out
_ => break,
}
}

Expand Down

0 comments on commit d5dec51

Please sign in to comment.