699 lines
21 KiB
Rust
699 lines
21 KiB
Rust
use std::borrow::Cow;
|
|
use std::cell::{Ref, RefMut};
|
|
use std::fmt;
|
|
use std::num::{
|
|
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize,
|
|
NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping,
|
|
};
|
|
use std::path::{Path, PathBuf};
|
|
use std::rc::Rc;
|
|
use std::sync::{Arc, MutexGuard, RwLockReadGuard, RwLockWriteGuard};
|
|
|
|
use crate::runtime::SizeHint;
|
|
|
|
use super::buffer::Buffer;
|
|
use super::escape;
|
|
|
|
/// 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
|
|
/// the behaviour.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// This trait allows modifying the previously-rendered contents or even decreasing the
|
|
/// buffer size. However, such an operation easily cause unexpected rendering results.
|
|
/// In order to avoid this, implementors should ensure that the contents which is already
|
|
/// rendered won't be changed during `render` or `render_escaped` method is called.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use sailfish::runtime::{Buffer, Render, RenderError};
|
|
///
|
|
/// struct MyU64(u64);
|
|
///
|
|
/// impl Render for MyU64 {
|
|
/// #[inline]
|
|
/// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
/// self.0.render(b)
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
pub trait Render {
|
|
/// render to `Buffer` without escaping
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError>;
|
|
|
|
/// render to `Buffer` with HTML escaping
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
let mut tmp = Buffer::new();
|
|
self.render(&mut tmp)?;
|
|
escape::escape_to_buf(tmp.as_str(), b);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// types which can be rendered inside buffer block (`<%= %>`)
|
|
///
|
|
/// See [`Render`] for more information.
|
|
pub trait RenderOnce: Sized {
|
|
/// render to `Buffer` without escaping
|
|
///
|
|
/// Render the template and append the result to `buf`.
|
|
///
|
|
/// This method never returns `Err`, unless you explicitly return RenderError
|
|
/// inside templates
|
|
///
|
|
/// ```should_fail
|
|
/// use sailfish::{Buffer, RenderOnce};
|
|
///
|
|
/// # pub struct HelloTemplate {
|
|
/// # messages: Vec<String>,
|
|
/// # }
|
|
/// #
|
|
/// # impl RenderOnce for HelloTemplate {
|
|
/// # fn render_once(self, buf: &mut Buffer) -> Result<(), sailfish::RenderError> {
|
|
/// # unimplemented!()
|
|
/// # }
|
|
/// # }
|
|
/// #
|
|
/// let tpl = HelloTemplate {
|
|
/// messages: vec!["foo".to_string()]
|
|
/// };
|
|
///
|
|
/// // custom pre-allocation
|
|
/// let mut buffer = Buffer::with_capacity(100);
|
|
/// tpl.render_once(&mut buffer).unwrap();
|
|
/// ```
|
|
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(())
|
|
}
|
|
|
|
/// Render the template and return the rendering result as `RenderResult`
|
|
///
|
|
/// This method never returns `Err`, unless you explicitly return RenderError
|
|
/// inside templates
|
|
///
|
|
/// When you use `render_once_to_string` method, total rendered size will be cached,
|
|
/// and at the next time, buffer will be pre-allocated based on the cached length.
|
|
///
|
|
/// If you don't want this behaviour, you can use `render_once` method instead.
|
|
#[inline]
|
|
fn render_once_to_string(self) -> RenderResult {
|
|
static SIZE_HINT: SizeHint = SizeHint::new();
|
|
|
|
let mut buf = Buffer::with_capacity(SIZE_HINT.get());
|
|
self.render_once(&mut buf)?;
|
|
SIZE_HINT.update(buf.len());
|
|
|
|
Ok(buf.into_string())
|
|
}
|
|
}
|
|
|
|
// 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: Render> RenderOnce for T {
|
|
#[inline]
|
|
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
self.render(b)
|
|
}
|
|
|
|
#[inline]
|
|
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
self.render_escaped(b)
|
|
}
|
|
}
|
|
|
|
// /// Autoref-based stable specialization
|
|
// ///
|
|
// /// Explanation can be found [here](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md)
|
|
// impl<T: Display> Render for &T {
|
|
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
// fmt::write(b, format_args!("{}", self))
|
|
// }
|
|
//
|
|
// fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
// struct Wrapper<'a>(&'a mut Buffer);
|
|
//
|
|
// impl<'a> fmt::Write for Wrapper<'a> {
|
|
// #[inline]
|
|
// fn push_str(&mut self, s: &str) -> Result<(), RenderError> {
|
|
// escape::escape_to_buf(s, self.0);
|
|
// Ok(())
|
|
// }
|
|
// }
|
|
//
|
|
// fmt::write(&mut Wrapper(b), format_args!("{}", self))
|
|
// }
|
|
// }
|
|
|
|
impl Render for String {
|
|
#[inline]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
b.push_str(self);
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
escape::escape_to_buf(self, b);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Render for str {
|
|
#[inline]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
b.push_str(self);
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
escape::escape_to_buf(self, b);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Render for char {
|
|
#[inline]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
b.push(*self);
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
match *self {
|
|
'\"' => b.push_str("""),
|
|
'&' => b.push_str("&"),
|
|
'<' => b.push_str("<"),
|
|
'>' => b.push_str(">"),
|
|
'\'' => b.push_str("'"),
|
|
_ => b.push(*self),
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Render for PathBuf {
|
|
#[inline]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
// TODO: speed up on Windows using OsStrExt
|
|
b.push_str(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
escape::escape_to_buf(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?, b);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Render for Path {
|
|
#[inline]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
// TODO: speed up on Windows using OsStrExt
|
|
b.push_str(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
escape::escape_to_buf(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?, b);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// impl Render for [u8] {
|
|
// #[inline]
|
|
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
// b.write_bytes(self);
|
|
// Ok(())
|
|
// }
|
|
// }
|
|
//
|
|
// impl<'a> Render for &'a [u8] {
|
|
// #[inline]
|
|
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
// b.write_bytes(self);
|
|
// Ok(())
|
|
// }
|
|
// }
|
|
//
|
|
// impl Render for Vec<u8> {
|
|
// #[inline]
|
|
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
// b.write_bytes(&**self);
|
|
// Ok(())
|
|
// }
|
|
// }
|
|
|
|
impl Render for bool {
|
|
#[inline]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
let s = if *self { "true" } else { "false" };
|
|
b.push_str(s);
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
self.render(b)
|
|
}
|
|
}
|
|
|
|
macro_rules! render_int {
|
|
($($int:ty),*) => {
|
|
$(
|
|
impl Render for $int {
|
|
#[cfg_attr(feature = "perf-inline", inline)]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
use itoap::Integer;
|
|
|
|
// SAFETY: `MAX_LEN < 40` and then does not overflows `isize::MAX`.
|
|
// Also `b.len()` should be always less than or equal to `isize::MAX`.
|
|
unsafe {
|
|
b.reserve_small(Self::MAX_LEN);
|
|
let ptr = b.as_mut_ptr().add(b.len());
|
|
|
|
// SAFETY: `MAX_LEN` is always greater than zero, so
|
|
// `b.as_mut_ptr()` always point to valid block of memory
|
|
let l = itoap::write_to_ptr(ptr, *self);
|
|
b.advance(l);
|
|
}
|
|
debug_assert!(b.len() <= b.capacity());
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
// push_str without escape
|
|
self.render(b)
|
|
}
|
|
}
|
|
)*
|
|
}
|
|
}
|
|
|
|
render_int!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, usize, isize);
|
|
|
|
impl Render for f32 {
|
|
#[cfg_attr(feature = "perf-inline", inline)]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
if likely!(self.is_finite()) {
|
|
unsafe {
|
|
b.reserve_small(16);
|
|
let ptr = b.as_mut_ptr().add(b.len());
|
|
let l = ryu::raw::format32(*self, ptr);
|
|
b.advance(l);
|
|
debug_assert!(b.len() <= b.capacity());
|
|
}
|
|
} else if self.is_nan() {
|
|
b.push_str("NaN");
|
|
} else if *self > 0.0 {
|
|
b.push_str("inf");
|
|
} else {
|
|
b.push_str("-inf");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
// escape string
|
|
self.render(b)
|
|
}
|
|
}
|
|
|
|
impl Render for f64 {
|
|
#[cfg_attr(feature = "perf-inline", inline)]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
if likely!(self.is_finite()) {
|
|
unsafe {
|
|
b.reserve_small(24);
|
|
let ptr = b.as_mut_ptr().add(b.len());
|
|
let l = ryu::raw::format64(*self, ptr);
|
|
b.advance(l);
|
|
debug_assert!(b.len() <= b.capacity());
|
|
}
|
|
} else if self.is_nan() {
|
|
b.push_str("NaN");
|
|
} else if *self > 0.0 {
|
|
b.push_str("inf");
|
|
} else {
|
|
b.push_str("-inf");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
// escape string
|
|
self.render(b)
|
|
}
|
|
}
|
|
|
|
macro_rules! render_deref {
|
|
(
|
|
$(#[doc = $doc:tt])*
|
|
$(default[$($default:tt)+])? [$($generics:tt)+] [$($bounds:tt)*] $desc:ty $(, [$($deref:tt)+])?
|
|
) => {
|
|
$(#[doc = $doc])*
|
|
$($($default)+)? impl <$($generics)+> Render for $desc where $($bounds)* {
|
|
#[inline]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
(**self).render(b)
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
(**self).render_escaped(b)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// render_ref!(['a, T] [&'a T: Render] T);
|
|
// render_ref!(['a] [] String);
|
|
|
|
render_deref!(['a, T: Render + ?Sized] [] &'a T);
|
|
render_deref!(['a, T: Render + ?Sized] [] &'a mut T);
|
|
render_deref!([T: Render + ?Sized] [] Box<T>);
|
|
render_deref!([T: Render + ?Sized] [] Rc<T>);
|
|
render_deref!([T: Render + ?Sized] [] Arc<T>);
|
|
render_deref!(['a, T: Render + ToOwned + ?Sized] [] Cow<'a, T>);
|
|
render_deref!(['a, T: Render + ?Sized] [] Ref<'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 {
|
|
($($type:ty,)*) => {
|
|
$(
|
|
impl Render for $type {
|
|
#[inline]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
self.get().render(b)
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
self.get().render_escaped(b)
|
|
}
|
|
}
|
|
)*
|
|
}
|
|
}
|
|
|
|
render_nonzero!(
|
|
NonZeroI8,
|
|
NonZeroI16,
|
|
NonZeroI32,
|
|
NonZeroI64,
|
|
NonZeroI128,
|
|
NonZeroIsize,
|
|
NonZeroU8,
|
|
NonZeroU16,
|
|
NonZeroU32,
|
|
NonZeroU64,
|
|
NonZeroU128,
|
|
NonZeroUsize,
|
|
);
|
|
|
|
impl<T: Render> Render for Wrapping<T> {
|
|
#[inline]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
self.0.render(b)
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
self.0.render_escaped(b)
|
|
}
|
|
}
|
|
|
|
macro_rules! render_tuple {
|
|
($A:ident) => {
|
|
render_tuple!(@ $A);
|
|
};
|
|
($A:ident $(, $T:ident)+) => {
|
|
render_tuple!(@ $A $(, $T)+);
|
|
render_tuple!($($T),+);
|
|
};
|
|
(@ $($T:ident),*) => {
|
|
impl<$($T: Render),+> Render for ($($T,)+) {
|
|
#[inline]
|
|
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
#[allow(non_snake_case)]
|
|
let ($($T,)+) = self;
|
|
$($T.render(b)?;)+
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
#[allow(non_snake_case)]
|
|
let ($($T,)+) = self;
|
|
$($T.render_escaped(b)?;)+
|
|
Ok(())
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! render_once_tuple {
|
|
($A:ident) => {
|
|
render_once_tuple!(@ $A);
|
|
};
|
|
($A:ident $(, $T:ident)+) => {
|
|
render_once_tuple!(@ $A $(, $T)+);
|
|
render_once_tuple!($($T),+);
|
|
};
|
|
(@ $($T:ident),*) => {
|
|
impl<$($T: RenderOnce),+> RenderOnce for ($($T,)+) {
|
|
#[inline]
|
|
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
#[allow(non_snake_case)]
|
|
let ($($T,)+) = self;
|
|
$($T.render_once(b)?;)+
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
|
#[allow(non_snake_case)]
|
|
let ($($T,)+) = self;
|
|
$($T.render_once_escaped(b)?;)+
|
|
Ok(())
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// TODO: figure out how to use specialization to allow a Render impl on tuples, if possible. If
|
|
// not, oh well, it can be worked around.
|
|
// render_tuple!(A, B, C, D, E, F, G, H);
|
|
render_once_tuple!(A, B, C, D, E, F, G, H);
|
|
|
|
/// The error type which is returned from template function
|
|
#[derive(Clone, Debug)]
|
|
pub enum RenderError {
|
|
/// Custom error message
|
|
Msg(String),
|
|
/// fmt::Error was raised during rendering
|
|
Fmt(fmt::Error),
|
|
/// Buffer size shrinked during rendering
|
|
///
|
|
/// This method won't be raised unless you implement `Render` trait for custom type.
|
|
///
|
|
/// Also there is no guarentee that this error will be returned whenever the buffer
|
|
/// size shrinked.
|
|
BufSize,
|
|
}
|
|
|
|
impl RenderError {
|
|
/// Construct a new error with custom message
|
|
pub fn new(msg: &str) -> Self {
|
|
RenderError::Msg(msg.to_owned())
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for RenderError {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
RenderError::Msg(ref s) => f.pad(&**s),
|
|
RenderError::Fmt(ref e) => fmt::Display::fmt(e, f),
|
|
RenderError::BufSize => f.pad("buffer size shrinked while rendering"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for RenderError {
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
match self {
|
|
RenderError::Msg(_) | RenderError::BufSize => None,
|
|
RenderError::Fmt(ref e) => Some(e),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<fmt::Error> for RenderError {
|
|
#[inline]
|
|
fn from(other: fmt::Error) -> Self {
|
|
RenderError::Fmt(other)
|
|
}
|
|
}
|
|
|
|
/// Result type returned from [`RenderOnce::render_once_to_string`] method
|
|
pub type RenderResult = Result<String, RenderError>;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::error::Error;
|
|
|
|
#[test]
|
|
fn receiver_coercion() {
|
|
let mut b = Buffer::new();
|
|
RenderOnce::render_once(&1, &mut b).unwrap();
|
|
RenderOnce::render_once(&&1, &mut b).unwrap();
|
|
RenderOnce::render_once(&&&1, &mut b).unwrap();
|
|
RenderOnce::render_once(&&&&1, &mut b).unwrap();
|
|
assert_eq!(b.as_str(), "1111");
|
|
b.clear();
|
|
|
|
RenderOnce::render_once(&true, &mut b).unwrap();
|
|
RenderOnce::render_once(&&false, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(&&&true, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(&&&&false, &mut b).unwrap();
|
|
assert_eq!(b.as_str(), "truefalsetruefalse");
|
|
b.clear();
|
|
|
|
let s = "apple";
|
|
RenderOnce::render_once_escaped(&s, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(&s, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(&&s, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(&&&s, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(&&&&s, &mut b).unwrap();
|
|
assert_eq!(b.as_str(), "appleappleappleappleapple");
|
|
b.clear();
|
|
|
|
RenderOnce::render_once_escaped(&'c', &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(&&'<', &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(&&&'&', &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(&&&&' ', &mut b).unwrap();
|
|
assert_eq!(b.as_str(), "c<& ");
|
|
b.clear();
|
|
}
|
|
|
|
#[test]
|
|
fn deref_coercion() {
|
|
use std::path::{Path, PathBuf};
|
|
use std::rc::Rc;
|
|
|
|
let mut b = Buffer::new();
|
|
Render::render(&String::from("a"), &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(2.3f32), &mut b).unwrap();
|
|
Render::render_escaped(Path::new("<"), &mut b).unwrap();
|
|
Render::render_escaped(&Path::new("d"), &mut b).unwrap();
|
|
|
|
assert_eq!(b.as_str(), "ab42.3<d");
|
|
}
|
|
|
|
#[test]
|
|
fn float() {
|
|
let mut b = Buffer::new();
|
|
|
|
RenderOnce::render_once_escaped(0.0f64, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(std::f64::INFINITY, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(std::f64::NEG_INFINITY, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(std::f64::NAN, &mut b).unwrap();
|
|
assert_eq!(b.as_str(), "0.0inf-infNaN");
|
|
b.clear();
|
|
|
|
RenderOnce::render_once_escaped(0.0f32, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(std::f32::INFINITY, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(std::f32::NEG_INFINITY, &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(std::f32::NAN, &mut b).unwrap();
|
|
assert_eq!(b.as_str(), "0.0inf-infNaN");
|
|
}
|
|
|
|
#[test]
|
|
fn test_char() {
|
|
let mut b = Buffer::new();
|
|
|
|
let funcs: Vec<fn(char, &mut Buffer) -> Result<(), RenderError>> =
|
|
vec![RenderOnce::render_once, RenderOnce::render_once_escaped];
|
|
|
|
for func in funcs {
|
|
func('a', &mut b).unwrap();
|
|
func('b', &mut b).unwrap();
|
|
func('c', &mut b).unwrap();
|
|
func('d', &mut b).unwrap();
|
|
|
|
assert_eq!(b.as_str(), "abcd");
|
|
b.clear();
|
|
|
|
func('あ', &mut b).unwrap();
|
|
func('い', &mut b).unwrap();
|
|
func('う', &mut b).unwrap();
|
|
func('え', &mut b).unwrap();
|
|
|
|
assert_eq!(b.as_str(), "あいうえ");
|
|
b.clear();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_nonzero() {
|
|
let mut b = Buffer::with_capacity(2);
|
|
RenderOnce::render_once(NonZeroU8::new(10).unwrap(), &mut b).unwrap();
|
|
RenderOnce::render_once_escaped(NonZeroI16::new(-20).unwrap(), &mut b).unwrap();
|
|
assert_eq!(b.as_str(), "10-20");
|
|
}
|
|
|
|
#[test]
|
|
fn render_error() {
|
|
let err = RenderError::new("custom error");
|
|
assert!(err.source().is_none());
|
|
assert_eq!(format!("{}", err), "custom error");
|
|
|
|
let err = RenderError::from(std::fmt::Error::default());
|
|
assert!(err.source().is_some());
|
|
assert_eq!(
|
|
format!("{}", err),
|
|
format!("{}", std::fmt::Error::default())
|
|
);
|
|
|
|
let err = RenderError::BufSize;
|
|
assert!(err.source().is_none());
|
|
format!("{}", err);
|
|
}
|
|
}
|