diff --git a/maud/tests/basic_syntax.rs b/maud/tests/basic_syntax.rs index b94fe58..d6ae8ef 100644 --- a/maud/tests/basic_syntax.rs +++ b/maud/tests/basic_syntax.rs @@ -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 { @@ -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 { @@ -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(); diff --git a/maud/tests/control_structures.rs b/maud/tests/control_structures.rs index b38a6e2..52060c8 100644 --- a/maud/tests/control_structures.rs +++ b/maud/tests/control_structures.rs @@ -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")] { diff --git a/maud/tests/splices.rs b/maud/tests/splices.rs index 0974e2a..8781f37 100644 --- a/maud/tests/splices.rs +++ b/maud/tests/splices.rs @@ -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] diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs index 43b36e6..7e511b0 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros/src/parse.rs @@ -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 @@ -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 @@ -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() { @@ -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() { @@ -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() {