Change splice operator and add in render by-move

This commit is contained in:
Wim Looman 2016-02-02 15:22:45 +01:00
parent 0440f5c74f
commit 6c8fbb5bad
4 changed files with 84 additions and 32 deletions
maud/src
maud_macros

View file

@ -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);

View file

@ -23,9 +23,6 @@ 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))
}
@ -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(..)))
}
@ -172,19 +172,11 @@ impl<'cx, 'i> Parser<'cx, 'i> {
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());
@ -308,9 +300,9 @@ impl<'cx, 'i> Parser<'cx, 'i> {
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());

View file

@ -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);
}

View file

@ -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, "&lt;pinkie&gt;");
}
@ -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;
}
@ -225,7 +225,7 @@ mod control {
let mut s = String::new();
html!(s, {
#if let Some(value) = input {
$value
^value
} #else {
"oh noes"
}
@ -240,7 +240,7 @@ mod control {
let mut s = String::new();
html!(s, {
ul #for pony in &ponies {
li $pony
li ^pony
}
}).unwrap();
assert_eq!(s, concat!(
@ -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,6 +327,55 @@ fn splice_with_path() {
}
let mut s = String::new();
html!(s, $inner::name()).unwrap();
html!(s, ^inner::name()).unwrap();
assert_eq!(s, "Maud");
}
#[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 ");
}