parent
abbb565412
commit
1badbd9df2
5 changed files with 109 additions and 113 deletions
|
@ -5,9 +5,6 @@
|
|||
//!
|
||||
//! [book]: http://lfairy.gitbooks.io/maud/content/
|
||||
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
/// Escapes an HTML value.
|
||||
pub fn escape(s: &str) -> String {
|
||||
use std::fmt::Write;
|
||||
|
@ -16,68 +13,11 @@ pub fn escape(s: &str) -> String {
|
|||
buf
|
||||
}
|
||||
|
||||
/// A block of HTML markup, as returned by the `html!` macro.
|
||||
///
|
||||
/// Use `.to_string()` to convert it to a `String`, or `.render()` to
|
||||
/// write it directly to a handle.
|
||||
pub struct Markup<F> {
|
||||
callback: F,
|
||||
}
|
||||
|
||||
impl<F> Markup<F> where F: Fn(&mut fmt::Write) -> fmt::Result {
|
||||
/// Renders the markup to a `std::io::Write`.
|
||||
pub fn render<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
struct Adaptor<'a, W: ?Sized + 'a> {
|
||||
inner: &'a mut W,
|
||||
error: io::Result<()>,
|
||||
}
|
||||
|
||||
impl<'a, W: ?Sized + io::Write> fmt::Write for Adaptor<'a, W> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
match self.inner.write_all(s.as_bytes()) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => {
|
||||
self.error = Err(e);
|
||||
Err(fmt::Error)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut output = Adaptor { inner: w, error: Ok(()) };
|
||||
match self.render_fmt(&mut output) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) => output.error,
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the markup to a `std::fmt::Write`.
|
||||
pub fn render_fmt(&self, w: &mut fmt::Write) -> fmt::Result {
|
||||
(self.callback)(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> ToString for Markup<F> where F: Fn(&mut fmt::Write) -> fmt::Result {
|
||||
fn to_string(&self) -> String {
|
||||
let mut buf = String::new();
|
||||
self.render_fmt(&mut buf).unwrap();
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal functions used by the `maud_macros` package. You should
|
||||
/// never need to call these directly.
|
||||
#[doc(hidden)]
|
||||
pub mod rt {
|
||||
use std::fmt;
|
||||
use super::Markup;
|
||||
|
||||
#[inline]
|
||||
pub fn make_markup<F>(f: F) -> Markup<F> where
|
||||
F: Fn(&mut fmt::Write) -> fmt::Result
|
||||
{
|
||||
Markup { callback: f }
|
||||
}
|
||||
|
||||
pub struct Escaper<'a, 'b: 'a> {
|
||||
pub inner: &'a mut (fmt::Write + 'b),
|
||||
|
|
|
@ -36,6 +36,6 @@ fn expand_html_debug<'cx>(cx: &'cx mut ExtCtxt, sp: Span, args: &[TokenTree]) ->
|
|||
|
||||
#[plugin_registrar]
|
||||
pub fn plugin_registrar(reg: &mut Registry) {
|
||||
reg.register_macro("html", expand_html);
|
||||
reg.register_macro("html_debug", expand_html_debug);
|
||||
reg.register_macro("write_html", expand_html);
|
||||
reg.register_macro("write_html_debug", expand_html_debug);
|
||||
}
|
||||
|
|
|
@ -43,16 +43,30 @@ macro_rules! ident {
|
|||
}
|
||||
|
||||
pub fn parse(cx: &ExtCtxt, input: &[TokenTree], sp: Span) -> P<Expr> {
|
||||
let (write, input) = split_comma(cx, input, sp);
|
||||
let mut parser = Parser {
|
||||
in_attr: false,
|
||||
input: input,
|
||||
span: sp,
|
||||
render: Renderer::new(cx),
|
||||
render: Renderer::new(cx, write.to_vec()),
|
||||
};
|
||||
parser.markups();
|
||||
parser.into_render().into_expr()
|
||||
}
|
||||
|
||||
fn split_comma<'a>(cx: &ExtCtxt, input: &'a [TokenTree], sp: Span) -> (&'a [TokenTree], &'a [TokenTree]) {
|
||||
fn is_comma(t: &TokenTree) -> bool {
|
||||
match *t {
|
||||
TtToken(_, token::Comma) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
match input.iter().position(is_comma) {
|
||||
Some(i) => (&input[..i], &input[1+i..]),
|
||||
None => cx.span_fatal(sp, "expected two arguments to write_html!()"),
|
||||
}
|
||||
}
|
||||
|
||||
struct Parser<'cx, 'i> {
|
||||
in_attr: bool,
|
||||
input: &'i [TokenTree],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use syntax::ast::{Expr, Ident, Pat, Stmt, TokenTree};
|
||||
use syntax::ast::{Expr, Ident, Pat, Stmt, TokenTree, TtToken};
|
||||
use syntax::codemap::DUMMY_SP;
|
||||
use syntax::ext::base::ExtCtxt;
|
||||
use syntax::ext::build::AstBuilder;
|
||||
use syntax::parse::token;
|
||||
use syntax::ptr::P;
|
||||
|
||||
|
@ -15,17 +15,25 @@ pub enum Escape {
|
|||
pub struct Renderer<'cx> {
|
||||
pub cx: &'cx ExtCtxt<'cx>,
|
||||
w: Ident,
|
||||
r: Ident,
|
||||
loop_label: Vec<TokenTree>,
|
||||
stmts: Vec<P<Stmt>>,
|
||||
tail: String,
|
||||
}
|
||||
|
||||
impl<'cx> Renderer<'cx> {
|
||||
/// Creates a new `Renderer` using the given extension context.
|
||||
pub fn new(cx: &'cx ExtCtxt<'cx>) -> Renderer<'cx> {
|
||||
pub fn new(cx: &'cx ExtCtxt<'cx>, writer_expr: Vec<TokenTree>) -> Renderer<'cx> {
|
||||
let w = token::gensym_ident("__maud_writer");
|
||||
let r = token::gensym_ident("__maud_result");
|
||||
let loop_label = token::gensym_ident("__maud_loop_label");
|
||||
let writer_stmt = quote_stmt!(cx, let $w = &mut $writer_expr).unwrap();
|
||||
Renderer {
|
||||
cx: cx,
|
||||
w: Ident::new(token::intern("w")),
|
||||
stmts: Vec::new(),
|
||||
w: w,
|
||||
r: r,
|
||||
loop_label: vec![TtToken(DUMMY_SP, token::Lifetime(loop_label))],
|
||||
stmts: vec![writer_stmt],
|
||||
tail: String::new(),
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +43,8 @@ impl<'cx> Renderer<'cx> {
|
|||
Renderer {
|
||||
cx: self.cx,
|
||||
w: self.w,
|
||||
r: self.r,
|
||||
loop_label: self.loop_label.clone(),
|
||||
stmts: Vec::new(),
|
||||
tail: String::new(),
|
||||
}
|
||||
|
@ -48,7 +58,7 @@ impl<'cx> Renderer<'cx> {
|
|||
let s = &*self.tail;
|
||||
quote_expr!(self.cx, $w.write_str($s))
|
||||
};
|
||||
let stmt = self.cx.stmt_expr(self.cx.expr_try(expr.span, expr));
|
||||
let stmt = self.wrap_try(expr);
|
||||
self.stmts.push(stmt);
|
||||
self.tail.clear();
|
||||
}
|
||||
|
@ -56,13 +66,16 @@ impl<'cx> Renderer<'cx> {
|
|||
|
||||
/// Reifies the `Renderer` into a block of markup.
|
||||
pub fn into_expr(mut self) -> P<Expr> {
|
||||
let Renderer { cx, w, stmts, .. } = { self.flush(); self };
|
||||
quote_expr!(cx,
|
||||
::maud::rt::make_markup(|$w: &mut ::std::fmt::Write| -> Result<(), ::std::fmt::Error> {
|
||||
let Renderer { cx, r, loop_label, stmts, .. } = { self.flush(); self };
|
||||
quote_expr!(cx, {
|
||||
let mut $r = Ok(());
|
||||
$loop_label: loop {
|
||||
use ::std::fmt::Write;
|
||||
$stmts
|
||||
Ok(())
|
||||
}))
|
||||
break $loop_label;
|
||||
}
|
||||
$r
|
||||
})
|
||||
}
|
||||
|
||||
/// Reifies the `Renderer` into a raw list of statements.
|
||||
|
@ -82,6 +95,21 @@ impl<'cx> Renderer<'cx> {
|
|||
self.tail.push_str(s);
|
||||
}
|
||||
|
||||
/// Wraps an expression in a `try!` call.
|
||||
fn wrap_try(&self, expr: P<Expr>) -> P<Stmt> {
|
||||
let r = self.r;
|
||||
let loop_label = &self.loop_label;
|
||||
quote_stmt!(
|
||||
self.cx,
|
||||
match $expr {
|
||||
Ok(()) => {},
|
||||
Err(e) => {
|
||||
$r = Err(e);
|
||||
break $loop_label;
|
||||
}
|
||||
}).unwrap()
|
||||
}
|
||||
|
||||
/// Appends a literal string, with the specified escaping method.
|
||||
pub fn string(&mut self, s: &str, escape: Escape) {
|
||||
let escaped;
|
||||
|
@ -105,7 +133,7 @@ impl<'cx> Renderer<'cx> {
|
|||
"{}",
|
||||
$expr)),
|
||||
};
|
||||
let stmt = self.cx.stmt_expr(self.cx.expr_try(expr.span, expr));
|
||||
let stmt = self.wrap_try(expr);
|
||||
self.push(stmt);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,69 +5,75 @@ extern crate maud;
|
|||
|
||||
#[test]
|
||||
fn literals() {
|
||||
let s = html!("du\tcks" -23 3.14 '\n' "geese").to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, "du\tcks" -23 3.14 '\n' "geese").unwrap();
|
||||
assert_eq!(s, "du\tcks-233.14\ngeese");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escaping() {
|
||||
let s = html!("<flim&flam>").to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, "<flim&flam>").unwrap();
|
||||
assert_eq!(s, "<flim&flam>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semicolons() {
|
||||
let s = html! {
|
||||
let mut s = String::new();
|
||||
write_html!(s, {
|
||||
"one";
|
||||
"two";
|
||||
"three";
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
"four";
|
||||
}.to_string();
|
||||
}).unwrap();
|
||||
assert_eq!(s, "onetwothreefour");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocks() {
|
||||
let s = html! {
|
||||
let mut s = String::new();
|
||||
write_html!(s, {
|
||||
"hello"
|
||||
{
|
||||
" ducks" " geese"
|
||||
}
|
||||
" swans"
|
||||
}.to_string();
|
||||
}).unwrap();
|
||||
assert_eq!(s, "hello ducks geese swans");
|
||||
}
|
||||
|
||||
mod elements {
|
||||
#[test]
|
||||
fn simple() {
|
||||
let s = html! {
|
||||
p { b { "pickle" } "barrel" i { "kumquat" } }
|
||||
}.to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, p { b { "pickle" } "barrel" i { "kumquat" } }).unwrap();
|
||||
assert_eq!(s, "<p><b>pickle</b>barrel<i>kumquat</i></p>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nesting() {
|
||||
let s = html!(html body div p sup "butts").to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, html body div p sup "butts").unwrap();
|
||||
assert_eq!(s, "<html><body><div><p><sup>butts</sup></p></div></body></html>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let s = html!("pinkie" br/ "pie").to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, "pinkie" br/ "pie").unwrap();
|
||||
assert_eq!(s, "pinkie<br>pie");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes() {
|
||||
let s = html! {
|
||||
let mut s = String::new();
|
||||
write_html!(s, {
|
||||
link rel="stylesheet" href="styles.css"/
|
||||
section id="midriff" {
|
||||
p class="hotpink" "Hello!"
|
||||
}
|
||||
}.to_string();
|
||||
}).unwrap();
|
||||
assert_eq!(s, concat!(
|
||||
r#"<link rel="stylesheet" href="styles.css">"#,
|
||||
r#"<section id="midriff"><p class="hotpink">Hello!</p></section>"#));
|
||||
|
@ -75,7 +81,8 @@ mod elements {
|
|||
|
||||
#[test]
|
||||
fn empty_attributes() {
|
||||
let s = html! { div readonly? input type="checkbox" checked? / }.to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, div readonly? input type="checkbox" checked? /).unwrap();
|
||||
assert_eq!(s, r#"<div readonly><input type="checkbox" checked></div>"#);
|
||||
}
|
||||
}
|
||||
|
@ -83,19 +90,22 @@ mod elements {
|
|||
mod splices {
|
||||
#[test]
|
||||
fn literals() {
|
||||
let s = html! { $"<pinkie>" }.to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, $"<pinkie>").unwrap();
|
||||
assert_eq!(s, "<pinkie>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_literals() {
|
||||
let s = html! { $$"<pinkie>" }.to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, $$"<pinkie>").unwrap();
|
||||
assert_eq!(s, "<pinkie>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocks() {
|
||||
let s = html! {
|
||||
let mut s = String::new();
|
||||
write_html!(s, {
|
||||
${
|
||||
let mut result = 1i32;
|
||||
for i in 2..11 {
|
||||
|
@ -103,19 +113,20 @@ mod splices {
|
|||
}
|
||||
result
|
||||
}
|
||||
}.to_string();
|
||||
}).unwrap();
|
||||
assert_eq!(s, "3628800");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes() {
|
||||
let rocks = true;
|
||||
let s = html! {
|
||||
let mut s = String::new();
|
||||
write_html!(s, {
|
||||
input checked?=true /
|
||||
input checked?=false /
|
||||
input checked?=rocks /
|
||||
input checked?=(!rocks) /
|
||||
}.to_string();
|
||||
}).unwrap();
|
||||
assert_eq!(s, concat!(
|
||||
r#"<input checked>"#,
|
||||
r#"<input>"#,
|
||||
|
@ -127,14 +138,16 @@ mod splices {
|
|||
|
||||
#[test]
|
||||
fn statics() {
|
||||
let s = html! { $BEST_PONY }.to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, $BEST_PONY).unwrap();
|
||||
assert_eq!(s, "Pinkie Pie");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closures() {
|
||||
let best_pony = "Pinkie Pie";
|
||||
let s = html! { $best_pony }.to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, $best_pony).unwrap();
|
||||
assert_eq!(s, "Pinkie Pie");
|
||||
}
|
||||
|
||||
|
@ -159,37 +172,36 @@ mod splices {
|
|||
name: "Pinkie Pie",
|
||||
adorableness: 9,
|
||||
};
|
||||
let s = html! {
|
||||
let mut s = String::new();
|
||||
write_html!(s, {
|
||||
"Name: " $pinkie.name ". Rating: " $pinkie.repugnance()
|
||||
}.to_string();
|
||||
}).unwrap();
|
||||
assert_eq!(s, "Name: Pinkie Pie. Rating: 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_macro_invocation() {
|
||||
let best_pony = "Pinkie Pie";
|
||||
let s = html! { $(format!("{}", best_pony)) }.to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, $(format!("{}", best_pony))).unwrap();
|
||||
assert_eq!(s, "Pinkie Pie");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_1() {
|
||||
let markup = html! { "Test" };
|
||||
let _ = markup.to_string();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_13() {
|
||||
let owned = String::from("yay");
|
||||
let _ = html! { $owned }.to_string();
|
||||
let mut s = String::new();
|
||||
write_html!(s, $owned).unwrap();
|
||||
let _ = owned;
|
||||
}
|
||||
|
||||
mod control {
|
||||
#[test]
|
||||
fn if_expr() {
|
||||
for (number, &name) in (1..4).zip(["one", "two", "three"].iter()) {
|
||||
let s = html! {
|
||||
let mut s = String::new();
|
||||
write_html!(s, {
|
||||
$if number == 1 {
|
||||
"one"
|
||||
} $else if number == 2 {
|
||||
|
@ -199,7 +211,7 @@ mod control {
|
|||
} $else {
|
||||
"oh noes"
|
||||
}
|
||||
}.to_string();
|
||||
}).unwrap();
|
||||
assert_eq!(s, name);
|
||||
}
|
||||
}
|
||||
|
@ -207,13 +219,14 @@ mod control {
|
|||
#[test]
|
||||
fn if_let() {
|
||||
for &(input, output) in [(Some("yay"), "yay"), (None, "oh noes")].iter() {
|
||||
let s = html! {
|
||||
let mut s = String::new();
|
||||
write_html!(s, {
|
||||
$if let Some(value) = input {
|
||||
$value
|
||||
} $else {
|
||||
"oh noes"
|
||||
}
|
||||
}.to_string();
|
||||
}).unwrap();
|
||||
assert_eq!(s, output);
|
||||
}
|
||||
}
|
||||
|
@ -221,11 +234,12 @@ mod control {
|
|||
#[test]
|
||||
fn for_expr() {
|
||||
let ponies = ["Apple Bloom", "Scootaloo", "Sweetie Belle"];
|
||||
let s = html! {
|
||||
let mut s = String::new();
|
||||
write_html!(s, {
|
||||
ul $for pony in &ponies {
|
||||
li $pony
|
||||
}
|
||||
}.to_string();
|
||||
}).unwrap();
|
||||
assert_eq!(s, concat!(
|
||||
"<ul>",
|
||||
"<li>Apple Bloom</li>",
|
||||
|
|
Loading…
Add table
Reference in a new issue