commit
f113623657
4 changed files with 181 additions and 57 deletions
|
@ -23,6 +23,17 @@ impl<T: fmt::Display + ?Sized> Render for T {
|
|||
}
|
||||
}
|
||||
|
||||
/// Represents a type that can be rendered as HTML just once.
|
||||
pub trait RenderOnce {
|
||||
fn render_once(self, &mut fmt::Write) -> fmt::Result;
|
||||
}
|
||||
|
||||
impl<'a, T: Render + ?Sized> RenderOnce for &'a T {
|
||||
fn render_once(self, w: &mut fmt::Write) -> fmt::Result {
|
||||
Render::render(self, w)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper that renders the inner value without escaping.
|
||||
#[derive(Debug)]
|
||||
pub struct PreEscaped<T>(pub T);
|
||||
|
|
|
@ -23,11 +23,8 @@ macro_rules! parse_error {
|
|||
($self_:expr, $sp:expr, $msg:expr) => (error!($self_.render.cx, $sp, $msg))
|
||||
}
|
||||
|
||||
macro_rules! dollar {
|
||||
() => (TokenTree::Token(_, Token::Dollar))
|
||||
}
|
||||
macro_rules! pound {
|
||||
() => (TokenTree::Token(_, Token::Pound))
|
||||
macro_rules! at {
|
||||
() => (TokenTree::Token(_, Token::At))
|
||||
}
|
||||
macro_rules! dot {
|
||||
() => (TokenTree::Token(_, Token::Dot))
|
||||
|
@ -53,6 +50,9 @@ macro_rules! minus {
|
|||
macro_rules! slash {
|
||||
() => (TokenTree::Token(_, Token::BinOp(BinOpToken::Slash)))
|
||||
}
|
||||
macro_rules! caret {
|
||||
() => (TokenTree::Token(_, Token::BinOp(BinOpToken::Caret)))
|
||||
}
|
||||
macro_rules! literal {
|
||||
() => (TokenTree::Token(_, Token::Literal(..)))
|
||||
}
|
||||
|
@ -156,35 +156,27 @@ impl<'cx, 'i> Parser<'cx, 'i> {
|
|||
try!(self.literal(tt, false))
|
||||
},
|
||||
// If
|
||||
[pound!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::If) => {
|
||||
[at!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::If) => {
|
||||
self.shift(2);
|
||||
try!(self.if_expr(sp));
|
||||
},
|
||||
// For
|
||||
[pound!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::For) => {
|
||||
[at!(), keyword!(sp, k), ..] if k.is_keyword(Keyword::For) => {
|
||||
self.shift(2);
|
||||
try!(self.for_expr(sp));
|
||||
},
|
||||
// Call
|
||||
[pound!(), ident!(sp, name), ..] if name.name.as_str() == "call" => {
|
||||
[at!(), ident!(sp, name), ..] if name.name.as_str() == "call" => {
|
||||
self.shift(2);
|
||||
let func = try!(self.splice(sp));
|
||||
self.render.emit_call(func);
|
||||
},
|
||||
// Splice
|
||||
[ref tt @ dollar!(), ..] => {
|
||||
[ref tt @ caret!(), ..] => {
|
||||
self.shift(1);
|
||||
let expr = try!(self.splice(tt.get_span()));
|
||||
self.render.splice(expr);
|
||||
},
|
||||
[substnt!(sp, ident), ..] => {
|
||||
self.shift(1);
|
||||
// Parse `SubstNt` as `[Dollar, Ident]`
|
||||
// See <https://github.com/lfairy/maud/issues/23>
|
||||
let prefix = TokenTree::Token(sp, Token::Ident(ident, IdentStyle::Plain));
|
||||
let expr = try!(self.splice_with_prefix(prefix));
|
||||
self.render.splice(expr);
|
||||
},
|
||||
// Element
|
||||
[ident!(sp, _), ..] => {
|
||||
let name = try!(self.name());
|
||||
|
@ -222,9 +214,9 @@ impl<'cx, 'i> Parser<'cx, 'i> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses and renders an `#if` expression.
|
||||
/// Parses and renders an `@if` expression.
|
||||
///
|
||||
/// The leading `#if` should already be consumed.
|
||||
/// The leading `@if` should already be consumed.
|
||||
fn if_expr(&mut self, sp: Span) -> PResult<()> {
|
||||
// Parse the initial if
|
||||
let mut if_cond = vec![];
|
||||
|
@ -239,11 +231,11 @@ impl<'cx, 'i> Parser<'cx, 'i> {
|
|||
self.shift(1);
|
||||
if_cond.push(tt.clone());
|
||||
},
|
||||
[] => parse_error!(self, sp, "expected body for this #if"),
|
||||
[] => parse_error!(self, sp, "expected body for this @if"),
|
||||
}}
|
||||
// Parse the (optional) else
|
||||
// Parse the (optional) @else
|
||||
let else_body = match self.input {
|
||||
[pound!(), keyword!(_, k), ..] if k.is_keyword(Keyword::Else) => {
|
||||
[at!(), keyword!(_, k), ..] if k.is_keyword(Keyword::Else) => {
|
||||
self.shift(2);
|
||||
match self.input {
|
||||
[keyword!(sp, k), ..] if k.is_keyword(Keyword::If) => {
|
||||
|
@ -263,7 +255,7 @@ impl<'cx, 'i> Parser<'cx, 'i> {
|
|||
self.shift(1);
|
||||
Some(try!(self.block(sp, &d.tts)))
|
||||
},
|
||||
_ => parse_error!(self, sp, "expected body for this #else"),
|
||||
_ => parse_error!(self, sp, "expected body for this @else"),
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
|
@ -272,9 +264,9 @@ impl<'cx, 'i> Parser<'cx, 'i> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses and renders a `#for` expression.
|
||||
/// Parses and renders a `@for` expression.
|
||||
///
|
||||
/// The leading `#for` should already be consumed.
|
||||
/// The leading `@for` should already be consumed.
|
||||
fn for_expr(&mut self, sp: Span) -> PResult<()> {
|
||||
let mut pattern = vec![];
|
||||
loop { match self.input {
|
||||
|
@ -286,7 +278,7 @@ impl<'cx, 'i> Parser<'cx, 'i> {
|
|||
self.shift(1);
|
||||
pattern.push(tt.clone());
|
||||
},
|
||||
_ => parse_error!(self, sp, "invalid #for"),
|
||||
_ => parse_error!(self, sp, "invalid @for"),
|
||||
}}
|
||||
let pattern = try!(self.with_rust_parser(pattern, RustParser::parse_pat));
|
||||
let mut iterable = vec![];
|
||||
|
@ -301,16 +293,16 @@ impl<'cx, 'i> Parser<'cx, 'i> {
|
|||
self.shift(1);
|
||||
iterable.push(tt.clone());
|
||||
},
|
||||
_ => parse_error!(self, sp, "invalid #for"),
|
||||
_ => parse_error!(self, sp, "invalid @for"),
|
||||
}}
|
||||
let iterable = try!(self.with_rust_parser(iterable, RustParser::parse_expr));
|
||||
self.render.emit_for(pattern, iterable, body);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses and renders a `$splice`.
|
||||
/// Parses and renders a `^splice`.
|
||||
///
|
||||
/// The leading `$` should already be consumed.
|
||||
/// The leading `^` should already be consumed.
|
||||
fn splice(&mut self, sp: Span) -> PResult<P<Expr>> {
|
||||
// First, munch a single token tree
|
||||
let prefix = match self.input {
|
||||
|
@ -323,24 +315,24 @@ impl<'cx, 'i> Parser<'cx, 'i> {
|
|||
self.splice_with_prefix(prefix)
|
||||
}
|
||||
|
||||
/// Parses and renders a `$splice`, given a prefix that we've already
|
||||
/// Parses and renders a `^splice`, given a prefix that we've already
|
||||
/// consumed.
|
||||
fn splice_with_prefix(&mut self, prefix: TokenTree) -> PResult<P<Expr>> {
|
||||
let mut tts = vec![prefix];
|
||||
loop { match self.input {
|
||||
// Munch attribute lookups e.g. `$person.address.street`
|
||||
// Munch attribute lookups e.g. `^person.address.street`
|
||||
[ref dot @ dot!(), ref ident @ ident!(_, _), ..] => {
|
||||
self.shift(2);
|
||||
tts.push(dot.clone());
|
||||
tts.push(ident.clone());
|
||||
},
|
||||
// Munch tuple attribute lookups e.g. `$person.1.2`
|
||||
// Munch tuple attribute lookups e.g. `^person.1.2`
|
||||
[ref dot @ dot!(), ref num @ integer!(), ..] => {
|
||||
self.shift(2);
|
||||
tts.push(dot.clone());
|
||||
tts.push(num.clone());
|
||||
},
|
||||
// Munch path lookups e.g. `$some_mod::Struct`
|
||||
// Munch path lookups e.g. `^some_mod::Struct`
|
||||
[ref sep @ modsep!(), ref ident @ ident!(_, _), ..] => {
|
||||
self.shift(2);
|
||||
tts.push(sep.clone());
|
||||
|
|
|
@ -118,7 +118,7 @@ impl<'cx> Renderer<'cx> {
|
|||
/// Appends the result of an expression, with the specified escaping method.
|
||||
pub fn splice(&mut self, expr: P<Expr>) {
|
||||
let w = self.writer;
|
||||
let expr = quote_expr!(self.cx, ::maud::Render::render(&$expr, &mut *$w));
|
||||
let expr = quote_expr!(self.cx, { use ::maud::RenderOnce; $expr.render_once(&mut *$w) });
|
||||
let stmt = self.wrap_try(expr);
|
||||
self.push(stmt);
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ mod splices {
|
|||
#[test]
|
||||
fn literals() {
|
||||
let mut s = String::new();
|
||||
html!(s, $"<pinkie>").unwrap();
|
||||
html!(s, ^"<pinkie>").unwrap();
|
||||
assert_eq!(s, "<pinkie>");
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ mod splices {
|
|||
fn raw_literals() {
|
||||
use maud::PreEscaped;
|
||||
let mut s = String::new();
|
||||
html!(s, $PreEscaped("<pinkie>")).unwrap();
|
||||
html!(s, ^PreEscaped("<pinkie>")).unwrap();
|
||||
assert_eq!(s, "<pinkie>");
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,7 @@ mod splices {
|
|||
fn blocks() {
|
||||
let mut s = String::new();
|
||||
html!(s, {
|
||||
${
|
||||
^{
|
||||
let mut result = 1i32;
|
||||
for i in 2..11 {
|
||||
result *= i;
|
||||
|
@ -142,7 +142,7 @@ mod splices {
|
|||
#[test]
|
||||
fn statics() {
|
||||
let mut s = String::new();
|
||||
html!(s, $BEST_PONY).unwrap();
|
||||
html!(s, ^BEST_PONY).unwrap();
|
||||
assert_eq!(s, "Pinkie Pie");
|
||||
}
|
||||
|
||||
|
@ -150,7 +150,7 @@ mod splices {
|
|||
fn closures() {
|
||||
let best_pony = "Pinkie Pie";
|
||||
let mut s = String::new();
|
||||
html!(s, $best_pony).unwrap();
|
||||
html!(s, ^best_pony).unwrap();
|
||||
assert_eq!(s, "Pinkie Pie");
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,7 @@ mod splices {
|
|||
};
|
||||
let mut s = String::new();
|
||||
html!(s, {
|
||||
"Name: " $pinkie.name ". Rating: " $pinkie.repugnance()
|
||||
"Name: " ^pinkie.name ". Rating: " ^pinkie.repugnance()
|
||||
}).unwrap();
|
||||
assert_eq!(s, "Name: Pinkie Pie. Rating: 1");
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ mod splices {
|
|||
fn nested_macro_invocation() {
|
||||
let best_pony = "Pinkie Pie";
|
||||
let mut s = String::new();
|
||||
html!(s, $(format!("{}", best_pony))).unwrap();
|
||||
html!(s, ^(format!("{}", best_pony))).unwrap();
|
||||
assert_eq!(s, "Pinkie Pie");
|
||||
}
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ mod splices {
|
|||
fn issue_13() {
|
||||
let owned = String::from("yay");
|
||||
let mut s = String::new();
|
||||
html!(s, $owned).unwrap();
|
||||
html!(s, ^owned).unwrap();
|
||||
let _ = owned;
|
||||
}
|
||||
|
||||
|
@ -205,13 +205,13 @@ mod control {
|
|||
for (number, &name) in (1..4).zip(["one", "two", "three"].iter()) {
|
||||
let mut s = String::new();
|
||||
html!(s, {
|
||||
#if number == 1 {
|
||||
@if number == 1 {
|
||||
"one"
|
||||
} #else if number == 2 {
|
||||
} @else if number == 2 {
|
||||
"two"
|
||||
} #else if number == 3 {
|
||||
} @else if number == 3 {
|
||||
"three"
|
||||
} #else {
|
||||
} @else {
|
||||
"oh noes"
|
||||
}
|
||||
}).unwrap();
|
||||
|
@ -224,9 +224,9 @@ mod control {
|
|||
for &(input, output) in [(Some("yay"), "yay"), (None, "oh noes")].iter() {
|
||||
let mut s = String::new();
|
||||
html!(s, {
|
||||
#if let Some(value) = input {
|
||||
$value
|
||||
} #else {
|
||||
@if let Some(value) = input {
|
||||
^value
|
||||
} @else {
|
||||
"oh noes"
|
||||
}
|
||||
}).unwrap();
|
||||
|
@ -239,8 +239,8 @@ mod control {
|
|||
let ponies = ["Apple Bloom", "Scootaloo", "Sweetie Belle"];
|
||||
let mut s = String::new();
|
||||
html!(s, {
|
||||
ul #for pony in &ponies {
|
||||
li $pony
|
||||
ul @for pony in &ponies {
|
||||
li ^pony
|
||||
}
|
||||
}).unwrap();
|
||||
assert_eq!(s, concat!(
|
||||
|
@ -288,9 +288,9 @@ fn call() {
|
|||
panic!("oh noes")
|
||||
};
|
||||
html!(s, {
|
||||
#call ducks
|
||||
#call (|w: &mut fmt::Write| write!(w, "Geese"))
|
||||
#call swans(true)
|
||||
@call ducks
|
||||
@call (|w: &mut fmt::Write| write!(w, "Geese"))
|
||||
@call swans(true)
|
||||
}).unwrap();
|
||||
assert_eq!(s, "DucksGeeseSwans");
|
||||
}
|
||||
|
@ -306,7 +306,7 @@ fn issue_23() {
|
|||
}
|
||||
|
||||
let name = "Lyra";
|
||||
let s = to_string!(p { "Hi, " $name "!" });
|
||||
let s = to_string!(p { "Hi, " ^name "!" });
|
||||
assert_eq!(s, "<p>Hi, Lyra!</p>");
|
||||
}
|
||||
|
||||
|
@ -314,7 +314,7 @@ fn issue_23() {
|
|||
fn tuple_accessors() {
|
||||
let mut s = String::new();
|
||||
let a = ("ducks", "geese");
|
||||
html!(s, { $a.0 }).unwrap();
|
||||
html!(s, { ^a.0 }).unwrap();
|
||||
assert_eq!(s, "ducks");
|
||||
}
|
||||
|
||||
|
@ -327,7 +327,7 @@ fn splice_with_path() {
|
|||
}
|
||||
|
||||
let mut s = String::new();
|
||||
html!(s, $inner::name()).unwrap();
|
||||
html!(s, ^inner::name()).unwrap();
|
||||
assert_eq!(s, "Maud");
|
||||
}
|
||||
|
||||
|
@ -365,3 +365,124 @@ fn classes_shorthand_with_attrs() {
|
|||
html!(s, p { "Hi, " span.name.here id="thing" { "Lyra" } "!" }).unwrap();
|
||||
assert_eq!(s, "<p>Hi, <span class=\"name here\" id=\"thing\">Lyra</span>!</p>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multirender() {
|
||||
struct R<'a>(&'a str);
|
||||
impl<'a> maud::Render for R<'a> {
|
||||
fn render(&self, w: &mut std::fmt::Write) -> std::fmt::Result {
|
||||
w.write_str(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
let mut s = String::new();
|
||||
let r = R("pinkie ");
|
||||
html!(s, ^r).unwrap();
|
||||
html!(s, ^r).unwrap();
|
||||
// R is not-Copyable so this shows that it will auto-ref splice arguments that implement Render.
|
||||
assert_eq!(s, "pinkie pinkie ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_once_by_move() {
|
||||
struct Once<'a>(&'a str);
|
||||
impl<'a> maud::RenderOnce for Once<'a> {
|
||||
fn render_once(self, w: &mut std::fmt::Write) -> std::fmt::Result {
|
||||
w.write_str(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
let mut s = String::new();
|
||||
let once = Once("pinkie");
|
||||
html!(s, ^once).unwrap();
|
||||
assert_eq!(s, "pinkie");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_once_by_move_with_copy() {
|
||||
#[derive(Clone, Copy)]
|
||||
struct Once<'a>(&'a str);
|
||||
impl<'a> maud::RenderOnce for Once<'a> {
|
||||
fn render_once(self, w: &mut std::fmt::Write) -> std::fmt::Result {
|
||||
w.write_str(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
let mut s = String::new();
|
||||
let once = Once("pinkie ");
|
||||
html!(s, ^once).unwrap();
|
||||
html!(s, ^once).unwrap();
|
||||
assert_eq!(s, "pinkie pinkie ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_26() {
|
||||
macro_rules! to_string {
|
||||
($($x:tt)*) => {{
|
||||
let mut s = String::new();
|
||||
html!(s, $($x)*).unwrap();
|
||||
s
|
||||
}}
|
||||
}
|
||||
|
||||
let name = "Lyra";
|
||||
let s = to_string!(p { "Hi, " ^(name) "!" });
|
||||
assert_eq!(s, "<p>Hi, Lyra!</p>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_26_2() {
|
||||
macro_rules! to_string {
|
||||
($($x:tt)*) => {{
|
||||
let mut s = String::new();
|
||||
html!(s, $($x)*).unwrap();
|
||||
s
|
||||
}}
|
||||
}
|
||||
|
||||
let name = "Lyra";
|
||||
let s = to_string!(p { "Hi, " ^("person called ".to_string() + name) "!" });
|
||||
assert_eq!(s, "<p>Hi, person called Lyra!</p>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_26_3() {
|
||||
macro_rules! to_string {
|
||||
($($x:tt)*) => {{
|
||||
let mut s = String::new();
|
||||
html!(s, $($x)*).unwrap();
|
||||
s
|
||||
}}
|
||||
}
|
||||
|
||||
let name = "Lyra";
|
||||
let s = to_string!(p { "Hi, " ^{"person called ".to_string() + name} "!" });
|
||||
assert_eq!(s, "<p>Hi, person called Lyra!</p>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_21() {
|
||||
macro_rules! greet {
|
||||
() => ({
|
||||
let mut result = String::new();
|
||||
let name = "Pinkie Pie";
|
||||
html!(result, p { "Hello, " ^name "!" }).map(|()| result)
|
||||
})
|
||||
}
|
||||
|
||||
let s = greet!().unwrap();
|
||||
assert_eq!(s, "<p>Hello, Pinkie Pie!</p>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_21_2() {
|
||||
macro_rules! greet {
|
||||
($name:expr) => ({
|
||||
let mut result = String::new();
|
||||
html!(result, p { "Hello, " ^$name "!" }).map(|()| result)
|
||||
})
|
||||
}
|
||||
|
||||
let s = greet!("Pinkie Pie").unwrap();
|
||||
assert_eq!(s, "<p>Hello, Pinkie Pie!</p>");
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue