Implement toggleable boolean attributes

Closes 
This commit is contained in:
Chris Wong 2015-01-29 13:47:11 +13:00
parent d8ee991da2
commit dca0400692
4 changed files with 68 additions and 20 deletions
maud/src
maud_macros

View file

@ -99,7 +99,7 @@
//! html! {
//! form method="POST" {
//! label for="waffles" "Do you like waffles?"
//! input name="waffles" type="checkbox" checked=! /
//! input name="waffles" type="checkbox" checked? /
//! }
//! }
//! ```
@ -114,7 +114,7 @@
//! Add attributes using the syntax `attr="value"`. Attributes must be
//! quoted: they are parsed as string literals.
//!
//! To declare an empty attribute, use `!` for the value: `checked=!`.
//! To declare an empty attribute, use a `?` suffix: `checked?`.
//!
//! ## Splices
//!

View file

@ -20,6 +20,9 @@ macro_rules! eq {
macro_rules! not {
() => (TtToken(_, token::Not))
}
macro_rules! question {
() => (TtToken(_, token::Question))
}
macro_rules! semi {
() => (TtToken(_, token::Semi))
}
@ -90,11 +93,13 @@ impl<'cx, 's, 'i, 'r, 'o> Parser<'cx, 's, 'i, 'r, 'o> {
// Splice
[ref tt @ dollar!(), dollar!(), ..] => {
self.shift(2);
self.splice(Escape::PassThru, tt.get_span())
let expr = self.splice(tt.get_span());
self.render.splice(expr, Escape::PassThru);
},
[ref tt @ dollar!(), ..] => {
self.shift(1);
self.splice(Escape::Escape, tt.get_span())
let expr = self.splice(tt.get_span());
self.render.splice(expr, Escape::Escape);
},
// Element
[ident!(sp, name), ..] => {
@ -127,7 +132,7 @@ impl<'cx, 's, 'i, 'r, 'o> Parser<'cx, 's, 'i, 'r, 'o> {
}
}
fn splice(&mut self, escape: Escape, sp: Span) {
fn splice(&mut self, sp: Span) -> P<Expr> {
let mut tts = vec![];
// First, munch a single token tree
if let [ref tt, ..] = self.input {
@ -151,10 +156,9 @@ impl<'cx, 's, 'i, 'r, 'o> Parser<'cx, 's, 'i, 'r, 'o> {
}
}
if tts.is_empty() {
self.render.cx.span_err(sp, "expected expression for this splice");
self.render.cx.span_fatal(sp, "expected expression for this splice");
} else {
let expr = self.new_rust_parser(tts).parse_expr();
self.render.splice(expr, escape);
self.new_rust_parser(tts).parse_expr()
}
}
@ -175,14 +179,10 @@ impl<'cx, 's, 'i, 'r, 'o> Parser<'cx, 's, 'i, 'r, 'o> {
}
fn attrs(&mut self) {
while let [ident!(name), eq!(), ..] = self.input {
self.shift(2);
if let [not!(), ..] = self.input {
// Empty attribute
self.shift(1);
self.render.attribute_empty(name.as_str());
} else {
loop { match self.input {
[ident!(name), eq!(), ..] => {
// Non-empty attribute
self.shift(2);
self.render.attribute_start(name.as_str());
{
// Parse a value under an attribute context
@ -192,8 +192,22 @@ impl<'cx, 's, 'i, 'r, 'o> Parser<'cx, 's, 'i, 'r, 'o> {
self.in_attr = old_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
self.shift(1);
let expr = self.splice(tt.get_span());
self.render.attribute_empty_if(name.as_str(), expr);
} else {
// Write the attribute unconditionally
self.render.attribute_empty(name.as_str());
}
},
_ => return,
}}
}
fn block(&mut self, tts: &[TokenTree]) {

View file

@ -43,8 +43,13 @@ impl<'cx, 's, 'o> Renderer<'cx, 's, 'o> {
/// Push an expression statement, also wrapping it with `try!`.
fn push(&mut self, expr: P<Expr>) {
let expr = self.cx.stmt_expr(self.cx.expr_try(expr.span, expr));
self.stmts.push(expr);
let stmt = self.make_stmt(expr);
self.stmts.push(stmt);
}
/// Create an expression statement, also wrapping it with `try!`.
fn make_stmt(&mut self, expr: P<Expr>) -> P<Stmt> {
self.cx.stmt_expr(self.cx.expr_try(expr.span, expr))
}
/// Append a literal pre-escaped string.
@ -94,6 +99,19 @@ impl<'cx, 's, 'o> Renderer<'cx, 's, 'o> {
self.write(name);
}
pub fn attribute_empty_if(&mut self, name: &str, expr: P<Expr>) {
let s: String = [" ", name].concat();
let s = &s[];
let w = self.w;
let expr = quote_expr!(self.cx,
if $expr {
$w.write_str($s)
} else {
Ok(())
});
self.push(expr);
}
pub fn attribute_end(&mut self) {
self.write("\"");
}

View file

@ -76,7 +76,7 @@ mod elements {
#[test]
fn empty_attributes() {
let s = html! { div readonly=! input type="checkbox" checked=! / }.to_string();
let s = html! { div readonly? input type="checkbox" checked? / }.to_string();
assert_eq!(s, r#"<div readonly><input type="checkbox" checked></div>"#);
}
}
@ -108,6 +108,22 @@ mod splices {
assert_eq!(s, "3628800");
}
#[test]
fn attributes() {
let rocks = true;
let s = html! {
input checked?=true /
input checked?=false /
input checked?=rocks /
input checked?=(!rocks) /
}.to_string();
assert_eq!(s, concat!(
r#"<input checked>"#,
r#"<input>"#,
r#"<input checked>"#,
r#"<input>"#));
}
static BEST_PONY: &'static str = "Pinkie Pie";
#[test]