From ebbd5d2e548a857740137e4c08b3914726dbcfbc Mon Sep 17 00:00:00 2001
From: Chris Wong <lambda.fairy@gmail.com>
Date: Mon, 7 Sep 2015 19:43:48 +1200
Subject: [PATCH] Add a `write_html!` macro

This handles the common case of wrapping the stream in a `Utf8Writer`.
---
 maud_macros/src/lib.rs     | 62 ++++++++++++++++++++++++++++----------
 maud_macros/src/parse.rs   | 11 ++++---
 maud_macros/tests/tests.rs |  9 ++----
 3 files changed, 55 insertions(+), 27 deletions(-)

diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs
index 2f27d9b..09253d2 100644
--- a/maud_macros/src/lib.rs
+++ b/maud_macros/src/lib.rs
@@ -7,35 +7,65 @@ extern crate syntax;
 extern crate rustc;
 extern crate maud;
 
-use syntax::ast::TokenTree;
-use syntax::codemap::Span;
+use syntax::ast::{Expr, TokenTree, TtToken};
+use syntax::codemap::{DUMMY_SP, Span};
 use syntax::ext::base::{ExtCtxt, MacEager, MacResult};
+use syntax::parse::token;
 use syntax::print::pprust;
+use syntax::ptr::P;
 use rustc::plugin::Registry;
 
 mod parse;
 mod render;
 
-fn expand_html_common<'cx>(cx: &'cx mut ExtCtxt, sp: Span, args: &[TokenTree],
-                           debug: bool) -> Box<MacResult + 'cx> {
-    let expr = parse::parse(cx, args, sp);
-    if debug {
-        cx.span_note(sp, &format!("expansion:\n{}",
-                                  pprust::expr_to_string(&expr)));
+fn _expand_html(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> P<Expr> {
+    let (write, input) = parse::split_comma(cx, sp, args);
+    parse::parse(cx, sp, write, input)
+}
+
+fn _expand_write_html(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> P<Expr> {
+    let (io_write, input) = parse::split_comma(cx, sp, args);
+    let io_write = io_write.to_vec();
+    let fmt_write = token::gensym_ident("__maud_utf8_writer");
+    let fmt_write = vec![
+        TtToken(DUMMY_SP, token::Ident(fmt_write, token::IdentStyle::Plain))];
+    let expr = parse::parse(cx, sp, &fmt_write, input);
+    quote_expr!(cx,
+        match ::maud::Utf8Writer::new(&mut $io_write) {
+            mut $fmt_write => {
+                let _ = $expr;
+                $fmt_write.into_result()
+            }
+        })
+}
+
+macro_rules! expand {
+    ($name:ident $debug_name:ident $inner_fn:ident) => {
+        fn $name<'cx>(cx: &'cx mut ExtCtxt, sp: Span, args: &[TokenTree])
+            -> Box<MacResult + 'cx>
+        {
+            let expr = $inner_fn(cx, sp, args);
+            MacEager::expr(expr)
+        }
+
+        fn $debug_name<'cx>(cx: &'cx mut ExtCtxt, sp: Span, args: &[TokenTree])
+            -> Box<MacResult + 'cx>
+        {
+            let expr = $inner_fn(cx, sp, args);
+            cx.span_note(sp, &format!("expansion:\n{}",
+                                      pprust::expr_to_string(&expr)));
+            MacEager::expr(expr)
+        }
     }
-    MacEager::expr(expr)
 }
 
-fn expand_html<'cx>(cx: &'cx mut ExtCtxt, sp: Span, args: &[TokenTree]) -> Box<MacResult + 'cx> {
-    expand_html_common(cx, sp, args, false)
-}
-
-fn expand_html_debug<'cx>(cx: &'cx mut ExtCtxt, sp: Span, args: &[TokenTree]) -> Box<MacResult + 'cx> {
-    expand_html_common(cx, sp, args, true)
-}
+expand!(expand_html expand_html_debug _expand_html);
+expand!(expand_write_html expand_write_html_debug _expand_write_html);
 
 #[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_write_html);
+    reg.register_macro("write_html_debug", expand_write_html_debug);
 }
diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index 7250698..ac5034e 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -42,8 +42,9 @@ macro_rules! ident {
     ($sp:pat, $x:pat) => (TtToken($sp, token::Ident($x, token::IdentStyle::Plain)))
 }
 
-pub fn parse(cx: &ExtCtxt, input: &[TokenTree], sp: Span) -> P<Expr> {
-    let (write, input) = split_comma(cx, input, sp);
+pub fn parse(cx: &ExtCtxt, sp: Span, write: &[TokenTree], input: &[TokenTree])
+    -> P<Expr>
+{
     let mut parser = Parser {
         in_attr: false,
         input: input,
@@ -54,15 +55,15 @@ pub fn parse(cx: &ExtCtxt, input: &[TokenTree], sp: Span) -> P<Expr> {
     parser.into_render().into_expr(write.to_vec())
 }
 
-fn split_comma<'a>(cx: &ExtCtxt, input: &'a [TokenTree], sp: Span) -> (&'a [TokenTree], &'a [TokenTree]) {
+pub fn split_comma<'a>(cx: &ExtCtxt, sp: Span, args: &'a [TokenTree]) -> (&'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..]),
+    match args.iter().position(is_comma) {
+        Some(i) => (&args[..i], &args[1+i..]),
         None => cx.span_fatal(sp, "expected two arguments to `html!`"),
     }
 }
diff --git a/maud_macros/tests/tests.rs b/maud_macros/tests/tests.rs
index a1f0eb1..6568bc8 100644
--- a/maud_macros/tests/tests.rs
+++ b/maud_macros/tests/tests.rs
@@ -250,11 +250,8 @@ mod control {
 }
 
 #[test]
-fn utf8_writer() {
-    use maud::Utf8Writer;
-    let mut w = Utf8Writer::new(vec![]);
-    let _ = html!(w, p "hello");
-    let (buf, r) = w.into_inner();
-    r.unwrap();
+fn write_html() {
+    let mut buf = vec![];
+    write_html!(buf, p "hello").unwrap();
     assert_eq!(buf, b"<p>hello</p>");
 }