sailfish/sailfish/src/runtime/filter.rs

386 lines
9.5 KiB
Rust
Raw Normal View History

2020-07-21 17:22:31 -04:00
//! Build-in filters
2020-07-14 18:31:07 -04:00
// TODO: performance improvement
use std::fmt;
2020-07-21 17:22:31 -04:00
use std::ptr;
2020-07-14 18:31:07 -04:00
use super::{Buffer, Render, RenderError};
2020-12-20 07:20:52 -05:00
/// Helper struct for 'display' filter
2020-07-14 18:31:07 -04:00
pub struct Display<'a, T>(&'a T);
impl<'a, T: fmt::Display> Render for Display<'a, T> {
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
use fmt::Write;
write!(b, "{}", self.0).map_err(|e| RenderError::from(e))
}
}
/// render using `std::fmt::Display` trait
2020-12-20 07:13:45 -05:00
///
/// # Examples
///
/// ```text
/// filename: <%= filename.display() | disp %>
/// ```
2020-07-14 18:31:07 -04:00
#[inline]
pub fn disp<T: fmt::Display>(expr: &T) -> Display<T> {
Display(expr)
}
2020-12-20 07:20:52 -05:00
/// Helper struct for 'dbg' filter
2020-07-14 18:31:07 -04:00
pub struct Debug<'a, T>(&'a T);
impl<'a, T: fmt::Debug> Render for Debug<'a, T> {
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
use fmt::Write;
write!(b, "{:?}", self.0).map_err(|e| RenderError::from(e))
}
}
/// render using `std::fmt::Debug` trait
2020-12-20 07:13:45 -05:00
///
/// # Examples
///
/// The following examples produce exactly same results, but former is a bit faster
///
/// ```text
/// table content: <%= table | dbg %>
/// ```
///
/// ```text
/// table content: <%= format!("{:?}", table); %>
/// ```
2020-07-14 18:31:07 -04:00
#[inline]
pub fn dbg<T: fmt::Debug>(expr: &T) -> Debug<T> {
Debug(expr)
}
2020-12-20 07:20:52 -05:00
/// Helper struct for 'upper' filter
2020-07-14 18:31:07 -04:00
pub struct Upper<'a, T>(&'a T);
impl<'a, T: Render> Render for Upper<'a, T> {
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len();
self.0.render(b)?;
let content = b
.as_str()
.get(old_len..)
2020-12-20 07:55:55 -05:00
.ok_or_else(|| RenderError::BufSize)?;
let s = content.to_uppercase();
unsafe { b._set_len(old_len) };
2020-07-14 18:31:07 -04:00
b.push_str(&*s);
Ok(())
}
}
/// convert the rendered contents to uppercase
2020-12-20 07:13:45 -05:00
///
/// # Examples
///
/// ```text
/// <%= "tschüß" | upper %>
/// ```
///
/// result:
///
/// ```text
/// TSCHÜSS
/// ```
2020-07-14 18:31:07 -04:00
#[inline]
pub fn upper<T: Render>(expr: &T) -> Upper<T> {
Upper(expr)
}
2020-12-20 07:20:52 -05:00
/// Helper struct for 'lower' filter
2020-07-14 18:31:07 -04:00
pub struct Lower<'a, T>(&'a T);
impl<'a, T: Render> Render for Lower<'a, T> {
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len();
self.0.render(b)?;
let content = b
.as_str()
.get(old_len..)
2020-12-20 07:55:55 -05:00
.ok_or_else(|| RenderError::BufSize)?;
let s = content.to_lowercase();
unsafe { b._set_len(old_len) };
2020-07-14 18:31:07 -04:00
b.push_str(&*s);
Ok(())
}
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len();
self.0.render_escaped(b)?;
let s = b.as_str()[old_len..].to_lowercase();
unsafe { b._set_len(old_len) };
2020-07-14 18:31:07 -04:00
b.push_str(&*s);
Ok(())
}
}
/// convert the rendered contents to lowercase
2020-12-20 07:13:45 -05:00
///
/// # Examples
///
/// ```text
/// <%= "ὈΔΥΣΣΕΎΣ" | lower %>
/// ```
///
/// result:
///
/// ```text
/// ὀδυσσεύς
/// ```
2020-07-14 18:31:07 -04:00
#[inline]
pub fn lower<T: Render>(expr: &T) -> Lower<T> {
Lower(expr)
}
2020-07-15 06:34:53 -04:00
2020-12-20 07:20:52 -05:00
/// Helper struct for 'trim' filter
2020-07-21 17:22:31 -04:00
pub struct Trim<'a, T>(&'a T);
impl<'a, T: Render> Render for Trim<'a, T> {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len();
self.0.render(b)?;
trim_impl(b, old_len)
2020-07-21 17:22:31 -04:00
}
#[inline]
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len();
self.0.render_escaped(b)?;
trim_impl(b, old_len)
2020-07-21 17:22:31 -04:00
}
}
fn trim_impl(b: &mut Buffer, old_len: usize) -> Result<(), RenderError> {
let new_contents = b
.as_str()
.get(old_len..)
2020-12-20 07:55:55 -05:00
.ok_or_else(|| RenderError::BufSize)?;
2020-07-21 17:22:31 -04:00
let trimmed = new_contents.trim();
let trimmed_len = trimmed.len();
if new_contents.len() != trimmed_len {
// performs inplace trimming
if new_contents.as_ptr() != trimmed.as_ptr() {
debug_assert!(new_contents.as_ptr() < trimmed.as_ptr());
let offset = trimmed.as_ptr() as usize - new_contents.as_ptr() as usize;
unsafe {
ptr::copy(
b.as_mut_ptr().add(old_len + offset),
b.as_mut_ptr().add(old_len),
trimmed_len,
);
}
}
debug_assert!(b.capacity() >= old_len + trimmed_len);
// SAFETY: `new_contents.len() = b.len() - old_len` and
// `trimmed_len < new_contents.len()`, so `old_len + trimmed_len < b.len()`.
2020-07-21 17:22:31 -04:00
unsafe {
b._set_len(old_len + trimmed_len);
}
}
Ok(())
2020-07-21 17:22:31 -04:00
}
/// Remove leading and trailing writespaces from rendered results
2020-12-20 07:13:45 -05:00
///
/// # Examples
///
/// ```text
/// <%= " Hello world\n" | trim %>
/// ```
///
/// result:
///
/// ```text
/// Hello world
/// ```
2020-07-21 17:22:31 -04:00
#[inline]
pub fn trim<T: Render>(expr: &T) -> Trim<T> {
Trim(expr)
}
2020-12-20 07:20:52 -05:00
/// Helper struct for 'truncate' filter
2020-12-17 07:02:26 -05:00
pub struct Truncate<'a, T>(&'a T, usize);
impl<'a, T: Render> Render for Truncate<'a, T> {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len();
self.0.render(b)?;
truncate_impl(b, old_len, self.1)
}
#[inline]
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
let old_len = b.len();
self.0.render_escaped(b)?;
truncate_impl(b, old_len, self.1)
}
}
fn truncate_impl(
b: &mut Buffer,
old_len: usize,
limit: usize,
) -> Result<(), RenderError> {
let new_contents = b
.as_str()
.get(old_len..)
2020-12-20 07:55:55 -05:00
.ok_or_else(|| RenderError::BufSize)?;
2020-12-17 07:02:26 -05:00
if let Some(idx) = new_contents.char_indices().nth(limit).map(|(i, _)| i) {
unsafe { b._set_len(old_len.wrapping_add(idx)) };
2020-12-17 07:02:26 -05:00
b.push_str("...");
}
Ok(())
2020-12-17 07:02:26 -05:00
}
/// Limit length of rendered contents, appends '...' if truncated
2020-12-20 07:13:45 -05:00
///
/// # Examples
///
/// The following example renders the first 20 characters of `message`
///
/// ```test
/// <%= message | truncate(20) %>
/// ```
2020-12-17 07:02:26 -05:00
#[inline]
pub fn truncate<T: Render>(expr: &T, limit: usize) -> Truncate<T> {
2020-12-17 07:02:26 -05:00
Truncate(expr, limit)
}
2020-12-17 04:00:05 -05:00
cfg_json! {
2020-12-20 07:20:52 -05:00
/// Helper struct for 'json' filter
2020-12-17 04:00:05 -05:00
pub struct Json<'a, T>(&'a T);
impl<'a, T: serde::Serialize> Render for Json<'a, T> {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
struct Writer<'a>(&'a mut Buffer);
impl<'a> std::io::Write for Writer<'a> {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let buf = unsafe { std::str::from_utf8_unchecked(buf) };
self.0.push_str(buf);
Ok(buf.len())
}
#[inline]
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
self.write(buf).map(|_| {})
}
#[inline]
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
serde_json::to_writer(Writer(b), self.0)
.map_err(|e| RenderError::new(&e.to_string()))
}
#[inline]
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
use super::escape::escape_to_buf;
struct Writer<'a>(&'a mut Buffer);
impl<'a> std::io::Write for Writer<'a> {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let buf = unsafe { std::str::from_utf8_unchecked(buf) };
escape_to_buf(buf, self.0);
Ok(buf.len())
}
#[inline]
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
self.write(buf).map(|_| {})
}
#[inline]
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
serde_json::to_writer(Writer(b), self.0)
.map_err(|e| RenderError::new(&e.to_string()))
}
}
2020-12-17 04:01:17 -05:00
/// Serialize the given data structure as JSON into the buffer
2020-12-20 07:13:45 -05:00
///
/// # Examples
///
/// ```text
/// {
/// "name": "JSON example",
/// "data": <%- data | json %>
/// }
/// ```
2020-12-17 04:00:05 -05:00
#[inline]
pub fn json<T: serde::Serialize>(expr: &T) -> Json<T> {
Json(expr)
}
}
2020-07-15 06:34:53 -04:00
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case() {
let mut buf = Buffer::new();
upper(&"hElLO, WOrLd!").render(&mut buf).unwrap();
assert_eq!(buf.as_str(), "HELLO, WORLD!");
buf.clear();
lower(&"hElLO, WOrLd!").render(&mut buf).unwrap();
assert_eq!(buf.as_str(), "hello, world!");
buf.clear();
lower(&"<h1>TITLE</h1>").render_escaped(&mut buf).unwrap();
assert_eq!(buf.as_str(), "&lt;h1&gt;title&lt;/h1&gt;");
}
2020-07-21 17:22:31 -04:00
#[test]
fn trim_test() {
let mut buf = Buffer::new();
trim(&" hello ").render(&mut buf).unwrap();
trim(&"hello ").render(&mut buf).unwrap();
trim(&" hello").render(&mut buf).unwrap();
assert_eq!(buf.as_str(), "hellohellohello");
let mut buf = Buffer::new();
trim(&"hello ").render(&mut buf).unwrap();
trim(&" hello").render(&mut buf).unwrap();
trim(&"hello").render(&mut buf).unwrap();
assert_eq!(buf.as_str(), "hellohellohello");
let mut buf = Buffer::new();
trim(&" hello").render(&mut buf).unwrap();
assert_eq!(buf.as_str(), "hello");
}
2020-07-15 06:34:53 -04:00
}