Improve API

This commit is contained in:
Michael Pfaff 2024-03-11 16:57:52 -04:00
parent d27b415d7a
commit e5a471d9ca
9 changed files with 374 additions and 273 deletions

View File

@ -1,10 +1,12 @@
use proc_macro2::TokenStream; use proc_macro2::Span;
use quote::quote; use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream, Result as ParseResult}; use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::visit_mut::VisitMut; use syn::visit_mut::VisitMut;
use syn::{ use syn::{
Block, Expr, ExprBreak, ExprContinue, ExprMacro, Ident, LitStr, Macro, Stmt, Block, Expr, ExprBreak, ExprCall, ExprContinue, ExprLit, ExprPath, Ident, Lit,
StmtMacro, Token, LitStr, Stmt, Token,
}; };
pub struct Optimizer { pub struct Optimizer {
@ -102,12 +104,12 @@ impl VisitMut for OptmizerImpl {
fl.body.stmts.remove(0); fl.body.stmts.remove(0);
*fl.body.stmts.last_mut().unwrap() = syn::parse2(quote! { *fl.body.stmts.last_mut().unwrap() = syn::parse2(quote! {
__sf_rt::render_text!(__sf_buf, #concat); __sf_rt::render_text(__sf_buf, #concat);
}) })
.unwrap(); .unwrap();
let mut new_stmts = syn::parse2::<Block>(quote! {{ let mut new_stmts = syn::parse2::<Block>(quote! {{
__sf_rt::render_text!(__sf_buf, #previous); __sf_rt::render_text(__sf_buf, #previous);
#stmt #stmt
unsafe { __sf_buf._set_len(__sf_buf.len() - #sf_len); } unsafe { __sf_buf._set_len(__sf_buf.len() - #sf_len); }
}}) }})
@ -119,38 +121,23 @@ impl VisitMut for OptmizerImpl {
i.stmts = results; i.stmts = results;
} }
fn visit_stmt_macro_mut(&mut self, i: &mut StmtMacro) { fn visit_expr_call_mut(&mut self, i: &mut ExprCall) {
if self.rm_whitespace { if self.rm_whitespace {
if let Some(v) = get_rendertext_value(&i.mac) { if let Some(v) = get_rendertext_value(&i) {
let ts = match remove_whitespace(v) { let ts = match remove_whitespace(v) {
Some(value) => value, Some(value) => value,
None => return, None => return,
}; };
i.mac.tokens = ts; i.args[1] = ts;
return; return;
} }
} }
syn::visit_mut::visit_stmt_macro_mut(self, i); syn::visit_mut::visit_expr_call_mut(self, i);
}
fn visit_expr_macro_mut(&mut self, i: &mut ExprMacro) {
if self.rm_whitespace {
if let Some(v) = get_rendertext_value(&i.mac) {
let ts = match remove_whitespace(v) {
Some(value) => value,
None => return,
};
i.mac.tokens = ts;
return;
}
}
syn::visit_mut::visit_expr_macro_mut(self, i);
} }
} }
fn remove_whitespace(v: String) -> Option<TokenStream> { fn remove_whitespace(v: String) -> Option<Expr> {
let mut buffer = String::new(); let mut buffer = String::new();
let mut it = v.lines().peekable(); let mut it = v.lines().peekable();
if let Some(line) = it.next() { if let Some(line) = it.next() {
@ -175,48 +162,64 @@ fn remove_whitespace(v: String) -> Option<TokenStream> {
} }
} }
Some(quote! { __sf_buf, #buffer }) Some(Expr::Lit(ExprLit {
attrs: vec![],
lit: LitStr::new(&buffer, Span::call_site()).into(),
}))
} }
fn get_rendertext_value(mac: &Macro) -> Option<String> { fn get_rendertext_value(call: &ExprCall) -> Option<String> {
struct RenderTextMacroArgument { struct RenderTextArguments<'a> {
#[allow(dead_code)] #[allow(dead_code)]
context: Ident, buf: &'a Ident,
arg: LitStr, text: &'a LitStr,
} }
impl Parse for RenderTextMacroArgument { impl<'a> RenderTextArguments<'a> {
fn parse(s: ParseStream) -> ParseResult<Self> { pub fn parse(expr: &'a Punctuated<Expr, Comma>) -> Option<Self> {
let context = s.parse()?; if expr.len() != 2 {
s.parse::<Token![,]>()?; panic!("bad arguments: {:?}", expr.to_token_stream());
let arg = s.parse()?; return None;
}
Ok(Self { context, arg }) let Expr::Path(ExprPath { path: buf, .. }) = &expr[0] else {
panic!("bad arguments: {:?}", expr.to_token_stream());
return None;
};
let Some(buf) = buf.get_ident() else {
panic!("bad arguments: {:?}", expr.to_token_stream());
return None;
};
let Expr::Lit(ExprLit {
lit: Lit::Str(text),
..
}) = &expr[1]
else {
panic!("bad arguments: {:?}", expr.to_token_stream());
return None;
};
Some(Self { buf, text })
} }
} }
let mut it = mac.path.segments.iter(); let Expr::Path(ExprPath { path, .. }) = &*call.func else {
return None;
if it.next().map_or(false, |s| s.ident == "__sf_rt") };
&& it.next().map_or(false, |s| s.ident == "render_text") if path.segments.len() != 2
&& it.next().is_none() || path.segments[0].ident != "__sf_rt"
|| path.segments[1].ident != "render_text"
{ {
let tokens = mac.tokens.clone(); return None;
if let Ok(macro_arg) = syn::parse2::<RenderTextMacroArgument>(tokens) {
return Some(macro_arg.arg.value());
}
} }
None let args = RenderTextArguments::parse(&call.args)?;
Some(args.text.value())
} }
fn get_rendertext_value_from_stmt(stmt: &Stmt) -> Option<String> { fn get_rendertext_value_from_stmt(stmt: &Stmt) -> Option<String> {
let em = match stmt { match stmt {
Stmt::Expr(Expr::Macro(ref mac), Some(_)) => mac, Stmt::Expr(Expr::Call(ref ec), Some(_)) => get_rendertext_value(ec),
_ => return None, _ => None,
}; }
get_rendertext_value(&em.mac)
} }
fn block_has_continue_or_break(i: &mut Block) -> bool { fn block_has_continue_or_break(i: &mut Block) -> bool {

View File

@ -368,7 +368,6 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
fn render_once_to(self, __sf_buf: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> { fn render_once_to(self, __sf_buf: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> {
// This line is required for cargo to track child templates // This line is required for cargo to track child templates
#include_bytes_seq; #include_bytes_seq;
use sailfish::runtime as __sf_rt; use sailfish::runtime as __sf_rt;
let #name { #field_names } = self; let #name { #field_names } = self;
include!(#output_file_string); include!(#output_file_string);

View File

@ -123,7 +123,7 @@ impl SourceBuilder {
length: 1, length: 1,
}); });
self.source.push_str("__sf_rt::render_text!(__sf_buf, "); self.source.push_str("__sf_rt::render_text(__sf_buf, ");
// write text token with Debug::fmt // write text token with Debug::fmt
write!(self.source, "{:?}", token.as_str()).unwrap(); write!(self.source, "{:?}", token.as_str()).unwrap();
self.source.push_str(");\n"); self.source.push_str(");\n");
@ -159,7 +159,7 @@ impl SourceBuilder {
self.source.push_str("__sf_rt::"); self.source.push_str("__sf_rt::");
self.source.push_str(method); self.source.push_str(method);
self.source.push_str("!(__sf_buf, "); self.source.push_str("(__sf_buf, ");
if let Some(filter) = code_block.filter { if let Some(filter) = code_block.filter {
let expr_str = format!("{}{}", code_block.expr.into_token_stream(), suffix); let expr_str = format!("{}{}", code_block.expr.into_token_stream(), suffix);
@ -177,7 +177,7 @@ impl SourceBuilder {
// arguments to filter function // arguments to filter function
{ {
self.source.push_str("&("); self.source.push_str("(");
let entry = SourceMapEntry { let entry = SourceMapEntry {
original: token.offset(), original: token.offset(),
new: self.source.len(), new: self.source.len(),
@ -199,7 +199,7 @@ impl SourceBuilder {
self.source.push_str(suffix); self.source.push_str(suffix);
} }
self.source.push_str(");\n"); self.source.push_str(")?;\n");
Ok(()) Ok(())
} }
@ -400,7 +400,7 @@ mod tests {
.ast .ast
.into_token_stream() .into_token_stream()
.to_string(), .to_string(),
r#"{ __sf_rt :: render_text ! (__sf_buf , "outer ") ; __sf_rt :: render ! (__sf_buf , inner . render_once () ?) ; __sf_rt :: render_text ! (__sf_buf , " outer") ; }"# r#"{ __sf_rt :: render_text (__sf_buf , "outer ") ; __sf_rt :: render (__sf_buf , inner . render_once () ?) ? ; __sf_rt :: render_text (__sf_buf , " outer") ; }"#
); );
} }
@ -422,7 +422,7 @@ mod tests {
.ast .ast
.into_token_stream() .into_token_stream()
.to_string(), .to_string(),
r#"{ __sf_rt :: render_text ! (__sf_buf , "outer ") ; __sf_rt :: render ! (__sf_buf , sailfish :: runtime :: filter :: upper (& (inner . render_once () ?))) ; __sf_rt :: render_text ! (__sf_buf , " outer") ; }"# r#"{ __sf_rt :: render_text (__sf_buf , "outer ") ; __sf_rt :: render (__sf_buf , sailfish :: runtime :: filter :: upper (inner)) ? ; __sf_rt :: render_text (__sf_buf , " outer") ; }"#
); );
} }
} }

View File

@ -35,6 +35,7 @@
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
#![allow(clippy::redundant_closure)] #![allow(clippy::redundant_closure)]
#![deny(missing_docs)] #![deny(missing_docs)]
#![feature(specialization)]
pub mod runtime; pub mod runtime;
@ -45,7 +46,7 @@ pub use runtime::{RenderError, RenderResult};
pub use sailfish_macros::TemplateOnce; pub use sailfish_macros::TemplateOnce;
/// Template that can be rendered with consuming itself. /// Template that can be rendered with consuming itself.
pub trait TemplateOnce: Sized + private::Sealed { pub trait TemplateOnce: Sized {
/// Render the template and return the rendering result as `RenderResult` /// Render the template and return the rendering result as `RenderResult`
/// ///
/// This method never returns `Err`, unless you explicitly return RenderError /// This method never returns `Err`, unless you explicitly return RenderError
@ -95,9 +96,28 @@ pub trait TemplateOnce: Sized + private::Sealed {
} }
/// Work in Progress /// Work in Progress
pub trait Template: private::Sealed { pub trait Template
where
for<'a> &'a Self: TemplateOnce,
{
/// Work in progress /// Work in progress
fn render(&self) -> runtime::RenderResult; fn render(&self) -> runtime::RenderResult;
/// Work in progress
fn render_to(&self, buf: &mut Buffer) -> Result<(), RenderError>;
}
impl<T> Template for T
where
for<'a> &'a T: TemplateOnce,
{
fn render(&self) -> runtime::RenderResult {
TemplateOnce::render_once(self)
}
fn render_to(&self, buf: &mut Buffer) -> Result<(), RenderError> {
TemplateOnce::render_once_to(self, buf)
}
} }
#[doc(hidden)] #[doc(hidden)]

View File

@ -0,0 +1,24 @@
use crate::RenderError;
use super::{Buffer, RenderOnce};
#[doc(hidden)]
#[inline(always)]
pub fn render<T: RenderOnce>(buf: &mut Buffer, value: T) -> Result<(), RenderError> {
value.render_once(buf)
}
#[doc(hidden)]
#[inline(always)]
pub fn render_escaped<T: RenderOnce>(
buf: &mut Buffer,
value: T,
) -> Result<(), RenderError> {
value.render_once_escaped(buf)
}
#[doc(hidden)]
#[inline(always)]
pub fn render_text(buf: &mut Buffer, value: &str) {
buf.push_str(value)
}

View File

@ -3,12 +3,15 @@
use std::fmt; use std::fmt;
use std::ptr; use std::ptr;
use super::escape;
use super::render::RenderOnce;
use super::{Buffer, Render, RenderError}; use super::{Buffer, Render, RenderError};
/// Helper struct for 'display' filter /// Helper struct for 'display' filter
pub struct Display<'a, T: ?Sized>(&'a T); #[derive(Clone, Copy)]
pub struct Display<T>(T);
impl<'a, T: fmt::Display + ?Sized> Render for Display<'a, T> { impl<T: fmt::Display> Render for Display<T> {
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
use fmt::Write; use fmt::Write;
@ -24,14 +27,15 @@ impl<'a, T: fmt::Display + ?Sized> Render for Display<'a, T> {
/// filename: <%= filename.display() | disp %> /// filename: <%= filename.display() | disp %>
/// ``` /// ```
#[inline] #[inline]
pub fn disp<T: fmt::Display + ?Sized>(expr: &T) -> Display<T> { pub fn disp<T: fmt::Display>(expr: T) -> Display<T> {
Display(expr) Display(expr)
} }
/// Helper struct for 'dbg' filter /// Helper struct for 'dbg' filter
pub struct Debug<'a, T: ?Sized>(&'a T); #[derive(Clone, Copy)]
pub struct Debug<T>(T);
impl<'a, T: fmt::Debug + ?Sized> Render for Debug<'a, T> { impl<T: fmt::Debug> Render for Debug<T> {
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
use fmt::Write; use fmt::Write;
@ -53,32 +57,38 @@ impl<'a, T: fmt::Debug + ?Sized> Render for Debug<'a, T> {
/// table content: <%= format!("{:?}", table) %> /// table content: <%= format!("{:?}", table) %>
/// ``` /// ```
#[inline] #[inline]
pub fn dbg<T: fmt::Debug + ?Sized>(expr: &T) -> Debug<T> { pub fn dbg<T: fmt::Debug>(expr: T) -> Debug<T> {
Debug(expr) Debug(expr)
} }
/// Helper struct for 'upper' filter /// Helper struct for 'upper' filter
pub struct Upper<'a, T: ?Sized>(&'a T); #[derive(Clone, Copy)]
pub struct Upper<T>(T);
impl<'a, T: Render + ?Sized> Render for Upper<'a, T> { impl<T: RenderOnce> RenderOnce for Upper<T> {
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len(); let mut tmp = Buffer::new();
self.0.render(b)?; self.0.render_once(&mut tmp)?;
// Estimate assuming ASCII and non-convertible UTF-8 are the most common.
let content = b.as_str().get(old_len..).ok_or(RenderError::BufSize)?; b.reserve(tmp.len());
let s = content.to_uppercase(); for c in tmp.as_str().chars().flat_map(|c| c.to_uppercase()) {
unsafe { b._set_len(old_len) }; b.push(c);
b.push_str(&*s); }
Ok(()) Ok(())
} }
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len(); let old_len = b.len();
self.0.render_escaped(b)?; self.0.render_once(b)?;
let mut tmp = Buffer::new();
let s = b.as_str()[old_len..].to_uppercase(); let s = &b.as_str()[old_len..];
// Estimate assuming ASCII and non-convertible UTF-8 are the most common.
tmp.reserve(s.len());
for c in s.chars().flat_map(|c| c.to_uppercase()) {
tmp.push(c);
}
unsafe { b._set_len(old_len) }; unsafe { b._set_len(old_len) };
b.push_str(&*s); escape::escape_to_buf(tmp.as_str(), b);
Ok(()) Ok(())
} }
} }
@ -97,32 +107,57 @@ impl<'a, T: Render + ?Sized> Render for Upper<'a, T> {
/// TSCHÜSS /// TSCHÜSS
/// ``` /// ```
#[inline] #[inline]
pub fn upper<T: Render + ?Sized>(expr: &T) -> Upper<T> { pub fn upper<T: RenderOnce>(expr: T) -> Upper<T> {
Upper(expr) Upper(expr)
} }
/// Helper struct for 'lower' filter /// Helper struct for 'lower' filter
pub struct Lower<'a, T: ?Sized>(&'a T); #[derive(Clone, Copy)]
pub struct Lower<T>(T);
impl<'a, T: Render + ?Sized> Render for Lower<'a, T> { impl<T: RenderOnce> RenderOnce for Lower<T> {
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
let mut tmp = Buffer::new();
self.0.render_once(&mut tmp)?;
// Estimate assuming ASCII and non-convertible UTF-8 are the most common.
b.reserve(tmp.len());
let old_len = b.len(); let old_len = b.len();
self.0.render(b)?; for c in tmp.as_str().chars() {
// see comments in str::to_lowercase
let content = b.as_str().get(old_len..).ok_or(RenderError::BufSize)?; if c == 'Σ' {
let s = content.to_lowercase(); let lower = tmp.as_str().to_lowercase();
unsafe { b._set_len(old_len) }; unsafe { b._set_len(old_len) };
b.push_str(&*s); b.push_str(&lower);
return Ok(());
}
for c in c.to_lowercase() {
b.push(c);
}
}
Ok(()) Ok(())
} }
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len(); let old_len = b.len();
self.0.render_escaped(b)?; self.0.render_once(b)?;
let mut tmp = Buffer::new();
let s = b.as_str()[old_len..].to_lowercase(); let s = &b.as_str()[old_len..];
// Estimate assuming ASCII and non-convertible UTF-8 are the most common.
tmp.reserve(s.len());
for c in s.chars() {
// see comments in str::to_lowercase
if c == 'Σ' {
let lower = s.to_lowercase();
unsafe { b._set_len(old_len) };
b.push_str(&lower);
return Ok(());
}
for c in c.to_lowercase() {
tmp.push(c);
}
}
unsafe { b._set_len(old_len) }; unsafe { b._set_len(old_len) };
b.push_str(&*s); escape::escape_to_buf(tmp.as_str(), b);
Ok(()) Ok(())
} }
} }
@ -141,25 +176,26 @@ impl<'a, T: Render + ?Sized> Render for Lower<'a, T> {
/// ὀδυσσεύς /// ὀδυσσεύς
/// ``` /// ```
#[inline] #[inline]
pub fn lower<T: Render + ?Sized>(expr: &T) -> Lower<T> { pub fn lower<T: RenderOnce>(expr: T) -> Lower<T> {
Lower(expr) Lower(expr)
} }
/// Helper struct for 'trim' filter /// Helper struct for 'trim' filter
pub struct Trim<'a, T: ?Sized>(&'a T); #[derive(Clone, Copy)]
pub struct Trim<T>(T);
impl<'a, T: Render + ?Sized> Render for Trim<'a, T> { impl<T: RenderOnce> RenderOnce for Trim<T> {
#[inline] #[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len(); let old_len = b.len();
self.0.render(b)?; self.0.render_once(b)?;
trim_impl(b, old_len) trim_impl(b, old_len)
} }
#[inline] #[inline]
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len(); let old_len = b.len();
self.0.render_escaped(b)?; self.0.render_once_escaped(b)?;
trim_impl(b, old_len) trim_impl(b, old_len)
} }
} }
@ -211,25 +247,26 @@ fn trim_impl(b: &mut Buffer, old_len: usize) -> Result<(), RenderError> {
/// Hello world /// Hello world
/// ``` /// ```
#[inline] #[inline]
pub fn trim<T: Render + ?Sized>(expr: &T) -> Trim<T> { pub fn trim<T: RenderOnce>(expr: T) -> Trim<T> {
Trim(expr) Trim(expr)
} }
/// Helper struct for 'truncate' filter /// Helper struct for 'truncate' filter
pub struct Truncate<'a, T: ?Sized>(&'a T, usize); #[derive(Clone, Copy)]
pub struct Truncate<T>(T, usize);
impl<'a, T: Render + ?Sized> Render for Truncate<'a, T> { impl<T: RenderOnce> RenderOnce for Truncate<T> {
#[inline] #[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len(); let old_len = b.len();
self.0.render(b)?; self.0.render_once(b)?;
truncate_impl(b, old_len, self.1) truncate_impl(b, old_len, self.1)
} }
#[inline] #[inline]
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len(); let old_len = b.len();
self.0.render_escaped(b)?; self.0.render_once_escaped(b)?;
truncate_impl(b, old_len, self.1) truncate_impl(b, old_len, self.1)
} }
} }
@ -242,7 +279,7 @@ fn truncate_impl(
let new_contents = b.as_str().get(old_len..).ok_or(RenderError::BufSize)?; let new_contents = b.as_str().get(old_len..).ok_or(RenderError::BufSize)?;
if let Some(idx) = new_contents.char_indices().nth(limit).map(|(i, _)| i) { if let Some(idx) = new_contents.char_indices().nth(limit).map(|(i, _)| i) {
unsafe { b._set_len(old_len.wrapping_add(idx)) }; unsafe { b._set_len(old_len + idx) };
b.push_str("..."); b.push_str("...");
} }
@ -265,15 +302,16 @@ fn truncate_impl(
/// Hello... /// Hello...
/// ``` /// ```
#[inline] #[inline]
pub fn truncate<T: Render + ?Sized>(expr: &T, limit: usize) -> Truncate<T> { pub fn truncate<T: RenderOnce>(expr: T, limit: usize) -> Truncate<T> {
Truncate(expr, limit) Truncate(expr, limit)
} }
cfg_json! { cfg_json! {
/// Helper struct for 'json' filter /// Helper struct for 'json' filter
pub struct Json<'a, T: ?Sized>(&'a T); #[derive(Clone, Copy)]
pub struct Json<T>(T);
impl<'a, T: serde::Serialize + ?Sized> Render for Json<'a, T> { impl<T: serde::Serialize> Render for Json<T> {
#[inline] #[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
struct Writer<'a>(&'a mut Buffer); struct Writer<'a>(&'a mut Buffer);
@ -297,7 +335,7 @@ cfg_json! {
} }
} }
serde_json::to_writer(Writer(b), self.0) serde_json::to_writer(Writer(b), &self.0)
.map_err(|e| RenderError::new(&e.to_string())) .map_err(|e| RenderError::new(&e.to_string()))
} }
@ -326,7 +364,7 @@ cfg_json! {
} }
} }
serde_json::to_writer(Writer(b), self.0) serde_json::to_writer(Writer(b), &self.0)
.map_err(|e| RenderError::new(&e.to_string())) .map_err(|e| RenderError::new(&e.to_string()))
} }
} }
@ -342,7 +380,7 @@ cfg_json! {
/// } /// }
/// ``` /// ```
#[inline] #[inline]
pub fn json<T: serde::Serialize + ?Sized>(expr: &T) -> Json<T> { pub fn json<T: serde::Serialize>(expr: T) -> Json<T> {
Json(expr) Json(expr)
} }
} }
@ -351,124 +389,124 @@ cfg_json! {
mod tests { mod tests {
use super::*; use super::*;
fn assert_render<T: Render>(expr: &T, expected: &str) { fn assert_render<T: RenderOnce>(expr: T, expected: &str) {
let mut buf = Buffer::new(); let mut buf = Buffer::new();
Render::render(expr, &mut buf).unwrap(); RenderOnce::render_once(expr, &mut buf).unwrap();
assert_eq!(buf.as_str(), expected); assert_eq!(buf.as_str(), expected);
} }
fn assert_render_escaped<T: Render>(expr: &T, expected: &str) { fn assert_render_escaped<T: RenderOnce>(expr: T, expected: &str) {
let mut buf = Buffer::new(); let mut buf = Buffer::new();
Render::render_escaped(expr, &mut buf).unwrap(); RenderOnce::render_once_escaped(expr, &mut buf).unwrap();
assert_eq!(buf.as_str(), expected); assert_eq!(buf.as_str(), expected);
} }
#[test] #[test]
fn test_lower() { fn test_lower() {
assert_render(&lower(""), ""); assert_render(lower(""), "");
assert_render_escaped(&lower(""), ""); assert_render_escaped(lower(""), "");
assert_render(&lower("lorem ipsum"), "lorem ipsum"); assert_render(lower("lorem ipsum"), "lorem ipsum");
assert_render(&lower("LOREM IPSUM"), "lorem ipsum"); assert_render(lower("LOREM IPSUM"), "lorem ipsum");
assert_render_escaped(&lower("hElLo, WOrLd!"), "hello, world!"); assert_render_escaped(lower("hElLo, WOrLd!"), "hello, world!");
assert_render_escaped(&lower("hElLo, WOrLd!"), "hello, world!"); assert_render_escaped(lower("hElLo, WOrLd!"), "hello, world!");
assert_render_escaped(&lower("<h1>TITLE</h1>"), "&lt;h1&gt;title&lt;/h1&gt;"); assert_render_escaped(lower("<h1>TITLE</h1>"), "&lt;h1&gt;title&lt;/h1&gt;");
assert_render_escaped(&lower("<<&\"\">>"), "&lt;&lt;&amp;&quot;&quot;&gt;&gt;"); assert_render_escaped(lower("<<&\"\">>"), "&lt;&lt;&amp;&quot;&quot;&gt;&gt;");
// non-ascii // non-ascii
assert_render(&lower("aBc"), "abc"); assert_render(lower("aBc"), "abc");
assert_render(&lower("ὈΔΥΣΣΕΎΣ"), "ὀδυσσεύς"); assert_render(lower("ὈΔΥΣΣΕΎΣ"), "ὀδυσσεύς");
} }
#[test] #[test]
fn test_upper() { fn test_upper() {
assert_render(&upper(""), ""); assert_render(upper(""), "");
assert_render_escaped(&upper(""), ""); assert_render_escaped(upper(""), "");
assert_render(&upper("lorem ipsum"), "LOREM IPSUM"); assert_render(upper("lorem ipsum"), "LOREM IPSUM");
assert_render(&upper("LOREM IPSUM"), "LOREM IPSUM"); assert_render(upper("LOREM IPSUM"), "LOREM IPSUM");
assert_render(&upper("hElLo, WOrLd!"), "HELLO, WORLD!"); assert_render(upper("hElLo, WOrLd!"), "HELLO, WORLD!");
assert_render(&upper("hElLo, WOrLd!"), "HELLO, WORLD!"); assert_render(upper("hElLo, WOrLd!"), "HELLO, WORLD!");
// non-ascii // non-ascii
assert_render(&upper("aBc"), "ABC"); assert_render(upper("aBc"), "ABC");
assert_render(&upper("tschüß"), "TSCHÜSS"); assert_render(upper("tschüß"), "TSCHÜSS");
} }
#[test] #[test]
fn test_trim() { fn test_trim() {
assert_render(&trim(""), ""); assert_render(trim(""), "");
assert_render_escaped(&trim(""), ""); assert_render_escaped(trim(""), "");
assert_render(&trim("\n \t\r\x0C"), ""); assert_render(trim("\n \t\r\x0C"), "");
assert_render(&trim("hello world!"), "hello world!"); assert_render(trim("hello world!"), "hello world!");
assert_render(&trim("hello world!\n"), "hello world!"); assert_render(trim("hello world!\n"), "hello world!");
assert_render(&trim("\thello world!"), "hello world!"); assert_render(trim("\thello world!"), "hello world!");
assert_render(&trim("\thello world!\r\n"), "hello world!"); assert_render(trim("\thello world!\r\n"), "hello world!");
assert_render_escaped(&trim(" <html> "), "&lt;html&gt;"); assert_render_escaped(trim(" <html> "), "&lt;html&gt;");
assert_render_escaped(&lower("<<&\"\">>"), "&lt;&lt;&amp;&quot;&quot;&gt;&gt;"); assert_render_escaped(lower("<<&\"\">>"), "&lt;&lt;&amp;&quot;&quot;&gt;&gt;");
// non-ascii whitespace // non-ascii whitespace
assert_render(&trim("\u{A0}空白\u{3000}\u{205F}"), "空白"); assert_render(trim("\u{A0}空白\u{3000}\u{205F}"), "空白");
} }
#[test] #[test]
fn test_truncate() { fn test_truncate() {
assert_render(&truncate("", 0), ""); assert_render(truncate("", 0), "");
assert_render(&truncate("", 5), ""); assert_render(truncate("", 5), "");
assert_render(&truncate("apple ", 0), "..."); assert_render(truncate("apple ", 0), "...");
assert_render(&truncate("apple ", 1), "a..."); assert_render(truncate("apple ", 1), "a...");
assert_render(&truncate("apple ", 2), "ap..."); assert_render(truncate("apple ", 2), "ap...");
assert_render(&truncate("apple ", 3), "app..."); assert_render(truncate("apple ", 3), "app...");
assert_render(&truncate("apple ", 4), "appl..."); assert_render(truncate("apple ", 4), "appl...");
assert_render(&truncate("apple ", 5), "apple..."); assert_render(truncate("apple ", 5), "apple...");
assert_render(&truncate("apple ", 6), "apple "); assert_render(truncate("apple ", 6), "apple ");
assert_render(&truncate("apple ", 7), "apple "); assert_render(truncate("apple ", 7), "apple ");
assert_render(&truncate(&std::f64::consts::PI, 10), "3.14159265..."); assert_render(truncate(std::f64::consts::PI, 10), "3.14159265...");
assert_render(&truncate(&std::f64::consts::PI, 20), "3.141592653589793"); assert_render(truncate(std::f64::consts::PI, 20), "3.141592653589793");
assert_render_escaped(&truncate("foo<br>bar", 10), "foo&lt;br&..."); assert_render_escaped(truncate("foo<br>bar", 10), "foo&lt;br&...");
assert_render_escaped(&truncate("foo<br>bar", 20), "foo&lt;br&gt;bar"); assert_render_escaped(truncate("foo<br>bar", 20), "foo&lt;br&gt;bar");
// non-ascii // non-ascii
assert_render(&truncate("魑魅魍魎", 0), "..."); assert_render(truncate("魑魅魍魎", 0), "...");
assert_render(&truncate("魑魅魍魎", 1), "魑..."); assert_render(truncate("魑魅魍魎", 1), "魑...");
assert_render(&truncate("魑魅魍魎", 2), "魑魅..."); assert_render(truncate("魑魅魍魎", 2), "魑魅...");
assert_render(&truncate("魑魅魍魎", 3), "魑魅魍..."); assert_render(truncate("魑魅魍魎", 3), "魑魅魍...");
assert_render(&truncate("魑魅魍魎", 4), "魑魅魍魎"); assert_render(truncate("魑魅魍魎", 4), "魑魅魍魎");
assert_render(&truncate("魑魅魍魎", 5), "魑魅魍魎"); assert_render(truncate("魑魅魍魎", 5), "魑魅魍魎");
} }
#[cfg(feature = "json")] #[cfg(feature = "json")]
#[test] #[test]
fn test_json() { fn test_json() {
assert_render(&json(""), "\"\""); assert_render(json(""), "\"\"");
assert_render(&json(&serde_json::json!({})), "{}"); assert_render(json(serde_json::json!({})), "{}");
assert_render_escaped(&json(&123_i32), "123"); assert_render_escaped(json(123_i32), "123");
assert_render_escaped(&json("Pokémon"), "&quot;Pokémon&quot;"); assert_render_escaped(json("Pokémon"), "&quot;Pokémon&quot;");
} }
#[test] #[test]
fn compine() { fn compine() {
assert_render( assert_render(
&lower(&upper("Li Europan lingues es membres del sam familie.")), lower(upper("Li Europan lingues es membres del sam familie.")),
"li europan lingues es membres del sam familie.", "li europan lingues es membres del sam familie.",
); );
assert_render(&lower(&lower("ハートのA")), "ハートのa"); assert_render(lower(lower("ハートのA")), "ハートのa");
assert_render(&upper(&upper("ハートのA")), "ハートのA"); assert_render(upper(upper("ハートのA")), "ハートのA");
assert_render(&truncate(&trim("\t起来!\r\n"), 1), "起..."); assert_render(truncate(trim("\t起来!\r\n"), 1), "起...");
assert_render(&truncate(&trim("\t起来!\r\n"), 3), "起来!"); assert_render(truncate(trim("\t起来!\r\n"), 3), "起来!");
assert_render(&truncate(&lower("Was möchtest du?"), 10), "was möchte..."); assert_render(truncate(lower("Was möchtest du?"), 10), "was möchte...");
assert_render(&truncate(&upper("Was möchtest du?"), 10), "WAS MÖCHTE..."); assert_render(truncate(upper("Was möchtest du?"), 10), "WAS MÖCHTE...");
} }
} }

View File

@ -1,29 +0,0 @@
#[macro_export]
#[doc(hidden)]
macro_rules! render {
($buf:ident, $value:expr) => {
$crate::runtime::Render::render(&($value), $buf)?
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! render_escaped {
($buf:ident, $value:expr) => {
$crate::runtime::Render::render_escaped(&($value), $buf)?
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! render_text {
($buf:ident, $value:expr) => {
$buf.push_str($value)
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! render_noop {
($buf:ident, $value:expr) => {};
}

View File

@ -3,16 +3,16 @@
#[macro_use] #[macro_use]
mod utils; mod utils;
mod alias_funcs;
mod buffer; mod buffer;
pub mod escape; pub mod escape;
pub mod filter; pub mod filter;
mod macros;
mod render; mod render;
mod size_hint; mod size_hint;
pub use buffer::Buffer; pub use buffer::Buffer;
pub use render::{Render, RenderError, RenderResult}; pub use render::{Render, RenderError, RenderOnce, RenderResult};
pub use size_hint::SizeHint; pub use size_hint::SizeHint;
#[doc(hidden)] #[doc(hidden)]
pub use crate::{render, render_escaped, render_noop, render_text}; pub use alias_funcs::{render, render_escaped, render_text};

View File

@ -12,7 +12,7 @@ use std::sync::{Arc, MutexGuard, RwLockReadGuard, RwLockWriteGuard};
use super::buffer::Buffer; use super::buffer::Buffer;
use super::escape; use super::escape;
/// types which can be rendered inside buffer block (`<%= %>`) /// types which can be rendered inside buffer block (`<%= %>`) by reference
/// ///
/// If you want to render the custom data, you must implement this trait and specify /// If you want to render the custom data, you must implement this trait and specify
/// the behaviour. /// the behaviour.
@ -52,6 +52,49 @@ pub trait Render {
} }
} }
/// types which can be rendered inside buffer block (`<%= %>`)
///
/// See [`Render`] for more information.
pub trait RenderOnce: Sized {
/// render to `Buffer` without escaping
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError>;
/// render to `Buffer` with HTML escaping
#[inline]
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
let mut tmp = Buffer::new();
self.render_once(&mut tmp)?;
escape::escape_to_buf(tmp.as_str(), b);
Ok(())
}
}
// impl<'a, T: ?Sized> Render for &'a T
// where
// T: Render,
// {
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
// T::render(self, b)
// }
// fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
// T::render_escaped(self, b)
// }
// }
impl<T> RenderOnce for T
where
T: Render,
{
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
self.render(b)
}
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
self.render_escaped(b)
}
}
// /// Autoref-based stable specialization // /// Autoref-based stable specialization
// /// // ///
// /// Explanation can be found [here](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md) // /// Explanation can be found [here](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md)
@ -78,13 +121,13 @@ pub trait Render {
impl Render for String { impl Render for String {
#[inline] #[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
b.push_str(&**self); b.push_str(self);
Ok(()) Ok(())
} }
#[inline] #[inline]
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
escape::escape_to_buf(&**self, b); escape::escape_to_buf(self, b);
Ok(()) Ok(())
} }
} }
@ -128,13 +171,13 @@ impl Render for PathBuf {
#[inline] #[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
// TODO: speed up on Windows using OsStrExt // TODO: speed up on Windows using OsStrExt
b.push_str(&*self.to_string_lossy()); b.push_str(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
Ok(()) Ok(())
} }
#[inline] #[inline]
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
escape::escape_to_buf(&*self.to_string_lossy(), b); escape::escape_to_buf(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?, b);
Ok(()) Ok(())
} }
} }
@ -143,13 +186,13 @@ impl Render for Path {
#[inline] #[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
// TODO: speed up on Windows using OsStrExt // TODO: speed up on Windows using OsStrExt
b.push_str(&*self.to_string_lossy()); b.push_str(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
Ok(()) Ok(())
} }
#[inline] #[inline]
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
escape::escape_to_buf(&*self.to_string_lossy(), b); escape::escape_to_buf(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?, b);
Ok(()) Ok(())
} }
} }
@ -288,10 +331,10 @@ impl Render for f64 {
macro_rules! render_deref { macro_rules! render_deref {
( (
$(#[doc = $doc:tt])* $(#[doc = $doc:tt])*
[$($bounds:tt)+] $($desc:tt)+ $(default[$($default:tt)+])? [$($generics:tt)+] [$($bounds:tt)*] $desc:ty $(, [$($deref:tt)+])?
) => { ) => {
$(#[doc = $doc])* $(#[doc = $doc])*
impl <$($bounds)+> Render for $($desc)+ { $($($default)+)? impl <$($generics)+> Render for $desc where $($bounds)* {
#[inline] #[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
(**self).render(b) (**self).render(b)
@ -305,17 +348,20 @@ macro_rules! render_deref {
}; };
} }
render_deref!(['a, T: Render + ?Sized] &'a T); // render_ref!(['a, T] [&'a T: Render] T);
render_deref!(['a, T: Render + ?Sized] &'a mut T); // render_ref!(['a] [] String);
render_deref!([T: Render + ?Sized] Box<T>);
render_deref!([T: Render + ?Sized] Rc<T>); render_deref!(['a, T: Render + ?Sized] [] &'a T);
render_deref!([T: Render + ?Sized] Arc<T>); render_deref!(['a, T: Render + ?Sized] [] &'a mut T);
render_deref!(['a, T: Render + ToOwned + ?Sized] Cow<'a, T>); render_deref!(default[default] [T: Render + ?Sized] [] Box<T>);
render_deref!(['a, T: Render + ?Sized] Ref<'a, T>); render_deref!([T: Render + ?Sized] [] Rc<T>);
render_deref!(['a, T: Render + ?Sized] RefMut<'a, T>); render_deref!([T: Render + ?Sized] [] Arc<T>);
render_deref!(['a, T: Render + ?Sized] MutexGuard<'a, T>); render_deref!(['a, T: Render + ToOwned + ?Sized] [] Cow<'a, T>);
render_deref!(['a, T: Render + ?Sized] RwLockReadGuard<'a, T>); render_deref!(['a, T: Render + ?Sized] [] Ref<'a, T>, [*]);
render_deref!(['a, T: Render + ?Sized] RwLockWriteGuard<'a, T>); render_deref!(['a, T: Render + ?Sized] [] RefMut<'a, T>, [*]);
render_deref!(['a, T: Render + ?Sized] [] MutexGuard<'a, T>, [*]);
render_deref!(['a, T: Render + ?Sized] [] RwLockReadGuard<'a, T>, [*]);
render_deref!(['a, T: Render + ?Sized] [] RwLockWriteGuard<'a, T>, [*]);
macro_rules! render_nonzero { macro_rules! render_nonzero {
($($type:ty,)*) => { ($($type:ty,)*) => {
@ -422,33 +468,33 @@ mod tests {
#[test] #[test]
fn receiver_coercion() { fn receiver_coercion() {
let mut b = Buffer::new(); let mut b = Buffer::new();
Render::render(&1, &mut b).unwrap(); RenderOnce::render_once(&1, &mut b).unwrap();
Render::render(&&1, &mut b).unwrap(); RenderOnce::render_once(&&1, &mut b).unwrap();
Render::render(&&&1, &mut b).unwrap(); RenderOnce::render_once(&&&1, &mut b).unwrap();
Render::render(&&&&1, &mut b).unwrap(); RenderOnce::render_once(&&&&1, &mut b).unwrap();
assert_eq!(b.as_str(), "1111"); assert_eq!(b.as_str(), "1111");
b.clear(); b.clear();
Render::render(&true, &mut b).unwrap(); RenderOnce::render_once(&true, &mut b).unwrap();
Render::render(&&false, &mut b).unwrap(); RenderOnce::render_once(&&false, &mut b).unwrap();
Render::render_escaped(&&&true, &mut b).unwrap(); RenderOnce::render_once_escaped(&&&true, &mut b).unwrap();
Render::render_escaped(&&&&false, &mut b).unwrap(); RenderOnce::render_once_escaped(&&&&false, &mut b).unwrap();
assert_eq!(b.as_str(), "truefalsetruefalse"); assert_eq!(b.as_str(), "truefalsetruefalse");
b.clear(); b.clear();
let s = "apple"; let s = "apple";
Render::render_escaped(&s, &mut b).unwrap(); RenderOnce::render_once_escaped(&s, &mut b).unwrap();
Render::render_escaped(&s, &mut b).unwrap(); RenderOnce::render_once_escaped(&s, &mut b).unwrap();
Render::render_escaped(&&s, &mut b).unwrap(); RenderOnce::render_once_escaped(&&s, &mut b).unwrap();
Render::render_escaped(&&&s, &mut b).unwrap(); RenderOnce::render_once_escaped(&&&s, &mut b).unwrap();
Render::render_escaped(&&&&s, &mut b).unwrap(); RenderOnce::render_once_escaped(&&&&s, &mut b).unwrap();
assert_eq!(b.as_str(), "appleappleappleappleapple"); assert_eq!(b.as_str(), "appleappleappleappleapple");
b.clear(); b.clear();
Render::render_escaped(&'c', &mut b).unwrap(); RenderOnce::render_once_escaped(&'c', &mut b).unwrap();
Render::render_escaped(&&'<', &mut b).unwrap(); RenderOnce::render_once_escaped(&&'<', &mut b).unwrap();
Render::render_escaped(&&&'&', &mut b).unwrap(); RenderOnce::render_once_escaped(&&&'&', &mut b).unwrap();
Render::render_escaped(&&&&' ', &mut b).unwrap(); RenderOnce::render_once_escaped(&&&&' ', &mut b).unwrap();
assert_eq!(b.as_str(), "c&lt;&amp; "); assert_eq!(b.as_str(), "c&lt;&amp; ");
b.clear(); b.clear();
} }
@ -460,7 +506,7 @@ mod tests {
let mut b = Buffer::new(); let mut b = Buffer::new();
Render::render(&String::from("a"), &mut b).unwrap(); Render::render(&String::from("a"), &mut b).unwrap();
Render::render(&&PathBuf::from("b"), &mut b).unwrap(); Render::render(&PathBuf::from("b"), &mut b).unwrap();
Render::render_escaped(&Rc::new(4u32), &mut b).unwrap(); Render::render_escaped(&Rc::new(4u32), &mut b).unwrap();
Render::render_escaped(&Rc::new(2.3f32), &mut b).unwrap(); Render::render_escaped(&Rc::new(2.3f32), &mut b).unwrap();
Render::render_escaped(Path::new("<"), &mut b).unwrap(); Render::render_escaped(Path::new("<"), &mut b).unwrap();
@ -473,17 +519,17 @@ mod tests {
fn float() { fn float() {
let mut b = Buffer::new(); let mut b = Buffer::new();
Render::render_escaped(&0.0f64, &mut b).unwrap(); RenderOnce::render_once_escaped(0.0f64, &mut b).unwrap();
Render::render_escaped(&std::f64::INFINITY, &mut b).unwrap(); RenderOnce::render_once_escaped(std::f64::INFINITY, &mut b).unwrap();
Render::render_escaped(&std::f64::NEG_INFINITY, &mut b).unwrap(); RenderOnce::render_once_escaped(std::f64::NEG_INFINITY, &mut b).unwrap();
Render::render_escaped(&std::f64::NAN, &mut b).unwrap(); RenderOnce::render_once_escaped(std::f64::NAN, &mut b).unwrap();
assert_eq!(b.as_str(), "0.0inf-infNaN"); assert_eq!(b.as_str(), "0.0inf-infNaN");
b.clear(); b.clear();
Render::render_escaped(&0.0f32, &mut b).unwrap(); RenderOnce::render_once_escaped(0.0f32, &mut b).unwrap();
Render::render_escaped(&std::f32::INFINITY, &mut b).unwrap(); RenderOnce::render_once_escaped(std::f32::INFINITY, &mut b).unwrap();
Render::render_escaped(&std::f32::NEG_INFINITY, &mut b).unwrap(); RenderOnce::render_once_escaped(std::f32::NEG_INFINITY, &mut b).unwrap();
Render::render_escaped(&std::f32::NAN, &mut b).unwrap(); RenderOnce::render_once_escaped(std::f32::NAN, &mut b).unwrap();
assert_eq!(b.as_str(), "0.0inf-infNaN"); assert_eq!(b.as_str(), "0.0inf-infNaN");
} }
@ -491,22 +537,22 @@ mod tests {
fn test_char() { fn test_char() {
let mut b = Buffer::new(); let mut b = Buffer::new();
let funcs: Vec<fn(&char, &mut Buffer) -> Result<(), RenderError>> = let funcs: Vec<fn(char, &mut Buffer) -> Result<(), RenderError>> =
vec![Render::render, Render::render_escaped]; vec![RenderOnce::render_once, RenderOnce::render_once_escaped];
for func in funcs { for func in funcs {
func(&'a', &mut b).unwrap(); func('a', &mut b).unwrap();
func(&'b', &mut b).unwrap(); func('b', &mut b).unwrap();
func(&'c', &mut b).unwrap(); func('c', &mut b).unwrap();
func(&'d', &mut b).unwrap(); func('d', &mut b).unwrap();
assert_eq!(b.as_str(), "abcd"); assert_eq!(b.as_str(), "abcd");
b.clear(); b.clear();
func(&'あ', &mut b).unwrap(); func('あ', &mut b).unwrap();
func(&'い', &mut b).unwrap(); func('い', &mut b).unwrap();
func(&'う', &mut b).unwrap(); func('う', &mut b).unwrap();
func(&'え', &mut b).unwrap(); func('え', &mut b).unwrap();
assert_eq!(b.as_str(), "あいうえ"); assert_eq!(b.as_str(), "あいうえ");
b.clear(); b.clear();
@ -516,8 +562,8 @@ mod tests {
#[test] #[test]
fn test_nonzero() { fn test_nonzero() {
let mut b = Buffer::with_capacity(2); let mut b = Buffer::with_capacity(2);
Render::render(&NonZeroU8::new(10).unwrap(), &mut b).unwrap(); RenderOnce::render_once(NonZeroU8::new(10).unwrap(), &mut b).unwrap();
Render::render_escaped(&NonZeroI16::new(-20).unwrap(), &mut b).unwrap(); RenderOnce::render_once_escaped(NonZeroI16::new(-20).unwrap(), &mut b).unwrap();
assert_eq!(b.as_str(), "10-20"); assert_eq!(b.as_str(), "10-20");
} }