Skip to content

Commit

Permalink
Parse arbitrary expressions in classes and IDs
Browse files Browse the repository at this point in the history
Closes #128
  • Loading branch information
lambda-fairy committed Aug 13, 2018
1 parent 8d8d596 commit e273d89
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 18 deletions.
22 changes: 21 additions & 1 deletion maud/tests/basic_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ fn hyphens_in_class_names() {
assert_eq!(s, r#"<p class="rocks-these are--my--rocks">yes</p>"#);
}

#[test]
fn class_string() {
let s = html!(h1."pinkie-123" { "Pinkie Pie" }).into_string();
assert_eq!(s, r#"<h1 class="pinkie-123">Pinkie Pie</h1>"#);
}

#[test]
fn toggle_classes() {
fn test(is_cupcake: bool, is_muffin: bool) -> Markup {
Expand All @@ -167,6 +173,14 @@ fn toggle_classes_braces() {
assert_eq!(s, r#"<p class="rocks">Awesome!</p>"#);
}

#[test]
fn toggle_classes_string() {
let is_cupcake = true;
let is_muffin = false;
let s = html!(p."cupcake"[is_cupcake]."is_muffin"[is_muffin] { "Testing!" }).into_string();
assert_eq!(s, r#"<p class="cupcake">Testing!</p>"#);
}

#[test]
fn mixed_classes() {
fn test(is_muffin: bool) -> Markup {
Expand All @@ -177,11 +191,17 @@ fn mixed_classes() {
}

#[test]
fn ids_shorthand() {
fn id_shorthand() {
let s = html!(p { "Hi, " span#thing { "Lyra" } "!" }).into_string();
assert_eq!(s, r#"<p>Hi, <span id="thing">Lyra</span>!</p>"#);
}

#[test]
fn id_string() {
let s = html!(h1#"pinkie-123" { "Pinkie Pie" }).into_string();
assert_eq!(s, r#"<h1 id="pinkie-123">Pinkie Pie</h1>"#);
}

#[test]
fn classes_attrs_ids_mixed_up() {
let s = html!(p { "Hi, " span.name.here lang="en" #thing { "Lyra" } "!" }).into_string();
Expand Down
16 changes: 16 additions & 0 deletions maud/tests/control_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ fn if_expr() {
}
}

#[test]
fn if_expr_in_class() {
for &(chocolate_milk, expected) in &[
(0, r#"<p class="empty">Chocolate milk</p>"#),
(1, r#"<p class="full">Chocolate milk</p>"#),
]
{
let s = html! {
p.@if chocolate_milk == 0 { "empty" } @else { "full" } {
"Chocolate milk"
}
}.into_string();
assert_eq!(s, expected);
}
}

#[test]
fn if_let() {
for &(input, output) in &[(Some("yay"), "yay"), (None, "oh noes")] {
Expand Down
21 changes: 21 additions & 0 deletions maud/tests/splices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,27 @@ fn attributes() {
assert_eq!(s, r#"<img src="pinkie.jpg" alt="Pinkie Pie">"#);
}

#[test]
fn class_shorthand() {
let pinkie_class = "pinkie";
let s = html!(p.(pinkie_class) { "Fun!" }).into_string();
assert_eq!(s, r#"<p class="pinkie">Fun!</p>"#);
}

#[test]
fn class_shorthand_block() {
let class_prefix = "pinkie-";
let s = html!(p.{ (class_prefix) "123" } { "Fun!" }).into_string();
assert_eq!(s, r#"<p class="pinkie-123">Fun!</p>"#);
}

#[test]
fn id_shorthand() {
let pinkie_id = "pinkie";
let s = html!(p#(pinkie_id) { "Fun!" }).into_string();
assert_eq!(s, r#"<p id="pinkie">Fun!</p>"#);
}

static BEST_PONY: &'static str = "Pinkie Pie";

#[test]
Expand Down
31 changes: 14 additions & 17 deletions maud_macros/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ impl Parser {
},
// Element
TokenTree::Ident(_) => {
let name = self.namespaced_name()?;
// `.try_namespaced_name()` should never fail as we've
// already seen an `Ident`
let name = self.try_namespaced_name().expect("identifier");
self.element(name)?
},
// Splice
Expand Down Expand Up @@ -544,16 +546,14 @@ impl Parser {
// Class shorthand
(None, Some(TokenTree::Punct(ref punct))) if punct.as_char() == '.' => {
self.commit(attempt);
// TODO parse arbitrary expressions here
let name = ast::Markup::Symbol { symbol: self.name()? };
let name = self.class_or_id_name()?;
let toggler = self.attr_toggler();
attrs.push(ast::Attr::Class { dot_span: punct.span(), name, toggler });
},
// ID shorthand
(None, Some(TokenTree::Punct(ref punct))) if punct.as_char() == '#' => {
self.commit(attempt);
// TODO parse arbitrary expressions here
let name = ast::Markup::Symbol { symbol: self.name()? };
let name = self.class_or_id_name()?;
attrs.push(ast::Attr::Id { hash_span: punct.span(), name });
},
// If it's not a valid attribute, backtrack and bail out
Expand Down Expand Up @@ -598,6 +598,15 @@ impl Parser {
Ok(attrs)
}

/// Parses the name of a class or ID.
fn class_or_id_name(&mut self) -> ParseResult<ast::Markup> {
if let Some(symbol) = self.try_name() {
Ok(ast::Markup::Symbol { symbol })
} else {
self.markup()
}
}

/// Parses the `[cond]` syntax after an empty attribute or class shorthand.
fn attr_toggler(&mut self) -> Option<ast::Toggler> {
match self.peek() {
Expand All @@ -613,12 +622,6 @@ impl Parser {
}

/// Parses an identifier, without dealing with namespaces.
fn name(&mut self) -> ParseResult<TokenStream> {
self.try_name().ok_or_else(|| {
Span::call_site().error("expected identifier").emit();
})
}

fn try_name(&mut self) -> Option<TokenStream> {
let mut result = Vec::new();
if let Some(token @ TokenTree::Ident(_)) = self.peek() {
Expand Down Expand Up @@ -648,12 +651,6 @@ impl Parser {

/// Parses a HTML element or attribute name, along with a namespace
/// if necessary.
fn namespaced_name(&mut self) -> ParseResult<TokenStream> {
self.try_namespaced_name().ok_or_else(|| {
Span::call_site().error("expected identifier").emit();
})
}

fn try_namespaced_name(&mut self) -> Option<TokenStream> {
let mut result = vec![self.try_name()?];
if let Some(TokenTree::Punct(ref punct)) = self.peek() {
Expand Down

0 comments on commit e273d89

Please sign in to comment.