Improve API
This commit is contained in:
parent
d27b415d7a
commit
e5a471d9ca
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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") ; }"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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>"), "<h1>title</h1>");
|
assert_render_escaped(lower("<h1>TITLE</h1>"), "<h1>title</h1>");
|
||||||
assert_render_escaped(&lower("<<&\"\">>"), "<<&"">>");
|
assert_render_escaped(lower("<<&\"\">>"), "<<&"">>");
|
||||||
|
|
||||||
// non-ascii
|
// non-ascii
|
||||||
assert_render(&lower("aBcAbc"), "abcabc");
|
assert_render(lower("aBcAbc"), "abcabc");
|
||||||
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("aBcAbc"), "ABCABC");
|
assert_render(upper("aBcAbc"), "ABCABC");
|
||||||
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> "), "<html>");
|
assert_render_escaped(trim(" <html> "), "<html>");
|
||||||
assert_render_escaped(&lower("<<&\"\">>"), "<<&"">>");
|
assert_render_escaped(lower("<<&\"\">>"), "<<&"">>");
|
||||||
|
|
||||||
// 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<br&...");
|
assert_render_escaped(truncate("foo<br>bar", 10), "foo<br&...");
|
||||||
assert_render_escaped(&truncate("foo<br>bar", 20), "foo<br>bar");
|
assert_render_escaped(truncate("foo<br>bar", 20), "foo<br>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"), ""Pokémon"");
|
assert_render_escaped(json("Pokémon"), ""Pokémon"");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) => {};
|
|
||||||
}
|
|
|
@ -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};
|
||||||
|
|
|
@ -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<& ");
|
assert_eq!(b.as_str(), "c<& ");
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue