sailfish/sailfish/src/filter.rs

539 lines
15 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
use std::fmt;
2020-07-21 17:22:31 -04:00
use std::ptr;
2020-07-14 18:31:07 -04:00
use crate::{Buffer, Escape, Render, RenderError, RenderOnce};
2020-07-14 18:31:07 -04:00
2020-12-20 07:20:52 -05:00
/// Helper struct for 'display' filter
2024-03-11 16:57:52 -04:00
#[derive(Clone, Copy)]
pub struct Display<T>(T);
2020-07-14 18:31:07 -04:00
2024-03-11 16:57:52 -04:00
impl<T: fmt::Display> Render for Display<T> {
2020-07-14 18:31:07 -04:00
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
use fmt::Write;
write!(b, "{}", self.0).map_err(RenderError::from)
2020-07-14 18:31:07 -04:00
}
}
/// render using `std::fmt::Display` trait
2020-12-20 07:13:45 -05:00
///
/// # Examples
///
/// ```text
/// filename: <%\html filename.display() | disp %>
2020-12-20 07:13:45 -05:00
/// ```
2020-07-14 18:31:07 -04:00
#[inline]
2024-03-11 16:57:52 -04:00
pub fn disp<T: fmt::Display>(expr: T) -> Display<T> {
2020-07-14 18:31:07 -04:00
Display(expr)
}
2020-12-20 07:20:52 -05:00
/// Helper struct for 'dbg' filter
2024-03-11 16:57:52 -04:00
#[derive(Clone, Copy)]
pub struct Debug<T>(T);
2020-07-14 18:31:07 -04:00
2024-03-11 16:57:52 -04:00
impl<T: fmt::Debug> Render for Debug<T> {
2020-07-14 18:31:07 -04:00
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
use fmt::Write;
write!(b, "{:?}", self.0).map_err(RenderError::from)
2020-07-14 18:31:07 -04:00
}
}
/// 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 faster
2020-12-20 07:13:45 -05:00
///
/// ```text
/// table content: <%\html table | dbg %>
2020-12-20 07:13:45 -05:00
/// ```
///
/// ```text
/// table content: <%\html format!("{:?}", table) %>
2020-12-20 07:13:45 -05:00
/// ```
2020-07-14 18:31:07 -04:00
#[inline]
2024-03-11 16:57:52 -04:00
pub fn dbg<T: fmt::Debug>(expr: T) -> Debug<T> {
2020-07-14 18:31:07 -04:00
Debug(expr)
}
2020-12-20 07:20:52 -05:00
/// Helper struct for 'upper' filter
2024-03-11 16:57:52 -04:00
#[derive(Clone, Copy)]
pub struct Upper<T>(T);
impl<T: RenderOnce> RenderOnce for Upper<T> {
#[inline]
2024-03-11 16:57:52 -04:00
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());
for c in tmp.as_str().chars().flat_map(|c| c.to_uppercase()) {
b.push(c);
}
2020-07-14 18:31:07 -04:00
Ok(())
}
#[inline]
fn render_once_escaped<E: Escape>(
self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
let old_len = b.len();
2024-03-11 16:57:52 -04:00
self.0.render_once(b)?;
let mut tmp = Buffer::new();
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) };
e.escape_to_buf(b, tmp.as_str());
Ok(())
}
2020-07-14 18:31:07 -04:00
}
/// convert the rendered contents to uppercase
2020-12-20 07:13:45 -05:00
///
/// # Examples
///
/// ```text
/// <%\html "tschüß" | upper %>
2020-12-20 07:13:45 -05:00
/// ```
///
/// result:
///
/// ```text
/// TSCHÜSS
/// ```
2020-07-14 18:31:07 -04:00
#[inline]
2024-03-11 16:57:52 -04:00
pub fn upper<T: RenderOnce>(expr: T) -> Upper<T> {
2020-07-14 18:31:07 -04:00
Upper(expr)
}
2020-12-20 07:20:52 -05:00
/// Helper struct for 'lower' filter
2024-03-11 16:57:52 -04:00
#[derive(Clone, Copy)]
pub struct Lower<T>(T);
impl<T: RenderOnce> RenderOnce for Lower<T> {
#[inline]
2024-03-11 16:57:52 -04:00
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());
2020-07-14 18:31:07 -04:00
let old_len = b.len();
2024-03-11 16:57:52 -04:00
for c in tmp.as_str().chars() {
// See comments in str::to_lowercase
2024-03-11 16:57:52 -04:00
if c == 'Σ' {
// This is very inefficient, but it seems unlikely to be worth the effort
// duplicating what already exists in the standard library for unicode support.
2024-03-11 16:57:52 -04:00
let lower = tmp.as_str().to_lowercase();
unsafe { b._set_len(old_len) };
b.push_str(&lower);
return Ok(());
}
for c in c.to_lowercase() {
b.push(c);
}
}
2020-07-14 18:31:07 -04:00
Ok(())
}
#[inline]
fn render_once_escaped<E: Escape>(
self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
2020-07-14 18:31:07 -04:00
let old_len = b.len();
2024-03-11 16:57:52 -04:00
self.0.render_once(b)?;
let mut tmp = Buffer::new();
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) };
e.escape_to_buf(b, &lower);
2024-03-11 16:57:52 -04:00
return Ok(());
}
for c in c.to_lowercase() {
tmp.push(c);
}
}
unsafe { b._set_len(old_len) };
e.escape_to_buf(b, tmp.as_str());
2020-07-14 18:31:07 -04:00
Ok(())
}
}
/// convert the rendered contents to lowercase
2020-12-20 07:13:45 -05:00
///
/// # Examples
///
/// ```text
/// <%\html "ὈΔΥΣΣΕΎΣ" | lower %>
2020-12-20 07:13:45 -05:00
/// ```
///
/// result:
///
/// ```text
/// ὀδυσσεύς
/// ```
2020-07-14 18:31:07 -04:00
#[inline]
2024-03-11 16:57:52 -04:00
pub fn lower<T: RenderOnce>(expr: T) -> Lower<T> {
2020-07-14 18:31:07 -04:00
Lower(expr)
}
2020-07-15 06:34:53 -04:00
2020-12-20 07:20:52 -05:00
/// Helper struct for 'trim' filter
2024-03-11 16:57:52 -04:00
#[derive(Clone, Copy)]
pub struct Trim<T>(T);
2020-07-21 17:22:31 -04:00
2024-03-11 16:57:52 -04:00
impl<T: RenderOnce> RenderOnce for Trim<T> {
2020-07-21 17:22:31 -04:00
#[inline]
2024-03-11 16:57:52 -04:00
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
2020-07-21 17:22:31 -04:00
let old_len = b.len();
2024-03-11 16:57:52 -04:00
self.0.render_once(b)?;
trim_impl(b, old_len)
2020-07-21 17:22:31 -04:00
}
#[inline]
fn render_once_escaped<E: Escape>(
self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
2020-07-21 17:22:31 -04:00
let old_len = b.len();
self.0.render_once_escaped(b, e)?;
trim_impl(b, old_len)
2020-07-21 17:22:31 -04:00
}
}
fn trim_impl(b: &mut Buffer, old_len: usize) -> Result<(), RenderError> {
2020-12-28 15:19:12 -05:00
let new_contents = b.as_str().get(old_len..).ok_or(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
/// <%\html " Hello world\n" | trim %>
2020-12-20 07:13:45 -05:00
/// ```
///
/// result:
///
/// ```text
/// Hello world
/// ```
2020-07-21 17:22:31 -04:00
#[inline]
2024-03-11 16:57:52 -04:00
pub fn trim<T: RenderOnce>(expr: T) -> Trim<T> {
2020-07-21 17:22:31 -04:00
Trim(expr)
}
2020-12-20 07:20:52 -05:00
/// Helper struct for 'truncate' filter
2024-03-11 16:57:52 -04:00
#[derive(Clone, Copy)]
pub struct Truncate<T>(T, usize);
2020-12-17 07:02:26 -05:00
2024-03-11 16:57:52 -04:00
impl<T: RenderOnce> RenderOnce for Truncate<T> {
2020-12-17 07:02:26 -05:00
#[inline]
2024-03-11 16:57:52 -04:00
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
2020-12-17 07:02:26 -05:00
let old_len = b.len();
2024-03-11 16:57:52 -04:00
self.0.render_once(b)?;
2020-12-17 07:02:26 -05:00
truncate_impl(b, old_len, self.1)
}
#[inline]
fn render_once_escaped<E: Escape>(
self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
2020-12-17 07:02:26 -05:00
let old_len = b.len();
self.0.render_once_escaped(b, e)?;
2020-12-17 07:02:26 -05:00
truncate_impl(b, old_len, self.1)
}
}
#[cfg_attr(feature = "perf-inline", inline)]
2020-12-17 07:02:26 -05:00
fn truncate_impl(
b: &mut Buffer,
old_len: usize,
limit: usize,
) -> Result<(), RenderError> {
2020-12-28 15:19:12 -05:00
let new_contents = b.as_str().get(old_len..).ok_or(RenderError::BufSize)?;
if new_contents.len() > limit {
if let Some(idx) = new_contents.char_indices().nth(limit).map(|(i, _)| i) {
unsafe { b._set_len(old_len + idx) };
b.push_str("...");
}
2020-12-17 07:02:26 -05:00
}
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
/// <%\html "Hello, world!" | truncate(5) %>
2020-12-28 07:28:53 -05:00
/// ```
///
/// result:
///
/// ```text
/// Hello...
2020-12-20 07:13:45 -05:00
/// ```
2020-12-17 07:02:26 -05:00
#[inline]
2024-03-11 16:57:52 -04:00
pub fn truncate<T: RenderOnce>(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
2024-03-11 16:57:52 -04:00
#[derive(Clone, Copy)]
pub struct Json<T>(T);
2020-12-17 04:00:05 -05:00
2024-03-11 16:57:52 -04:00
impl<T: serde::Serialize> Render for Json<T> {
2020-12-17 04:00:05 -05:00
#[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(())
}
}
2024-03-11 16:57:52 -04:00
serde_json::to_writer(Writer(b), &self.0)
2020-12-17 04:00:05 -05:00
.map_err(|e| RenderError::new(&e.to_string()))
}
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
struct Writer<'a, E: Escape>(&'a mut Buffer, &'a E);
impl<'a, E: Escape> std::io::Write for Writer<'a, E> {
2020-12-17 04:00:05 -05:00
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let buf = unsafe { std::str::from_utf8_unchecked(buf) };
self.1.escape_to_buf(self.0, buf);
2020-12-17 04:00:05 -05:00
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, e), &self.0)
2020-12-17 04:00:05 -05:00
.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]
2024-03-11 16:57:52 -04:00
pub fn json<T: serde::Serialize>(expr: T) -> Json<T> {
2020-12-17 04:00:05 -05:00
Json(expr)
}
}
2020-07-15 06:34:53 -04:00
#[cfg(test)]
mod tests {
use crate::EscapeHtml;
2020-07-15 06:34:53 -04:00
use super::*;
2024-03-11 16:57:52 -04:00
fn assert_render<T: RenderOnce>(expr: T, expected: &str) {
2020-07-15 06:34:53 -04:00
let mut buf = Buffer::new();
2024-03-11 16:57:52 -04:00
RenderOnce::render_once(expr, &mut buf).unwrap();
2020-12-28 08:44:36 -05:00
assert_eq!(buf.as_str(), expected);
}
2020-07-15 06:34:53 -04:00
2024-03-11 16:57:52 -04:00
fn assert_render_escaped<T: RenderOnce>(expr: T, expected: &str) {
2020-12-28 08:44:36 -05:00
let mut buf = Buffer::new();
RenderOnce::render_once_escaped(expr, &mut buf, &EscapeHtml).unwrap();
2020-12-28 08:44:36 -05:00
assert_eq!(buf.as_str(), expected);
}
2020-07-15 06:34:53 -04:00
2020-12-28 08:44:36 -05:00
#[test]
2020-12-28 11:32:56 -05:00
fn test_lower() {
2024-03-11 16:57:52 -04:00
assert_render(lower(""), "");
assert_render_escaped(lower(""), "");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render(lower("lorem ipsum"), "lorem ipsum");
assert_render(lower("LOREM IPSUM"), "lorem ipsum");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render_escaped(lower("hElLo, WOrLd!"), "hello, world!");
assert_render_escaped(lower("hElLo, WOrLd!"), "hello, world!");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
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;");
2020-12-28 11:32:56 -05:00
// non-ascii
2024-03-11 16:57:52 -04:00
assert_render(lower("aBc"), "abc");
assert_render(lower("ὈΔΥΣΣΕΎΣ"), "ὀδυσσεύς");
2020-12-28 11:32:56 -05:00
}
#[test]
fn test_upper() {
2024-03-11 16:57:52 -04:00
assert_render(upper(""), "");
assert_render_escaped(upper(""), "");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render(upper("lorem ipsum"), "LOREM IPSUM");
assert_render(upper("LOREM IPSUM"), "LOREM IPSUM");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render(upper("hElLo, WOrLd!"), "HELLO, WORLD!");
assert_render(upper("hElLo, WOrLd!"), "HELLO, WORLD!");
2020-12-28 11:32:56 -05:00
// non-ascii
2024-03-11 16:57:52 -04:00
assert_render(upper("aBc"), "ABC");
assert_render(upper("tschüß"), "TSCHÜSS");
2020-07-15 06:34:53 -04:00
}
2020-07-21 17:22:31 -04:00
#[test]
2020-12-28 11:32:56 -05:00
fn test_trim() {
2024-03-11 16:57:52 -04:00
assert_render(trim(""), "");
assert_render_escaped(trim(""), "");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render(trim("\n \t\r\x0C"), "");
2020-07-21 17:22:31 -04:00
2024-03-11 16:57:52 -04:00
assert_render(trim("hello world!"), "hello world!");
assert_render(trim("hello world!\n"), "hello world!");
assert_render(trim("\thello world!"), "hello world!");
assert_render(trim("\thello world!\r\n"), "hello world!");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render_escaped(trim(" <html> "), "&lt;html&gt;");
assert_render_escaped(lower("<<&\"\">>"), "&lt;&lt;&amp;&quot;&quot;&gt;&gt;");
2020-12-28 11:32:56 -05:00
// non-ascii whitespace
2024-03-11 16:57:52 -04:00
assert_render(trim("\u{A0}空白\u{3000}\u{205F}"), "空白");
2020-12-28 11:32:56 -05:00
}
#[test]
fn test_truncate() {
2024-03-11 16:57:52 -04:00
assert_render(truncate("", 0), "");
assert_render(truncate("", 5), "");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render(truncate("apple ", 0), "...");
assert_render(truncate("apple ", 1), "a...");
assert_render(truncate("apple ", 2), "ap...");
assert_render(truncate("apple ", 3), "app...");
assert_render(truncate("apple ", 4), "appl...");
assert_render(truncate("apple ", 5), "apple...");
assert_render(truncate("apple ", 6), "apple ");
assert_render(truncate("apple ", 7), "apple ");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render(truncate(std::f64::consts::PI, 10), "3.14159265...");
assert_render(truncate(std::f64::consts::PI, 20), "3.141592653589793");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render_escaped(truncate("foo<br>bar", 10), "foo&lt;br&...");
assert_render_escaped(truncate("foo<br>bar", 20), "foo&lt;br&gt;bar");
2020-12-28 11:32:56 -05:00
// non-ascii
2024-03-11 16:57:52 -04:00
assert_render(truncate("魑魅魍魎", 0), "...");
assert_render(truncate("魑魅魍魎", 1), "魑...");
assert_render(truncate("魑魅魍魎", 2), "魑魅...");
assert_render(truncate("魑魅魍魎", 3), "魑魅魍...");
assert_render(truncate("魑魅魍魎", 4), "魑魅魍魎");
assert_render(truncate("魑魅魍魎", 5), "魑魅魍魎");
2020-12-28 11:32:56 -05:00
}
#[cfg(feature = "json")]
#[test]
fn test_json() {
2024-03-11 16:57:52 -04:00
assert_render(json(""), "\"\"");
assert_render(json(serde_json::json!({})), "{}");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render_escaped(json(123_i32), "123");
assert_render_escaped(json("Pokémon"), "&quot;Pokémon&quot;");
2020-12-28 11:32:56 -05:00
}
#[test]
fn compine() {
assert_render(
2024-03-11 16:57:52 -04:00
lower(upper("Li Europan lingues es membres del sam familie.")),
2020-12-28 11:32:56 -05:00
"li europan lingues es membres del sam familie.",
);
2024-03-11 16:57:52 -04:00
assert_render(lower(lower("ハートのA")), "ハートのa");
assert_render(upper(upper("ハートのA")), "ハートのA");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render(truncate(trim("\t起来!\r\n"), 1), "起...");
assert_render(truncate(trim("\t起来!\r\n"), 3), "起来!");
2020-12-28 11:32:56 -05:00
2024-03-11 16:57:52 -04:00
assert_render(truncate(lower("Was möchtest du?"), 10), "was möchte...");
assert_render(truncate(upper("Was möchtest du?"), 10), "WAS MÖCHTE...");
2020-07-21 17:22:31 -04:00
}
2020-07-15 06:34:53 -04:00
}