From d8b52cf82e1886f8e6df432ad66a3ee8be6e0b8f Mon Sep 17 00:00:00 2001
From: nikvoid <77514082+nikvoid@users.noreply.github.com>
Date: Wed, 12 Apr 2023 17:09:08 +0300
Subject: [PATCH 1/2] PoC writing to existing buffer with html_to! macro
---
maud/src/lib.rs | 2 +-
maud/tests/html_to.rs | 44 ++++++++++++++++++++++++++++++++++++++++++
maud_macros/src/lib.rs | 43 ++++++++++++++++++++++++++++++++++-------
3 files changed, 81 insertions(+), 8 deletions(-)
create mode 100644 maud/tests/html_to.rs
diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index 9c4a1809..6a606436 100644
--- a/maud/src/lib.rs
+++ b/maud/src/lib.rs
@@ -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;
diff --git a/maud/tests/html_to.rs b/maud/tests/html_to.rs
new file mode 100644
index 00000000..5cc1f53d
--- /dev/null
+++ b/maud/tests/html_to.rs
@@ -0,0 +1,44 @@
+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, "
existing
");
+}
+
+#[test]
+fn html_buffer_reuse() {
+ let mut buf = String::new();
+ html_to! { buf
+ p { "existing" }
+ };
+
+ html_to! { buf
+ p { "reused" }
+ };
+
+ assert_eq!(buf, "existing
reused
");
+}
+
+#[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, "foobar
");
+}
\ No newline at end of file
diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs
index b1ccf2df..b746c822 100644
--- a/maud_macros/src/lib.rs
+++ b/maud_macros/src/lib.rs
@@ -11,7 +11,7 @@ mod generate;
mod parse;
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
-use proc_macro_error::proc_macro_error;
+use proc_macro_error::{proc_macro_error, abort};
use quote::quote;
#[proc_macro]
@@ -20,18 +20,47 @@ 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 {
+ // TODO: Better/more beatiful way to get ident?
+ let mut iter = input.clone().into_iter();
+
+ // TODO: Better place for error handling?
+ let output_ident = match iter.next() {
+ Some(ident @ TokenTree::Ident(_)) => ident,
+ Some(token) => abort!(
+ token,
+ "expected mutable String buffer",
+ ),
+ None => abort!(
+ input,
+ "expected mutable String buffer"
+ ),
+ };
+
// 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 markups = parse::parse(iter.collect());
let stmts = generate::generate(markups, 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)
})
-}
+}
\ No newline at end of file
From 0a3f7a258437844873cc0992c2a6cd55e76903f4 Mon Sep 17 00:00:00 2001
From: nikvoid <77514082+nikvoid@users.noreply.github.com>
Date: Wed, 12 Apr 2023 19:10:45 +0300
Subject: [PATCH 2/2] separate buffer ident from remaining tokens with comma
(,), add trait to avoid taking &mut String references (fix Splicing exprs
into html_to!)
---
maud/src/lib.rs | 32 ++++++++++++++++++++++++++++----
maud/tests/html_to.rs | 26 ++++++++++++++++++++++----
maud_macros/src/generate.rs | 2 +-
maud_macros/src/lib.rs | 30 +++++++++++-------------------
maud_macros/src/parse.rs | 19 +++++++++++++++++--
5 files changed, 79 insertions(+), 30 deletions(-)
diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index 6a606436..a4d1d31a 100644
--- a/maud/src/lib.rs
+++ b/maud/src/lib.rs
@@ -376,18 +376,42 @@ pub mod macro_private {
}
}
+ pub trait AsMutString {
+ fn as_mut_string(&mut self) -> &mut String;
+ }
+
impl ViaRender for &ChooseRenderOrDisplay {}
impl ViaDisplay for ChooseRenderOrDisplay {}
+ 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(self, value: &T, buffer: &mut String) {
- value.render_to(buffer);
+ pub fn render_to(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(self, value: &T, buffer: &mut String) {
- display(value).render_to(buffer);
+ pub fn render_to(self, value: &T, mut buffer: B)
+ where
+ T: Display + ?Sized,
+ B: AsMutString
+ {
+ display(value).render_to(buffer.as_mut_string());
}
}
}
diff --git a/maud/tests/html_to.rs b/maud/tests/html_to.rs
index 5cc1f53d..f274bcf8 100644
--- a/maud/tests/html_to.rs
+++ b/maud/tests/html_to.rs
@@ -4,7 +4,7 @@ use maud::{self, html, html_to, Render};
fn html_render_to_buffer() {
let mut buf = String::new();
- html_to! { buf
+ html_to! { buf,
p { "existing" }
};
@@ -14,11 +14,11 @@ fn html_render_to_buffer() {
#[test]
fn html_buffer_reuse() {
let mut buf = String::new();
- html_to! { buf
+ html_to! { buf,
p { "existing" }
};
- html_to! { buf
+ html_to! { buf,
p { "reused" }
};
@@ -30,7 +30,7 @@ fn impl_render_to_html_to() {
struct Foo;
impl Render for Foo {
fn render_to(&self, buffer: &mut String) {
- html_to! { buffer
+ html_to! { buffer,
a { "foobar" }
}
}
@@ -41,4 +41,22 @@ fn impl_render_to_html_to() {
}.into_string();
assert_eq!(rendered, "foobar
");
+}
+
+#[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, "42
");
}
\ No newline at end of file
diff --git a/maud_macros/src/generate.rs b/maud_macros/src/generate.rs
index c9ba9fb6..386f27fc 100644
--- a/maud_macros/src/generate.rs
+++ b/maud_macros/src/generate.rs
@@ -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, body: ElementBody, build: &mut Builder) {
diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs
index b746c822..615f58bb 100644
--- a/maud_macros/src/lib.rs
+++ b/maud_macros/src/lib.rs
@@ -10,7 +10,7 @@ mod escape;
mod generate;
mod parse;
-use proc_macro2::{Ident, Span, TokenStream, TokenTree};
+use proc_macro2::{TokenStream, TokenTree};
use proc_macro_error::{proc_macro_error, abort};
use quote::quote;
@@ -31,33 +31,25 @@ fn expand(input: TokenStream) -> TokenStream {
extern crate alloc;
extern crate maud;
let mut __maud_output = alloc::string::String::new();
- maud::html_to!(__maud_output #input);
+ maud::html_to!(__maud_output, #input);
maud::PreEscaped(__maud_output)
})
}
fn expand_to(input: TokenStream) -> TokenStream {
- // TODO: Better/more beatiful way to get ident?
- let mut iter = input.clone().into_iter();
-
- // TODO: Better place for error handling?
- let output_ident = match iter.next() {
- Some(ident @ TokenTree::Ident(_)) => ident,
- Some(token) => abort!(
- token,
- "expected mutable String buffer",
- ),
- None => abort!(
+ // 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();
+ // 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"
- ),
+ )
};
- // 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(iter.collect());
- let stmts = generate::generate(markups, output_ident.clone());
+ let stmts = generate::generate(markups, TokenTree::Ident(output_ident.clone()));
quote!({
extern crate maud;
#output_ident.reserve(#size_hint);
diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index dee037e4..94460b19 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -6,8 +6,9 @@ use syn::Lit;
use crate::ast;
-pub fn parse(input: TokenStream) -> Vec {
- Parser::new(input).markups()
+pub fn parse(input: TokenStream) -> (Option, Vec) {
+ let mut parser = Parser::new(input);
+ (parser.buffer_ident(), parser.markups())
}
#[derive(Clone)]
@@ -82,6 +83,20 @@ impl Parser {
result
}
+ /// Try to parse an output buffer ident
+ fn buffer_ident(&mut self) -> Option {
+ 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() {