diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs index cd68d08..5d99a00 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros/src/parse.rs @@ -481,7 +481,8 @@ impl<'cx, 'a, 'i> Parser<'cx, 'a, 'i> { /// Parses and renders the attributes of an element. fn attrs(&mut self) -> PResult<()> { - let mut classes = Vec::new(); + let mut classes_static = Vec::new(); + let mut classes_toggled = Vec::new(); let mut ids = Vec::new(); loop { let old_input = self.input; @@ -525,7 +526,18 @@ impl<'cx, 'a, 'i> Parser<'cx, 'a, 'i> { (Err(_), &[dot!(), ident!(_, _), ..]) => { // Class shorthand self.shift(1); - classes.push(self.name().unwrap()); + let class_name = self.name().unwrap(); + match *self.input { + [TokenTree::Delimited(_, ref d), ..] if d.delim == DelimToken::Bracket => { + // Toggle the class based on a boolean expression + self.shift(1); + let cond = self.with_rust_parser(d.tts.clone(), RustParser::parse_expr)?; + let cond = cond.to_tokens(self.render.cx); + classes_toggled.push((cond, class_name)); + }, + // Emit the class unconditionally + _ => classes_static.push(class_name), + } }, (Err(_), &[pound!(), ident!(_, _), ..]) => { // ID shorthand @@ -538,9 +550,22 @@ impl<'cx, 'a, 'i> Parser<'cx, 'a, 'i> { }, } } - if !classes.is_empty() { + if !classes_static.is_empty() || !classes_toggled.is_empty() { self.render.attribute_start("class"); - self.render.string(&classes.join(" ")); + self.render.string(&classes_static.join(" ")); + for (i, (cond, mut class_name)) in classes_toggled.into_iter().enumerate() { + // If a class comes first in the list, then it shouldn't be + // prefixed by a space + if i > 0 || !classes_static.is_empty() { + class_name = format!(" {}", class_name); + } + let body = { + let mut r = self.render.fork(); + r.string(&class_name); + r.into_stmts() + }; + self.render.emit_if(cond, body, None); + } self.render.attribute_end(); } if !ids.is_empty() { diff --git a/maud_macros/tests/basic_syntax.rs b/maud_macros/tests/basic_syntax.rs index cc53953..30ccaff 100644 --- a/maud_macros/tests/basic_syntax.rs +++ b/maud_macros/tests/basic_syntax.rs @@ -3,6 +3,8 @@ extern crate maud; +use maud::Markup; + #[test] fn literals() { let s = html!("du\tcks" "-23" "3.14\n" "geese").into_string(); @@ -76,6 +78,22 @@ fn empty_attributes() { assert_eq!(s, r#"<div readonly><input type="checkbox" checked></div>"#); } +#[test] +fn toggle_empty_attributes() { + let rocks = true; + let s = html!({ + input checked?[true] / + input checked?[false] / + input checked?[rocks] / + input checked?[!rocks] / + }).into_string(); + assert_eq!(s, concat!( + r#"<input checked>"#, + r#"<input>"#, + r#"<input checked>"#, + r#"<input>"#)); +} + #[test] fn colons_in_names() { let s = html!(pon-pon:controls-alpha a on:click="yay()" "Yay!").into_string(); @@ -121,6 +139,26 @@ fn hyphens_in_class_names() { assert_eq!(s, r#"<p class="rocks-these are--my--rocks">yes</p>"#); } +#[test] +fn toggle_classes() { + fn test(is_cupcake: bool, is_muffin: bool) -> Markup { + html!(p.cupcake[is_cupcake].muffin[is_muffin] "Testing!") + } + assert_eq!(test(true, true).into_string(), r#"<p class="cupcake muffin">Testing!</p>"#); + assert_eq!(test(false, true).into_string(), r#"<p class=" muffin">Testing!</p>"#); + assert_eq!(test(true, false).into_string(), r#"<p class="cupcake">Testing!</p>"#); + assert_eq!(test(false, false).into_string(), r#"<p class="">Testing!</p>"#); +} + +#[test] +fn mixed_classes() { + fn test(is_muffin: bool) -> Markup { + html!(p.cupcake.muffin[is_muffin].lamington "Testing!") + } + assert_eq!(test(true).into_string(), r#"<p class="cupcake lamington muffin">Testing!</p>"#); + assert_eq!(test(false).into_string(), r#"<p class="cupcake lamington">Testing!</p>"#); +} + #[test] fn ids_shorthand() { let s = html!(p { "Hi, " span#thing { "Lyra" } "!" }).into_string(); diff --git a/maud_macros/tests/splices.rs b/maud_macros/tests/splices.rs index 9270ec0..b5dc006 100644 --- a/maud_macros/tests/splices.rs +++ b/maud_macros/tests/splices.rs @@ -37,22 +37,6 @@ fn attributes() { assert_eq!(s, r#"<img src="pinkie.jpg" alt="Pinkie Pie">"#); } -#[test] -fn empty_attributes() { - let rocks = true; - let s = html!({ - input checked?[true] / - input checked?[false] / - input checked?[rocks] / - input checked?[!rocks] / - }).into_string(); - assert_eq!(s, concat!( - r#"<input checked>"#, - r#"<input>"#, - r#"<input checked>"#, - r#"<input>"#)); -} - static BEST_PONY: &'static str = "Pinkie Pie"; #[test]