diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs index 2af9ba3..0825f3d 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros/src/parse.rs @@ -2,6 +2,7 @@ use std::mem; use syntax::ast::{Expr, ExprParen, Lit, Stmt, TokenTree, TtDelimited, TtToken}; use syntax::ext::quote::rt::ToTokens; use syntax::codemap::Span; +use syntax::diagnostic::FatalError; use syntax::ext::base::ExtCtxt; use syntax::parse::{self, PResult}; use syntax::parse::parser::Parser as RustParser; @@ -163,9 +164,9 @@ impl<'cx, 'i> Parser<'cx, 'i> { self.render.splice(expr, Escape::Escape); }, // Element - [ident!(sp, name), ..] => { - self.shift(1); - try!(self.element(sp, &name.name.as_str())); + [ident!(sp, _), ..] => { + let name = try!(self.name()); + try!(self.element(sp, &name)); }, // Block [TtDelimited(_, ref d), ..] if d.delim == DelimToken::Brace => { @@ -335,42 +336,66 @@ impl<'cx, 'i> Parser<'cx, 'i> { /// Parses and renders the attributes of an element. fn attrs(&mut self) -> PResult<()> { - loop { match self.input { - [ident!(name), eq!(), ..] => { - // Non-empty attribute - self.shift(2); - self.render.attribute_start(&name.name.as_str()); - { - // Parse a value under an attribute context - let mut in_attr = true; - mem::swap(&mut self.in_attr, &mut in_attr); - try!(self.markup()); - mem::swap(&mut self.in_attr, &mut in_attr); - } - self.render.attribute_end(); - }, - [ident!(name), question!(), ..] => { - // Empty attribute - self.shift(2); - if let [ref tt @ eq!(), ..] = self.input { - // Toggle the attribute based on a boolean expression + loop { + let old_input = self.input; + let maybe_name = self.name(); + match (maybe_name, self.input) { + (Ok(name), [eq!(), ..]) => { + // Non-empty attribute self.shift(1); - let cond = try!(self.splice(tt.get_span())); - // Silence "unnecessary parentheses" warnings - let cond = strip_outer_parens(cond).to_tokens(self.render.cx); - let body = { - let mut r = self.render.fork(); - r.attribute_empty(&name.name.as_str()); - r.into_stmts() - }; - self.render.emit_if(cond, body, None); - } else { - // Write the attribute unconditionally - self.render.attribute_empty(&name.name.as_str()); - } - }, - _ => return Ok(()), + self.render.attribute_start(&name); + { + // Parse a value under an attribute context + let mut in_attr = true; + mem::swap(&mut self.in_attr, &mut in_attr); + try!(self.markup()); + mem::swap(&mut self.in_attr, &mut in_attr); + } + self.render.attribute_end(); + }, + (Ok(name), [question!(), ..]) => { + // Empty attribute + self.shift(1); + if let [ref tt @ eq!(), ..] = self.input { + // Toggle the attribute based on a boolean expression + self.shift(1); + let cond = try!(self.splice(tt.get_span())); + // Silence "unnecessary parentheses" warnings + let cond = strip_outer_parens(cond).to_tokens(self.render.cx); + let body = { + let mut r = self.render.fork(); + r.attribute_empty(&name); + r.into_stmts() + }; + self.render.emit_if(cond, body, None); + } else { + // Write the attribute unconditionally + self.render.attribute_empty(&name); + } + }, + _ => { + self.input = old_input; + break; + }, }} + Ok(()) + } + + /// Parses a HTML element or attribute name. + fn name(&mut self) -> PResult<String> { + let mut s = match self.input { + [ident!(name), ..] => { + self.shift(1); + String::from(&name.name.as_str() as &str) + }, + _ => return Err(FatalError), + }; + while let [minus!(), ident!(name), ..] = self.input { + self.shift(2); + s.push('-'); + s.push_str(&name.name.as_str()); + } + Ok(s) } /// Parses the given token tree, returning a vector of statements. diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs index f957d99..3950b5f 100644 --- a/maud_macros/tests/tests.rs +++ b/maud_macros/tests/tests.rs @@ -255,3 +255,19 @@ fn html_utf8() { html_utf8!(buf, p "hello").unwrap(); assert_eq!(buf, b"<p>hello</p>"); } + +mod issue_10 { + #[test] + fn hyphens_in_element_names() { + let mut s = String::new(); + html!(s, custom-element {}).unwrap(); + assert_eq!(s, "<custom-element></custom-element>"); + } + + #[test] + fn hyphens_in_attribute_names() { + let mut s = String::new(); + html!(s, this sentence-is="false" of-course? {}).unwrap(); + assert_eq!(s, r#"<this sentence-is="false" of-course></this>"#); + } +}