//
// Copyright (c) 2021 chiya.dev
//
// Use of this source code is governed by the MIT License
// which can be found in the LICENSE file and at:
//
// https://opensource.org/licenses/MIT
//
use crate::internal::{escape, Buffer};
use alloc::{
borrow::{Cow, ToOwned},
string::String,
};
use core::{
fmt::{Arguments, Write},
num::{
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize,
},
};
/// Formats a value into an HTML representation.
///
/// # Example
///
/// The following example shows how this trait can be implemented. This is a low-level trait, so
/// any data written to the output buffer is [not escaped](escape) by default.
///
/// ```
/// # use laby_common as laby;
/// use laby::Render;
/// use laby::internal::{Buffer, escape};
///
/// struct Hello<'a> {
/// name: &'a str
/// }
///
/// impl Render for Hello<'_> {
/// #[inline]
/// fn render(self, buffer: &mut Buffer) {
/// buffer.push_str("
");
/// buffer.push_str("hello, ");
/// escape(self.name, buffer); // ensure string is escaped
/// buffer.push_str("
");
/// }
/// }
///
/// let x = Hello {
/// name: "laby"
/// };
///
/// let mut buffer = Buffer::new();
/// x.render(&mut buffer);
/// assert_eq!(buffer.into_string(), "hello, laby
");
/// ```
pub trait Render {
/// Formats this value into the given output buffer, consuming itself.
fn render(self, buffer: &mut Buffer);
}
impl Render for () {
#[inline]
fn render(self, _: &mut Buffer) {}
}
impl Render for char {
#[inline]
fn render(self, buffer: &mut Buffer) {
match self {
'"' => buffer.push_str("""),
'&' => buffer.push_str("&"),
'\'' => buffer.push_str("'"),
'<' => buffer.push_str("<"),
'>' => buffer.push_str(">"),
v => buffer.push(v),
}
}
}
impl Render for bool {
#[inline]
fn render(self, buffer: &mut Buffer) {
buffer.push_str(if self { "true" } else { "false" });
}
}
macro_rules! impl_str {
($type:ty) => {
impl Render for $type {
#[inline]
fn render(self, buffer: &mut Buffer) {
escape(self.as_ref(), buffer);
}
}
};
}
impl_str!(&str);
impl_str!(String);
macro_rules! impl_int {
($type:ty) => {
impl Render for $type {
// fast integer rendering function from sailfish, using itoap.
// https://github.com/Kogia-sima/sailfish/blob/6ea0ae2fad1d961b9495b3d50d1d0e1b0b30a219/sailfish/src/runtime/render.rs#L195
#[inline]
fn render(self, buffer: &mut Buffer) {
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 {
buffer.reserve_small(Self::MAX_LEN);
let ptr = buffer.as_mut_ptr().add(buffer.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);
buffer.advance(l);
}
}
}
};
}
impl_int!(u8);
impl_int!(u16);
impl_int!(u32);
impl_int!(u64);
impl_int!(u128);
impl_int!(usize);
impl_int!(i8);
impl_int!(i16);
impl_int!(i32);
impl_int!(i64);
impl_int!(i128);
impl_int!(isize);
macro_rules! impl_nonzero_int {
($type:ty) => {
impl Render for $type {
#[inline]
fn render(self, buffer: &mut Buffer) {
self.get().render(buffer);
}
}
};
}
impl_nonzero_int!(NonZeroU8);
impl_nonzero_int!(NonZeroU16);
impl_nonzero_int!(NonZeroU32);
impl_nonzero_int!(NonZeroU64);
impl_nonzero_int!(NonZeroU128);
impl_nonzero_int!(NonZeroUsize);
impl_nonzero_int!(NonZeroI8);
impl_nonzero_int!(NonZeroI16);
impl_nonzero_int!(NonZeroI32);
impl_nonzero_int!(NonZeroI64);
impl_nonzero_int!(NonZeroI128);
impl_nonzero_int!(NonZeroIsize);
macro_rules! impl_float {
($type:ty, $min:literal, $fn:ident) => {
impl Render for $type {
// fast float rendering function from sailfish, using ryu.
// https://github.com/Kogia-sima/sailfish/blob/6ea0ae2fad1d961b9495b3d50d1d0e1b0b30a219/sailfish/src/runtime/render.rs#L230
#[inline]
fn render(self, buffer: &mut Buffer) {
use ryu::raw::$fn;
if self.is_finite() {
unsafe {
buffer.reserve_small($min);
let ptr = buffer.as_mut_ptr().add(buffer.len());
let l = $fn(self, ptr);
buffer.advance(l);
}
} else if self.is_nan() {
buffer.push_str("NaN");
} else if self > 0.0 {
buffer.push_str("inf");
} else {
buffer.push_str("-inf");
}
}
}
};
}
impl_float!(f32, 16, format32);
impl_float!(f64, 24, format64);
impl Render for Option
where
R: Render,
{
#[inline]
fn render(self, buffer: &mut Buffer) {
if let Some(value) = self {
value.render(buffer);
}
}
}
impl<'a, R> Render for Cow<'a, R>
where
R: ToOwned,
&'a R: Render,
R::Owned: Render,
{
#[inline]
fn render(self, buffer: &mut Buffer) {
match self {
Cow::Owned(value) => value.render(buffer),
Cow::Borrowed(value) => value.render(buffer),
}
}
}
impl<'a> Render for Arguments<'a> {
fn render(self, buffer: &mut Buffer) {
struct EscapingBufferWriter<'a>(&'a mut Buffer);
impl<'a> Write for EscapingBufferWriter<'a> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
Ok(s.render(self.0))
}
fn write_char(&mut self, c: char) -> core::fmt::Result {
Ok(c.render(self.0))
}
}
core::fmt::write(&mut EscapingBufferWriter(buffer), self).unwrap();
}
}