Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions maud/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extern crate alloc;
use alloc::{borrow::Cow, boxed::Box, string::String};
use core::fmt::{self, Arguments, Display, Write};

pub use maud_macros::html;
pub use maud_macros::{html, html_to};

mod escape;

Expand Down Expand Up @@ -376,18 +376,42 @@ pub mod macro_private {
}
}

pub trait AsMutString {
fn as_mut_string(&mut self) -> &mut String;
}

impl<T: Render> ViaRender for &ChooseRenderOrDisplay<T> {}
impl<T: Display> ViaDisplay for ChooseRenderOrDisplay<T> {}

impl AsMutString for &mut String {
fn as_mut_string(&mut self) -> &mut String {
self
}
}

impl AsMutString for String {
fn as_mut_string(&mut self) -> &mut String {
self
}
}

impl ViaRenderTag {
pub fn render_to<T: Render + ?Sized>(self, value: &T, buffer: &mut String) {
value.render_to(buffer);
pub fn render_to<T, B>(self, value: &T, mut buffer: B)
where
T: Render + ?Sized,
B: AsMutString
{
value.render_to(buffer.as_mut_string());
}
}

impl ViaDisplayTag {
pub fn render_to<T: Display + ?Sized>(self, value: &T, buffer: &mut String) {
display(value).render_to(buffer);
pub fn render_to<T, B>(self, value: &T, mut buffer: B)
where
T: Display + ?Sized,
B: AsMutString
{
display(value).render_to(buffer.as_mut_string());
}
}
}
62 changes: 62 additions & 0 deletions maud/tests/html_to.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use maud::{self, html, html_to, Render};

#[test]
fn html_render_to_buffer() {
let mut buf = String::new();

html_to! { buf,
p { "existing" }
};

assert_eq!(buf, "<p>existing</p>");
}

#[test]
fn html_buffer_reuse() {
let mut buf = String::new();
html_to! { buf,
p { "existing" }
};

html_to! { buf,
p { "reused" }
};

assert_eq!(buf, "<p>existing</p><p>reused</p>");
}

#[test]
fn impl_render_to_html_to() {
struct Foo;
impl Render for Foo {
fn render_to(&self, buffer: &mut String) {
html_to! { buffer,
a { "foobar" }
}
}
}

let rendered = html! {
p { (Foo) }
}.into_string();

assert_eq!(rendered, "<p><a>foobar</a></p>");
}

#[test]
fn impl_render_to_html_to_use_render_in_html_to() {
struct Foo;
impl Render for Foo {
fn render_to(&self, buffer: &mut String) {
html_to! { buffer,
a { (42) }
}
}
}

let rendered = html! {
p { (Foo) }
}.into_string();

assert_eq!(rendered, "<p><a>42</a></p>");
}
Expand Down
2 changes: 1 addition & 1 deletion maud_macros/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ impl Generator {

fn splice(&self, expr: TokenStream, build: &mut Builder) {
let output_ident = self.output_ident.clone();
build.push_tokens(quote!(maud::macro_private::render_to!(&#expr, &mut #output_ident);));
build.push_tokens(quote!(maud::macro_private::render_to!(&#expr, #output_ident.as_mut_string());));
}

fn element(&self, name: TokenStream, attrs: Vec<Attr>, body: ElementBody, build: &mut Builder) {
Expand Down
39 changes: 30 additions & 9 deletions maud_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ mod escape;
mod generate;
mod parse;

use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use proc_macro_error::proc_macro_error;
use proc_macro2::{TokenStream, TokenTree};
use proc_macro_error::{proc_macro_error, abort};
use quote::quote;

#[proc_macro]
Expand All @@ -20,18 +20,39 @@ pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
expand(input.into()).into()
}

#[proc_macro]
#[proc_macro_error]
pub fn html_to(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
expand_to(input.into()).into()
}

fn expand(input: TokenStream) -> TokenStream {
let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site()));
quote!({
extern crate alloc;
extern crate maud;
let mut __maud_output = alloc::string::String::new();
maud::html_to!(__maud_output, #input);
maud::PreEscaped(__maud_output)
})
}

fn expand_to(input: TokenStream) -> TokenStream {
// Heuristic: the size of the resulting markup tends to correlate with the
// code size of the template itself
let size_hint = input.to_string().len();
let markups = parse::parse(input);
let stmts = generate::generate(markups, output_ident.clone());
// TODO: Better place for error handling?
let (output_ident, markups) = match parse::parse(input.clone()) {
(Some(ident), markups) => (ident, markups),
_ => abort!(
input,
"expected mutable String buffer"
)
};

let stmts = generate::generate(markups, TokenTree::Ident(output_ident.clone()));
quote!({
extern crate alloc;
extern crate maud;
let mut #output_ident = alloc::string::String::with_capacity(#size_hint);
#output_ident.reserve(#size_hint);
#stmts
maud::PreEscaped(#output_ident)
})
}
}
19 changes: 17 additions & 2 deletions maud_macros/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use syn::Lit;

use crate::ast;

pub fn parse(input: TokenStream) -> Vec<ast::Markup> {
Parser::new(input).markups()
pub fn parse(input: TokenStream) -> (Option<Ident>, Vec<ast::Markup>) {
let mut parser = Parser::new(input);
(parser.buffer_ident(), parser.markups())
}

#[derive(Clone)]
Expand Down Expand Up @@ -82,6 +83,20 @@ impl Parser {
result
}

/// Try to parse an output buffer ident
fn buffer_ident(&mut self) -> Option<Ident> {
match self.peek2() {
Some((
TokenTree::Ident(ident),
Some(TokenTree::Punct(ref punct)),
)) if punct.as_char() == ',' => {
self.advance2();
Some(ident)
},
_ => None,
}
}

/// Parses a single block of markup.
fn markup(&mut self) -> ast::Markup {
let token = match self.peek() {
Expand Down