From aded75f01fa84e4d54550ce23e87c927188c53f9 Mon Sep 17 00:00:00 2001
From: Chris Wong <lambda.fairy@gmail.com>
Date: Mon, 15 Aug 2016 20:32:39 +1200
Subject: [PATCH] Add some juicy `impl Trait` niceness

---
 maud/src/lib.rs           |  6 ++++++
 maud_macros/src/lib.rs    | 14 ++++++++++++++
 maud_macros/tests/misc.rs | 20 +++++++++++++++++++-
 3 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index 41bc699..545f2f5 100644
--- a/maud/src/lib.rs
+++ b/maud/src/lib.rs
@@ -151,3 +151,9 @@ impl<W: io::Write> fmt::Write for Utf8Writer<W> {
         }
     }
 }
+
+/// A template is a closure that, when invoked, outputs markup to a
+/// `std::fmt::Write`.
+pub trait Template: FnOnce(&mut fmt::Write) -> fmt::Result {}
+
+impl<T> Template for T where T: FnOnce(&mut fmt::Write) -> fmt::Result {}
diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs
index 4b32e36..abce681 100644
--- a/maud_macros/src/lib.rs
+++ b/maud_macros/src/lib.rs
@@ -44,6 +44,17 @@ fn html_utf8(cx: &mut ExtCtxt, sp: Span, mac_name: &str, args: &[TokenTree]) ->
         }))
 }
 
+fn template(cx: &mut ExtCtxt, sp: Span, _mac_name: &str, args: &[TokenTree]) -> PResult<P<Expr>> {
+    let write_ident = token::gensym_ident("__maud_writer");
+    let write = vec![TokenTree::Token(DUMMY_SP, token::Ident(write_ident))];
+    let star_write = vec![
+        TokenTree::Token(DUMMY_SP, token::BinOp(token::BinOpToken::Star)),
+        TokenTree::Token(DUMMY_SP, token::Ident(write_ident)),
+    ];
+    let expr = parse::parse(cx, sp, &star_write, args)?;
+    Ok(quote_expr!(cx, move |$write: &mut ::std::fmt::Write| $expr))
+}
+
 macro_rules! generate_debug_wrappers {
     ($fn_name:ident $fn_debug_name:ident $mac_name:ident) => {
         fn $fn_name<'cx>(cx: &'cx mut ExtCtxt, sp: Span, args: &[TokenTree])
@@ -72,6 +83,7 @@ macro_rules! generate_debug_wrappers {
 
 generate_debug_wrappers!(expand_html expand_html_debug html);
 generate_debug_wrappers!(expand_html_utf8 expand_html_utf8_debug html_utf8);
+generate_debug_wrappers!(expand_template expand_template_debug template);
 
 #[plugin_registrar]
 pub fn plugin_registrar(reg: &mut Registry) {
@@ -79,4 +91,6 @@ pub fn plugin_registrar(reg: &mut Registry) {
     reg.register_macro("html_debug", expand_html_debug);
     reg.register_macro("html_utf8", expand_html_utf8);
     reg.register_macro("html_utf8_debug", expand_html_utf8_debug);
+    reg.register_macro("template", expand_template);
+    reg.register_macro("template_debug", expand_template_debug);
 }
diff --git a/maud_macros/tests/misc.rs b/maud_macros/tests/misc.rs
index 7d68553..15befee 100644
--- a/maud_macros/tests/misc.rs
+++ b/maud_macros/tests/misc.rs
@@ -1,4 +1,4 @@
-#![feature(plugin)]
+#![feature(conservative_impl_trait, plugin)]
 #![plugin(maud_macros)]
 
 extern crate maud;
@@ -140,3 +140,21 @@ fn render_once_impl() {
     html!(s, ^once).unwrap();
     assert_eq!(s, "pinkie");
 }
+
+fn cute<'a>(name: &'a str) -> impl maud::Template + 'a {
+    template! {
+        p {
+            ^name " is the cutest"
+        }
+    }
+}
+
+#[test]
+fn call() {
+    let mut s = String::new();
+    html!(s, {
+        @call cute("Pinkie Pie")
+        @call cute("Rarity")
+    }).unwrap();
+    assert_eq!(s, "<p>Pinkie Pie is the cutest</p><p>Rarity is the cutest</p>");
+}