mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-27 04:50:40 +00:00
Merge pull request #397 from Kijewski/pr-fast-writable-values
Pass variables to sub-templates more reliably even if indirectly
This commit is contained in:
commit
2c1e86e410
@ -1,8 +1,11 @@
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use core::cell::Cell;
|
||||
use core::convert::Infallible;
|
||||
use core::fmt::{self, Write};
|
||||
|
||||
use super::MAX_LEN;
|
||||
use super::escape::HtmlSafeOutput;
|
||||
use super::{FastWritable, MAX_LEN};
|
||||
use crate::Result;
|
||||
|
||||
/// Return an ephemeral `&str` for `$src: impl fmt::Display`
|
||||
@ -118,14 +121,36 @@ pub fn format() {}
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn linebreaks(s: impl fmt::Display) -> Result<HtmlSafeOutput<String>, fmt::Error> {
|
||||
fn linebreaks(s: &str) -> String {
|
||||
let linebroken = s.replace("\n\n", "</p><p>").replace('\n', "<br/>");
|
||||
alloc::format!("<p>{linebroken}</p>")
|
||||
}
|
||||
pub fn linebreaks<S: fmt::Display>(source: S) -> Result<HtmlSafeOutput<Linebreaks<S>>, Infallible> {
|
||||
Ok(HtmlSafeOutput(Linebreaks(source)))
|
||||
}
|
||||
|
||||
let mut buffer;
|
||||
Ok(HtmlSafeOutput(linebreaks(try_to_str!(s => buffer))))
|
||||
pub struct Linebreaks<S>(S);
|
||||
|
||||
impl<S: fmt::Display> fmt::Display for Linebreaks<S> {
|
||||
#[inline]
|
||||
fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut buffer;
|
||||
flush_linebreaks(dest, try_to_str!(self.0 => buffer))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: FastWritable> FastWritable for Linebreaks<S> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn crate::Values,
|
||||
) -> crate::Result<()> {
|
||||
let mut buffer = String::new();
|
||||
self.0.write_into(&mut buffer, values)?;
|
||||
Ok(flush_linebreaks(dest, &buffer)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_linebreaks(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
|
||||
let linebroken = s.replace("\n\n", "</p><p>").replace('\n', "<br/>");
|
||||
write!(dest, "<p>{linebroken}</p>")
|
||||
}
|
||||
|
||||
/// Converts all newlines in a piece of plain text to HTML line breaks
|
||||
@ -149,13 +174,37 @@ pub fn linebreaks(s: impl fmt::Display) -> Result<HtmlSafeOutput<String>, fmt::E
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn linebreaksbr(s: impl fmt::Display) -> Result<HtmlSafeOutput<String>, fmt::Error> {
|
||||
fn linebreaksbr(s: &str) -> String {
|
||||
s.replace('\n', "<br/>")
|
||||
}
|
||||
pub fn linebreaksbr<S: fmt::Display>(
|
||||
source: S,
|
||||
) -> Result<HtmlSafeOutput<Linebreaksbr<S>>, Infallible> {
|
||||
Ok(HtmlSafeOutput(Linebreaksbr(source)))
|
||||
}
|
||||
|
||||
let mut buffer;
|
||||
Ok(HtmlSafeOutput(linebreaksbr(try_to_str!(s => buffer))))
|
||||
pub struct Linebreaksbr<S>(S);
|
||||
|
||||
impl<S: fmt::Display> fmt::Display for Linebreaksbr<S> {
|
||||
#[inline]
|
||||
fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut buffer;
|
||||
flush_linebreaksbr(dest, try_to_str!(self.0 => buffer))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: FastWritable> FastWritable for Linebreaksbr<S> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn crate::Values,
|
||||
) -> crate::Result<()> {
|
||||
let mut buffer = String::new();
|
||||
self.0.write_into(&mut buffer, values)?;
|
||||
Ok(flush_linebreaksbr(dest, &buffer)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_linebreaksbr(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
|
||||
dest.write_str(&s.replace('\n', "<br/>"))
|
||||
}
|
||||
|
||||
/// Replaces only paragraph breaks in plain text with appropriate HTML
|
||||
@ -183,14 +232,38 @@ pub fn linebreaksbr(s: impl fmt::Display) -> Result<HtmlSafeOutput<String>, fmt:
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn paragraphbreaks(s: impl fmt::Display) -> Result<HtmlSafeOutput<String>, fmt::Error> {
|
||||
fn paragraphbreaks(s: &str) -> String {
|
||||
let linebroken = s.replace("\n\n", "</p><p>").replace("<p></p>", "");
|
||||
alloc::format!("<p>{linebroken}</p>")
|
||||
}
|
||||
pub fn paragraphbreaks<S: fmt::Display>(
|
||||
source: S,
|
||||
) -> Result<HtmlSafeOutput<Paragraphbreaks<S>>, Infallible> {
|
||||
Ok(HtmlSafeOutput(Paragraphbreaks(source)))
|
||||
}
|
||||
|
||||
let mut buffer;
|
||||
Ok(HtmlSafeOutput(paragraphbreaks(try_to_str!(s => buffer))))
|
||||
pub struct Paragraphbreaks<S>(S);
|
||||
|
||||
impl<S: fmt::Display> fmt::Display for Paragraphbreaks<S> {
|
||||
#[inline]
|
||||
fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut buffer;
|
||||
flush_paragraphbreaks(dest, try_to_str!(self.0 => buffer))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: FastWritable> FastWritable for Paragraphbreaks<S> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn crate::Values,
|
||||
) -> crate::Result<()> {
|
||||
let mut buffer = String::new();
|
||||
self.0.write_into(&mut buffer, values)?;
|
||||
Ok(flush_paragraphbreaks(dest, &buffer)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_paragraphbreaks(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
|
||||
let linebroken = s.replace("\n\n", "</p><p>").replace("<p></p>", "");
|
||||
write!(dest, "<p>{linebroken}</p>")
|
||||
}
|
||||
|
||||
/// Converts to lowercase
|
||||
@ -219,9 +292,35 @@ pub fn paragraphbreaks(s: impl fmt::Display) -> Result<HtmlSafeOutput<String>, f
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn lower(s: impl fmt::Display) -> Result<String, fmt::Error> {
|
||||
let mut buffer;
|
||||
Ok(try_to_str!(s => buffer).to_lowercase())
|
||||
pub fn lower<S: fmt::Display>(source: S) -> Result<Lower<S>, Infallible> {
|
||||
Ok(Lower(source))
|
||||
}
|
||||
|
||||
pub struct Lower<S>(S);
|
||||
|
||||
impl<S: fmt::Display> fmt::Display for Lower<S> {
|
||||
#[inline]
|
||||
fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut buffer;
|
||||
flush_lower(dest, try_to_str!(self.0 => buffer))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: FastWritable> FastWritable for Lower<S> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn crate::Values,
|
||||
) -> crate::Result<()> {
|
||||
let mut buffer = String::new();
|
||||
self.0.write_into(&mut buffer, values)?;
|
||||
Ok(flush_lower(dest, &buffer)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_lower(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
|
||||
dest.write_str(&s.to_lowercase())
|
||||
}
|
||||
|
||||
/// Converts to lowercase, alias for the `|lower` filter
|
||||
@ -250,8 +349,8 @@ pub fn lower(s: impl fmt::Display) -> Result<String, fmt::Error> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn lowercase(s: impl fmt::Display) -> Result<String, fmt::Error> {
|
||||
lower(s)
|
||||
pub fn lowercase<S: fmt::Display>(source: S) -> Result<Lower<S>, Infallible> {
|
||||
lower(source)
|
||||
}
|
||||
|
||||
/// Converts to uppercase
|
||||
@ -280,9 +379,35 @@ pub fn lowercase(s: impl fmt::Display) -> Result<String, fmt::Error> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn upper(s: impl fmt::Display) -> Result<String, fmt::Error> {
|
||||
let mut buffer;
|
||||
Ok(try_to_str!(s => buffer).to_uppercase())
|
||||
pub fn upper<S: fmt::Display>(source: S) -> Result<Upper<S>, Infallible> {
|
||||
Ok(Upper(source))
|
||||
}
|
||||
|
||||
pub struct Upper<S>(S);
|
||||
|
||||
impl<S: fmt::Display> fmt::Display for Upper<S> {
|
||||
#[inline]
|
||||
fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut buffer;
|
||||
flush_upper(dest, try_to_str!(self.0 => buffer))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: FastWritable> FastWritable for Upper<S> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn crate::Values,
|
||||
) -> crate::Result<()> {
|
||||
let mut buffer = String::new();
|
||||
self.0.write_into(&mut buffer, values)?;
|
||||
Ok(flush_upper(dest, &buffer)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_upper(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
|
||||
dest.write_str(&s.to_uppercase())
|
||||
}
|
||||
|
||||
/// Converts to uppercase, alias for the `|upper` filter
|
||||
@ -311,8 +436,8 @@ pub fn upper(s: impl fmt::Display) -> Result<String, fmt::Error> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn uppercase(s: impl fmt::Display) -> Result<String, fmt::Error> {
|
||||
upper(s)
|
||||
pub fn uppercase<S: fmt::Display>(source: S) -> Result<Upper<S>, Infallible> {
|
||||
upper(source)
|
||||
}
|
||||
|
||||
/// Strip leading and trailing whitespace
|
||||
@ -335,24 +460,48 @@ pub fn uppercase(s: impl fmt::Display) -> Result<String, fmt::Error> {
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn trim<T: fmt::Display>(s: T) -> Result<String> {
|
||||
struct Collector(String);
|
||||
#[inline]
|
||||
pub fn trim<S: fmt::Display>(source: S) -> Result<Trim<S>, Infallible> {
|
||||
Ok(Trim(source))
|
||||
}
|
||||
|
||||
impl fmt::Write for Collector {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
match self.0.is_empty() {
|
||||
true => self.0.write_str(s.trim_start()),
|
||||
false => self.0.write_str(s),
|
||||
}
|
||||
pub struct Trim<S>(S);
|
||||
|
||||
impl<S: fmt::Display> fmt::Display for Trim<S> {
|
||||
#[inline]
|
||||
fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut collector = TrimCollector(String::new());
|
||||
write!(collector, "{}", self.0)?;
|
||||
flush_trim(dest, collector)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: FastWritable> FastWritable for Trim<S> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn crate::Values,
|
||||
) -> crate::Result<()> {
|
||||
let mut collector = TrimCollector(String::new());
|
||||
self.0.write_into(&mut collector, values)?;
|
||||
Ok(flush_trim(dest, collector)?)
|
||||
}
|
||||
}
|
||||
|
||||
struct TrimCollector(String);
|
||||
|
||||
impl fmt::Write for TrimCollector {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
match self.0.is_empty() {
|
||||
true => self.0.write_str(s.trim_start()),
|
||||
false => self.0.write_str(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut collector = Collector(String::new());
|
||||
write!(collector, "{s}")?;
|
||||
let Collector(mut s) = collector;
|
||||
s.truncate(s.trim_end().len());
|
||||
Ok(s)
|
||||
fn flush_trim(dest: &mut (impl fmt::Write + ?Sized), collector: TrimCollector) -> fmt::Result {
|
||||
dest.write_str(collector.0.trim_end())
|
||||
}
|
||||
|
||||
/// Indent lines with `width` spaces
|
||||
@ -376,39 +525,61 @@ pub fn trim<T: fmt::Display>(s: T) -> Result<String> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn indent(s: impl fmt::Display, width: usize) -> Result<String, fmt::Error> {
|
||||
fn indent(args: fmt::Arguments<'_>, width: usize) -> Result<String, fmt::Error> {
|
||||
let mut buffer = String::new();
|
||||
let s = if width >= MAX_LEN {
|
||||
buffer.write_fmt(args)?;
|
||||
return Ok(buffer);
|
||||
} else if let Some(s) = args.as_str() {
|
||||
if s.len() >= MAX_LEN {
|
||||
return Ok(s.into());
|
||||
} else {
|
||||
s
|
||||
}
|
||||
pub fn indent<S: fmt::Display>(source: S, width: usize) -> Result<Indent<S>, Infallible> {
|
||||
Ok(Indent { source, width })
|
||||
}
|
||||
|
||||
pub struct Indent<S> {
|
||||
source: S,
|
||||
width: usize,
|
||||
}
|
||||
|
||||
impl<S: fmt::Display> fmt::Display for Indent<S> {
|
||||
fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self { ref source, width } = *self;
|
||||
if width >= MAX_LEN || width == 0 {
|
||||
write!(dest, "{source}")
|
||||
} else {
|
||||
buffer.write_fmt(args)?;
|
||||
if buffer.len() >= MAX_LEN {
|
||||
return Ok(buffer);
|
||||
}
|
||||
buffer.as_str()
|
||||
};
|
||||
|
||||
let mut indented = String::new();
|
||||
for (i, c) in s.char_indices() {
|
||||
indented.push(c);
|
||||
|
||||
if c == '\n' && i < s.len() - 1 {
|
||||
for _ in 0..width {
|
||||
indented.push(' ');
|
||||
}
|
||||
}
|
||||
let mut buffer;
|
||||
flush_indent(dest, width, try_to_str!(source => buffer))
|
||||
}
|
||||
Ok(indented)
|
||||
}
|
||||
indent(format_args!("{s}"), width)
|
||||
}
|
||||
|
||||
impl<S: FastWritable> FastWritable for Indent<S> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn crate::Values,
|
||||
) -> crate::Result<()> {
|
||||
let Self { ref source, width } = *self;
|
||||
if width >= MAX_LEN || width == 0 {
|
||||
source.write_into(dest, values)
|
||||
} else {
|
||||
let mut buffer = String::new();
|
||||
source.write_into(&mut buffer, values)?;
|
||||
Ok(flush_indent(dest, width, &buffer)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_indent(dest: &mut (impl fmt::Write + ?Sized), width: usize, s: &str) -> fmt::Result {
|
||||
if s.len() >= MAX_LEN {
|
||||
return dest.write_str(s);
|
||||
}
|
||||
|
||||
let mut prefix = String::new();
|
||||
for (idx, line) in s.split_inclusive('\n').enumerate() {
|
||||
if idx > 0 {
|
||||
// only allocate prefix if needed, i.e. not for single-line outputs
|
||||
if prefix.is_empty() {
|
||||
prefix = format!("{: >1$}", "", width);
|
||||
}
|
||||
dest.write_str(&prefix)?;
|
||||
}
|
||||
dest.write_str(line)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Capitalize a value. The first character will be uppercase, all others lowercase.
|
||||
@ -437,21 +608,45 @@ pub fn indent(s: impl fmt::Display, width: usize) -> Result<String, fmt::Error>
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn capitalize(s: impl fmt::Display) -> Result<String, fmt::Error> {
|
||||
fn capitalize(s: &str) -> Result<String, fmt::Error> {
|
||||
let mut chars = s.chars();
|
||||
if let Some(c) = chars.next() {
|
||||
let mut replacement = String::with_capacity(s.len());
|
||||
replacement.extend(c.to_uppercase());
|
||||
replacement.push_str(&chars.as_str().to_lowercase());
|
||||
Ok(replacement)
|
||||
} else {
|
||||
Ok(String::new())
|
||||
}
|
||||
}
|
||||
pub fn capitalize<S: fmt::Display>(source: S) -> Result<Capitalize<S>, Infallible> {
|
||||
Ok(Capitalize(source))
|
||||
}
|
||||
|
||||
let mut buffer;
|
||||
capitalize(try_to_str!(s => buffer))
|
||||
pub struct Capitalize<S>(S);
|
||||
|
||||
impl<S: fmt::Display> fmt::Display for Capitalize<S> {
|
||||
#[inline]
|
||||
fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut buffer;
|
||||
flush_capitalize(dest, try_to_str!(self.0 => buffer))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: FastWritable> FastWritable for Capitalize<S> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn crate::Values,
|
||||
) -> crate::Result<()> {
|
||||
let mut buffer = String::new();
|
||||
self.0.write_into(&mut buffer, values)?;
|
||||
Ok(flush_capitalize(dest, &buffer)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_capitalize(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
|
||||
let mut chars = s.chars();
|
||||
if let Some(c) = chars.next() {
|
||||
write!(
|
||||
dest,
|
||||
"{}{}",
|
||||
c.to_uppercase(),
|
||||
chars.as_str().to_lowercase()
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Count the words in that string.
|
||||
@ -474,9 +669,55 @@ pub fn capitalize(s: impl fmt::Display) -> Result<String, fmt::Error> {
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn wordcount(s: impl fmt::Display) -> Result<usize, fmt::Error> {
|
||||
let mut buffer;
|
||||
Ok(try_to_str!(s => buffer).split_whitespace().count())
|
||||
#[inline]
|
||||
pub fn wordcount<S>(source: S) -> Wordcount<S> {
|
||||
Wordcount(Cell::new(Some(WordcountInner {
|
||||
source,
|
||||
buffer: String::new(),
|
||||
})))
|
||||
}
|
||||
|
||||
pub struct Wordcount<S>(Cell<Option<WordcountInner<S>>>);
|
||||
|
||||
struct WordcountInner<S> {
|
||||
source: S,
|
||||
buffer: String,
|
||||
}
|
||||
|
||||
impl<S: fmt::Display> fmt::Display for Wordcount<S> {
|
||||
#[inline]
|
||||
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(mut inner) = self.0.take() {
|
||||
write!(inner.buffer, "{}", inner.source)?;
|
||||
self.0.set(Some(inner));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: FastWritable> FastWritable for Wordcount<S> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
_: &mut W,
|
||||
values: &dyn crate::Values,
|
||||
) -> crate::Result<()> {
|
||||
if let Some(mut inner) = self.0.take() {
|
||||
inner.source.write_into(&mut inner.buffer, values)?;
|
||||
self.0.set(Some(inner));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Wordcount<S> {
|
||||
pub fn into_count(self) -> usize {
|
||||
if let Some(inner) = self.0.into_inner() {
|
||||
inner.buffer.split_whitespace().count()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a title cased version of the value. Words will start with uppercase letters, all
|
||||
@ -500,32 +741,39 @@ pub fn wordcount(s: impl fmt::Display) -> Result<usize, fmt::Error> {
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn title(s: impl fmt::Display) -> Result<String, fmt::Error> {
|
||||
let mut buffer;
|
||||
let s = try_to_str!(s => buffer);
|
||||
let mut need_capitalization = true;
|
||||
#[inline]
|
||||
pub fn title<S: fmt::Display>(source: S) -> Result<Title<S>, Infallible> {
|
||||
Ok(Title(source))
|
||||
}
|
||||
|
||||
// Sadly enough, we can't mutate a string when iterating over its chars, likely because it could
|
||||
// change the size of a char, "breaking" the char indices.
|
||||
let mut output = String::with_capacity(s.len());
|
||||
for c in s.chars() {
|
||||
if c.is_whitespace() {
|
||||
output.push(c);
|
||||
need_capitalization = true;
|
||||
} else if need_capitalization {
|
||||
match c.is_uppercase() {
|
||||
true => output.push(c),
|
||||
false => output.extend(c.to_uppercase()),
|
||||
}
|
||||
need_capitalization = false;
|
||||
} else {
|
||||
match c.is_lowercase() {
|
||||
true => output.push(c),
|
||||
false => output.extend(c.to_lowercase()),
|
||||
}
|
||||
}
|
||||
pub struct Title<S>(S);
|
||||
|
||||
impl<S: fmt::Display> fmt::Display for Title<S> {
|
||||
#[inline]
|
||||
fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut buffer;
|
||||
flush_title(dest, try_to_str!(self.0 => buffer))
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
impl<S: FastWritable> FastWritable for Title<S> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn crate::Values,
|
||||
) -> crate::Result<()> {
|
||||
let mut buffer = String::new();
|
||||
self.0.write_into(&mut buffer, values)?;
|
||||
Ok(flush_title(dest, &buffer)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_title(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
|
||||
for word in s.split_inclusive(char::is_whitespace) {
|
||||
flush_capitalize(dest, word)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -533,6 +781,7 @@ mod tests {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use super::*;
|
||||
use crate::NO_VALUES;
|
||||
|
||||
#[test]
|
||||
fn test_linebreaks() {
|
||||
@ -630,23 +879,40 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_wordcount() {
|
||||
assert_eq!(wordcount("").unwrap(), 0);
|
||||
assert_eq!(wordcount(" \n\t").unwrap(), 0);
|
||||
assert_eq!(wordcount("foo").unwrap(), 1);
|
||||
assert_eq!(wordcount("foo bar").unwrap(), 2);
|
||||
assert_eq!(wordcount("foo bar").unwrap(), 2);
|
||||
for &(word, count) in &[
|
||||
("", 0),
|
||||
(" \n\t", 0),
|
||||
("foo", 1),
|
||||
("foo bar", 2),
|
||||
("foo bar", 2),
|
||||
] {
|
||||
let w = wordcount(word);
|
||||
let _ = w.to_string();
|
||||
assert_eq!(w.into_count(), count, "fmt: {word:?}");
|
||||
|
||||
let w = wordcount(word);
|
||||
w.write_into(&mut String::new(), NO_VALUES).unwrap();
|
||||
assert_eq!(w.into_count(), count, "FastWritable: {word:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_title() {
|
||||
assert_eq!(&title("").unwrap(), "");
|
||||
assert_eq!(&title(" \n\t").unwrap(), " \n\t");
|
||||
assert_eq!(&title("foo").unwrap(), "Foo");
|
||||
assert_eq!(&title(" foo").unwrap(), " Foo");
|
||||
assert_eq!(&title("foo bar").unwrap(), "Foo Bar");
|
||||
assert_eq!(&title("foo bar ").unwrap(), "Foo Bar ");
|
||||
assert_eq!(&title("fOO").unwrap(), "Foo");
|
||||
assert_eq!(&title("fOo BaR").unwrap(), "Foo Bar");
|
||||
assert_eq!(&title("").unwrap().to_string(), "");
|
||||
assert_eq!(&title(" \n\t").unwrap().to_string(), " \n\t");
|
||||
assert_eq!(&title("foo").unwrap().to_string(), "Foo");
|
||||
assert_eq!(&title(" foo").unwrap().to_string(), " Foo");
|
||||
assert_eq!(&title("foo bar").unwrap().to_string(), "Foo Bar");
|
||||
assert_eq!(&title("foo bar ").unwrap().to_string(), "Foo Bar ");
|
||||
assert_eq!(&title("fOO").unwrap().to_string(), "Foo");
|
||||
assert_eq!(&title("fOo BaR").unwrap().to_string(), "Foo Bar");
|
||||
assert_eq!(&title("foo\r\nbar").unwrap().to_string(), "Foo\r\nBar");
|
||||
assert_eq!(
|
||||
&title("Fo\x0boo\x0coO\u{2002}OO\u{3000}baR")
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"Fo\x0bOo\x0cOo\u{2002}Oo\u{3000}Bar"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -6,7 +6,7 @@ use core::pin::Pin;
|
||||
|
||||
use super::MAX_LEN;
|
||||
use super::escape::FastWritable;
|
||||
use crate::{Error, Result};
|
||||
use crate::{Error, Result, Values};
|
||||
|
||||
/// Limit string length, appends '...' if truncated
|
||||
///
|
||||
@ -50,9 +50,13 @@ impl<S: fmt::Display> fmt::Display for TruncateFilter<S> {
|
||||
|
||||
impl<S: FastWritable> FastWritable for TruncateFilter<S> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
self.source
|
||||
.write_into(&mut TruncateWriter::new(dest, self.remaining))
|
||||
.write_into(&mut TruncateWriter::new(dest, self.remaining), values)
|
||||
}
|
||||
}
|
||||
|
||||
@ -440,10 +444,14 @@ impl<S: fmt::Display, P: fmt::Display> fmt::Display for Pluralize<S, P> {
|
||||
|
||||
impl<S: FastWritable, P: FastWritable> FastWritable for Pluralize<S, P> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
match self {
|
||||
Pluralize::Singular(value) => value.write_into(dest),
|
||||
Pluralize::Plural(value) => value.write_into(dest),
|
||||
Pluralize::Singular(value) => value.write_into(dest, values),
|
||||
Pluralize::Plural(value) => value.write_into(dest, values),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,8 +82,12 @@ impl<T: fmt::Display, E: Escaper> fmt::Display for EscapeDisplay<T, E> {
|
||||
|
||||
impl<T: FastWritable, E: Escaper> FastWritable for EscapeDisplay<T, E> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
self.0.write_into(&mut EscapeWriter(dest, self.1))
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
self.0.write_into(&mut EscapeWriter(dest, self.1), values)
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,10 +254,14 @@ impl<'a, T: HtmlSafe + ?Sized> AutoEscape for &AutoEscaper<'a, T, Html> {
|
||||
///
|
||||
/// ```rust
|
||||
/// mod filters {
|
||||
/// use askama::{filters::MaybeSafe, Result};
|
||||
/// use askama::{filters::MaybeSafe, Result, Values};
|
||||
///
|
||||
/// // Do not actually use this filter! It's an intentionally bad example.
|
||||
/// pub fn backdoor<T: std::fmt::Display>(s: T, enable: &bool) -> Result<MaybeSafe<T>> {
|
||||
/// pub fn backdoor<T: std::fmt::Display>(
|
||||
/// s: T,
|
||||
/// _: &dyn Values,
|
||||
/// enable: &bool,
|
||||
/// ) -> Result<MaybeSafe<T>> {
|
||||
/// Ok(match *enable {
|
||||
/// true => MaybeSafe::Safe(s),
|
||||
/// false => MaybeSafe::NeedsEscaping(s),
|
||||
@ -303,12 +311,16 @@ const _: () = {
|
||||
// This is the fallback. The filter is not the last element of the filter chain.
|
||||
impl<T: FastWritable> FastWritable for MaybeSafe<T> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
let inner = match self {
|
||||
MaybeSafe::Safe(inner) => inner,
|
||||
MaybeSafe::NeedsEscaping(inner) => inner,
|
||||
};
|
||||
inner.write_into(dest)
|
||||
inner.write_into(dest, values)
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,10 +350,14 @@ const _: () = {
|
||||
}
|
||||
|
||||
impl<T: FastWritable + ?Sized, E: Escaper> FastWritable for Wrapped<'_, T, E> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
match *self {
|
||||
Wrapped::Safe(t) => t.write_into(dest),
|
||||
Wrapped::NeedsEscaping(t, e) => EscapeDisplay(t, e).write_into(dest),
|
||||
Wrapped::Safe(t) => t.write_into(dest, values),
|
||||
Wrapped::NeedsEscaping(t, e) => EscapeDisplay(t, e).write_into(dest, values),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -368,10 +384,10 @@ const _: () = {
|
||||
///
|
||||
/// ```rust
|
||||
/// mod filters {
|
||||
/// use askama::{filters::Safe, Result};
|
||||
/// use askama::{filters::Safe, Result, Values};
|
||||
///
|
||||
/// // Do not actually use this filter! It's an intentionally bad example.
|
||||
/// pub fn strip_except_apos(s: impl ToString) -> Result<Safe<String>> {
|
||||
/// pub fn strip_except_apos(s: impl ToString, _: &dyn Values) -> Result<Safe<String>> {
|
||||
/// Ok(Safe(s
|
||||
/// .to_string()
|
||||
/// .chars()
|
||||
@ -409,8 +425,12 @@ const _: () = {
|
||||
// This is the fallback. The filter is not the last element of the filter chain.
|
||||
impl<T: FastWritable> FastWritable for Safe<T> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
self.0.write_into(dest)
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
self.0.write_into(dest, values)
|
||||
}
|
||||
}
|
||||
|
||||
@ -503,15 +523,23 @@ pub trait WriteWritable {
|
||||
/// Types implementing this trait can be written without needing to employ an [`fmt::Formatter`].
|
||||
pub trait FastWritable {
|
||||
/// Used internally by askama to speed up writing some types.
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()>;
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
crate::impl_for_ref! {
|
||||
impl FastWritable for T {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
<T>::write_into(self, dest)
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
<T>::write_into(self, dest, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -522,16 +550,24 @@ const _: () = {
|
||||
<T as Deref>::Target: FastWritable,
|
||||
{
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
self.as_ref().get_ref().write_into(dest)
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
self.as_ref().get_ref().write_into(dest, values)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<T: FastWritable + alloc::borrow::ToOwned> FastWritable for alloc::borrow::Cow<'_, T> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
T::write_into(self.as_ref(), dest)
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
T::write_into(self.as_ref(), dest, values)
|
||||
}
|
||||
}
|
||||
|
||||
@ -540,8 +576,12 @@ const _: () = {
|
||||
($($ty:ty)*) => { $(
|
||||
impl FastWritable for $ty {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
itoa::Buffer::new().format(*self).write_into(dest)
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
itoa::Buffer::new().format(*self).write_into(dest, values)
|
||||
}
|
||||
}
|
||||
)* };
|
||||
@ -557,8 +597,12 @@ const _: () = {
|
||||
($($id:ident)*) => { $(
|
||||
impl FastWritable for core::num::$id {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
self.get().write_into(dest)
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
self.get().write_into(dest, values)
|
||||
}
|
||||
}
|
||||
)* };
|
||||
@ -571,7 +615,11 @@ const _: () = {
|
||||
|
||||
impl FastWritable for str {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
_: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
Ok(dest.write_str(self)?)
|
||||
}
|
||||
}
|
||||
@ -579,14 +627,22 @@ const _: () = {
|
||||
#[cfg(feature = "alloc")]
|
||||
impl FastWritable for alloc::string::String {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
self.as_str().write_into(dest)
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
self.as_str().write_into(dest, values)
|
||||
}
|
||||
}
|
||||
|
||||
impl FastWritable for bool {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
_: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
Ok(dest.write_str(match self {
|
||||
true => "true",
|
||||
false => "false",
|
||||
@ -596,13 +652,21 @@ const _: () = {
|
||||
|
||||
impl FastWritable for char {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
_: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
Ok(dest.write_char(*self)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl FastWritable for fmt::Arguments<'_> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
_: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
Ok(match self.as_str() {
|
||||
Some(s) => dest.write_str(s),
|
||||
None => dest.write_fmt(*self),
|
||||
@ -626,9 +690,9 @@ const _: () = {
|
||||
fn askama_write<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
_: &dyn Values,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
self.0.write_into(dest)
|
||||
self.0.write_into(dest, values)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ use core::mem::MaybeUninit;
|
||||
|
||||
use super::FastWritable;
|
||||
use crate::ascii_str::{AsciiChar, AsciiStr};
|
||||
use crate::{NO_VALUES, Values};
|
||||
|
||||
/// Returns adequate string representation (in KB, ..) of number of bytes
|
||||
///
|
||||
@ -33,14 +34,18 @@ pub struct FilesizeFormatFilter(f32);
|
||||
impl fmt::Display for FilesizeFormatFilter {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Ok(self.write_into(f)?)
|
||||
Ok(self.write_into(f, NO_VALUES)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl FastWritable for FilesizeFormatFilter {
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
if self.0 < 1e3 {
|
||||
(self.0 as u32).write_into(dest)?;
|
||||
(self.0 as u32).write_into(dest, values)?;
|
||||
Ok(dest.write_str(" B")?)
|
||||
} else if let Some((prefix, factor)) = SI_PREFIXES
|
||||
.iter()
|
||||
@ -49,8 +54,8 @@ impl FastWritable for FilesizeFormatFilter {
|
||||
{
|
||||
// u32 is big enough to hold the number 999_999
|
||||
let scaled = (self.0 * factor) as u32;
|
||||
(scaled / 100).write_into(dest)?;
|
||||
format_frac(&mut MaybeUninit::uninit(), prefix, scaled).write_into(dest)
|
||||
(scaled / 100).write_into(dest, values)?;
|
||||
format_frac(&mut MaybeUninit::uninit(), prefix, scaled).write_into(dest, values)
|
||||
} else {
|
||||
too_big(self.0, dest)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use serde_json::ser::{CompactFormatter, PrettyFormatter, Serializer};
|
||||
|
||||
use super::FastWritable;
|
||||
use crate::ascii_str::{AsciiChar, AsciiStr};
|
||||
use crate::{NO_VALUES, Values};
|
||||
|
||||
/// Serialize to JSON (requires `json` feature)
|
||||
///
|
||||
@ -188,7 +189,8 @@ where
|
||||
}
|
||||
|
||||
impl<S: Serialize> FastWritable for ToJson<S> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W, _: &dyn Values) -> crate::Result<()> {
|
||||
serialize(f, &self.value, CompactFormatter)
|
||||
}
|
||||
}
|
||||
@ -196,12 +198,13 @@ impl<S: Serialize> FastWritable for ToJson<S> {
|
||||
impl<S: Serialize> fmt::Display for ToJson<S> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Ok(self.write_into(f)?)
|
||||
Ok(self.write_into(f, NO_VALUES)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Serialize, I: AsIndent> FastWritable for ToJsonPretty<S, I> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W, _: &dyn Values) -> crate::Result<()> {
|
||||
serialize(
|
||||
f,
|
||||
&self.value,
|
||||
@ -213,11 +216,10 @@ impl<S: Serialize, I: AsIndent> FastWritable for ToJsonPretty<S, I> {
|
||||
impl<S: Serialize, I: AsIndent> fmt::Display for ToJsonPretty<S, I> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Ok(self.write_into(f)?)
|
||||
Ok(self.write_into(f, NO_VALUES)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn serialize<S, W, F>(dest: &mut W, value: &S, formatter: F) -> Result<(), crate::Error>
|
||||
where
|
||||
S: Serialize + ?Sized,
|
||||
|
@ -4,6 +4,7 @@ use std::fmt::Write;
|
||||
|
||||
use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
|
||||
|
||||
use crate::Values;
|
||||
use crate::filters::{FastWritable, HtmlSafeOutput};
|
||||
|
||||
// Urlencode char encoding set. Only the characters in the unreserved set don't
|
||||
@ -110,8 +111,12 @@ impl<T: fmt::Display> fmt::Display for UrlencodeFilter<T> {
|
||||
|
||||
impl<T: FastWritable> FastWritable for UrlencodeFilter<T> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> {
|
||||
self.0.write_into(&mut UrlencodeWriter(f, self.1))
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
f: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
self.0.write_into(&mut UrlencodeWriter(f, self.1), values)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ use core::iter::{Enumerate, Peekable};
|
||||
use core::ops::Deref;
|
||||
use core::pin::Pin;
|
||||
|
||||
use crate::Values;
|
||||
pub use crate::error::{ErrorMarker, ResultConverter};
|
||||
use crate::filters::FastWritable;
|
||||
pub use crate::values::get_value;
|
||||
@ -245,7 +246,19 @@ impl fmt::Display for Empty {
|
||||
|
||||
impl FastWritable for Empty {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, _: &mut W) -> crate::Result<()> {
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, _: &mut W, _: &dyn Values) -> crate::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for Empty {
|
||||
#[inline]
|
||||
fn write_str(&mut self, _: &str) -> fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_char(&mut self, _: char) -> fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -267,9 +280,13 @@ impl<L: fmt::Display, R: fmt::Display> fmt::Display for Concat<L, R> {
|
||||
|
||||
impl<L: FastWritable, R: FastWritable> FastWritable for Concat<L, R> {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
|
||||
self.0.write_into(dest)?;
|
||||
self.1.write_into(dest)
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
dest: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
self.0.write_into(dest, values)?;
|
||||
self.1.write_into(dest, values)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,8 +406,12 @@ mod tests {
|
||||
|
||||
impl filters::FastWritable for Test {
|
||||
#[inline]
|
||||
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> {
|
||||
self.render_into(f)
|
||||
fn write_into<W: fmt::Write + ?Sized>(
|
||||
&self,
|
||||
f: &mut W,
|
||||
values: &dyn Values,
|
||||
) -> crate::Result<()> {
|
||||
self.render_into_with_values(f, values)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,11 +310,15 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
impl #wrapper_impl_generics askama::filters::FastWritable
|
||||
for #wrapper_id #wrapper_ty_generics #wrapper_where_clause {
|
||||
#[inline]
|
||||
fn write_into<AskamaW>(&self, dest: &mut AskamaW) -> askama::Result<()>
|
||||
fn write_into<AskamaW>(
|
||||
&self,
|
||||
dest: &mut AskamaW,
|
||||
values: &dyn askama::Values,
|
||||
) -> askama::Result<()>
|
||||
where
|
||||
AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized
|
||||
{
|
||||
<_ as askama::Template>::render_into(self, dest)
|
||||
<_ as askama::Template>::render_into_with_values(self, dest, values)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,6 +268,7 @@ impl<'a> Generator<'a, '_> {
|
||||
"urlencode" => Self::_visit_urlencode_filter,
|
||||
"urlencode_strict" => Self::_visit_urlencode_strict_filter,
|
||||
"value" => return self._visit_value(ctx, buf, args, generics, node, "`value` filter"),
|
||||
"wordcount" => Self::_visit_wordcount_filter,
|
||||
name if BUILTIN_FILTERS.contains(&name) => {
|
||||
return self._visit_builtin_filter(ctx, buf, name, args, generics, node);
|
||||
}
|
||||
@ -295,7 +296,12 @@ impl<'a> Generator<'a, '_> {
|
||||
buf.write(format_args!("filters::{name}"));
|
||||
self.visit_call_generics(buf, generics);
|
||||
buf.write('(');
|
||||
self._visit_args(ctx, buf, args)?;
|
||||
self._visit_arg(ctx, buf, &args[0])?;
|
||||
buf.write(",__askama_values");
|
||||
if args.len() > 1 {
|
||||
buf.write(',');
|
||||
self._visit_args(ctx, buf, &args[1..])?;
|
||||
}
|
||||
buf.write(")?");
|
||||
Ok(DisplayWrap::Unwrapped)
|
||||
}
|
||||
@ -366,6 +372,37 @@ impl<'a> Generator<'a, '_> {
|
||||
Ok(DisplayWrap::Unwrapped)
|
||||
}
|
||||
|
||||
fn _visit_wordcount_filter(
|
||||
&mut self,
|
||||
ctx: &Context<'_>,
|
||||
buf: &mut Buffer,
|
||||
args: &[WithSpan<'_, Expr<'a>>],
|
||||
node: Span<'_>,
|
||||
) -> Result<DisplayWrap, CompileError> {
|
||||
ensure_filter_has_feature_alloc(ctx, "wordcount", node)?;
|
||||
if args.len() != 1 {
|
||||
return Err(ctx.generate_error(
|
||||
format_args!("unexpected argument(s) in `wordcount` filter"),
|
||||
node,
|
||||
));
|
||||
}
|
||||
|
||||
buf.write("match askama::filters::wordcount(&(");
|
||||
self._visit_args(ctx, buf, args)?;
|
||||
buf.write(
|
||||
")) {\
|
||||
expr0 => {\
|
||||
(&&&askama::filters::Writable(&expr0)).\
|
||||
askama_write(&mut askama::helpers::Empty, __askama_values)?;\
|
||||
expr0.into_count()\
|
||||
}\
|
||||
}\
|
||||
",
|
||||
);
|
||||
|
||||
Ok(DisplayWrap::Unwrapped)
|
||||
}
|
||||
|
||||
fn _visit_humansize(
|
||||
&mut self,
|
||||
ctx: &Context<'_>,
|
||||
|
@ -64,11 +64,15 @@ fn impl_fast_writable(ast: &DeriveInput, buf: &mut Buffer) {
|
||||
buf.write(
|
||||
"\
|
||||
#[inline]\
|
||||
fn write_into<AskamaW>(&self, dest: &mut AskamaW) -> askama::Result<()> \
|
||||
fn write_into<AskamaW>(\
|
||||
&self,\
|
||||
dest: &mut AskamaW,\
|
||||
values: &dyn askama::Values\
|
||||
) -> askama::Result<()> \
|
||||
where \
|
||||
AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized,\
|
||||
{\
|
||||
askama::Template::render_into(self, dest)\
|
||||
askama::Template::render_into_with_values(self, dest, values)\
|
||||
}\
|
||||
}",
|
||||
);
|
||||
|
@ -611,7 +611,6 @@ const BUILTIN_FILTERS: &[&str] = &[
|
||||
"truncate",
|
||||
"upper",
|
||||
"uppercase",
|
||||
"wordcount",
|
||||
];
|
||||
|
||||
// Built-in filters that need the `alloc` feature.
|
||||
|
@ -69,11 +69,15 @@ fn compare_ex(
|
||||
|
||||
impl askama::filters::FastWritable for Foo {
|
||||
#[inline]
|
||||
fn write_into<AskamaW>(&self, dest: &mut AskamaW) -> askama::Result<()>
|
||||
fn write_into<AskamaW>(
|
||||
&self,
|
||||
dest: &mut AskamaW,
|
||||
values: &dyn askama::Values,
|
||||
) -> askama::Result<()>
|
||||
where
|
||||
AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized,
|
||||
{
|
||||
askama::Template::render_into(self, dest)
|
||||
askama::Template::render_into_with_values(self, dest, values)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -467,17 +467,20 @@ Prefix with two characters:
|
||||
|
||||
To define your own filters, simply have a module named `filters` in scope of the context deriving a `Template` impl
|
||||
and define the filters as functions within this module.
|
||||
The functions must have at least one argument and the return type must be `askama::Result<T>`.
|
||||
The functions must have at least two arguments and the return type must be `askama::Result<T>`.
|
||||
Although there are no restrictions on `T` for a single filter,
|
||||
the final result of a chain of filters must implement `Display`.
|
||||
|
||||
The arguments to the filters are passed as follows.
|
||||
The first argument corresponds to the expression they are applied to.
|
||||
Subsequent arguments, if any, must be given directly when calling the filter.
|
||||
The first argument may or may not be a reference, depending on the context in which the filter is called.
|
||||
The arguments to the filters are passed as follows:
|
||||
* The first argument corresponds to the expression they are applied to.
|
||||
* The second argument are the [runtime values](./runtime.html):
|
||||
[`values: &dyn askama::Values`](./doc/askama/trait.Values.html).
|
||||
* Subsequent arguments, if any, must be given directly when calling the filter.
|
||||
The first argument may or may not be a reference, depending on the context in which the filter is called.
|
||||
|
||||
To abstract over ownership, consider defining your argument as a trait bound.
|
||||
For example, the `trim` built-in filter accepts any value implementing `Display`.
|
||||
Its signature is similar to `fn trim(s: impl std::fmt::Display) -> askama::Result<String>`.
|
||||
Its signature is similar to `fn trim(s: impl std::fmt::Display, values: &dyn askama::Values) -> askama::Result<String>`.
|
||||
|
||||
Note that built-in filters have preference over custom filters, so, in case of name collision, the built-in filter is applied.
|
||||
|
||||
@ -496,7 +499,10 @@ struct MyFilterTemplate<'a> {
|
||||
// Any filter defined in the module `filters` is accessible in your template.
|
||||
mod filters {
|
||||
// This filter does not have extra arguments
|
||||
pub fn myfilter<T: std::fmt::Display>(s: T) -> askama::Result<String> {
|
||||
pub fn myfilter<T: std::fmt::Display>(
|
||||
s: T,
|
||||
_: &dyn askama::Values,
|
||||
) -> askama::Result<String> {
|
||||
let s = s.to_string();
|
||||
Ok(s.replace("oo", "aa"))
|
||||
}
|
||||
@ -521,7 +527,11 @@ struct MyFilterTemplate<'a> {
|
||||
// Any filter defined in the module `filters` is accessible in your template.
|
||||
mod filters {
|
||||
// This filter requires a `usize` input when called in templates
|
||||
pub fn myfilter<T: std::fmt::Display>(s: T, n: usize) -> askama::Result<String> {
|
||||
pub fn myfilter<T: std::fmt::Display>(
|
||||
s: T,
|
||||
_: &dyn askama::Values,
|
||||
n: usize,
|
||||
) -> askama::Result<String> {
|
||||
let s = s.to_string();
|
||||
let mut replace = String::with_capacity(n);
|
||||
replace.extend((0..n).map(|_| "a"));
|
||||
@ -535,6 +545,15 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Runtime values
|
||||
[#runtime-values]: #runtime-values
|
||||
|
||||
It is possible to access [runtime values](./runtime.html) in custom filters:
|
||||
|
||||
```rust
|
||||
{{#include ../../testing/tests/book_example_runtime_values_in_custom_filters.rs}}
|
||||
```
|
||||
|
||||
## HTML-safe types
|
||||
[#html-safe-types]: #html-safe-types
|
||||
|
||||
|
@ -0,0 +1,60 @@
|
||||
// This example contains a custom filter `|cased`.
|
||||
// Depending on the runtime value `"case"`, the input is either turned
|
||||
// into lower case, upper case, or left alone, if the runtime value is undefined.
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use askama::{Template, Values};
|
||||
|
||||
mod filters {
|
||||
use super::*;
|
||||
|
||||
pub fn cased(value: impl ToString, values: &dyn Values) -> askama::Result<String> {
|
||||
let value = value.to_string();
|
||||
let case = askama::get_value(values, "case").ok();
|
||||
Ok(match case {
|
||||
Some(Case::Lower) => value.to_lowercase(),
|
||||
Some(Case::Upper) => value.to_uppercase(),
|
||||
None => value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Case {
|
||||
Lower,
|
||||
Upper,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runtime_values_in_custom_filters() {
|
||||
#[derive(Template)]
|
||||
#[template(ext = "txt", source = "Hello, {{ user | cased }}!")]
|
||||
struct MyStruct<'a> {
|
||||
user: &'a str,
|
||||
}
|
||||
|
||||
// The filter source ("wOrLd") should be written in lower case.
|
||||
let values: (&str, &dyn Any) = ("case", &Case::Lower);
|
||||
assert_eq!(
|
||||
MyStruct { user: "wOrLd" }
|
||||
.render_with_values(&values)
|
||||
.unwrap(),
|
||||
"Hello, world!"
|
||||
);
|
||||
|
||||
// The filter source ("wOrLd") should be written in upper case.
|
||||
let values: (&str, &dyn Any) = ("case", &Case::Upper);
|
||||
assert_eq!(
|
||||
MyStruct { user: "wOrLd" }
|
||||
.render_with_values(&values)
|
||||
.unwrap(),
|
||||
"Hello, WORLD!"
|
||||
);
|
||||
|
||||
// The filter source ("wOrLd") should be written as is.
|
||||
assert_eq!(
|
||||
MyStruct { user: "wOrLd" }.render().unwrap(),
|
||||
"Hello, wOrLd!"
|
||||
);
|
||||
}
|
@ -99,11 +99,14 @@ fn filter_fmt() {
|
||||
}
|
||||
|
||||
mod filters {
|
||||
pub fn myfilter(s: &str) -> ::askama::Result<String> {
|
||||
use askama::Values;
|
||||
|
||||
pub fn myfilter(s: &str, _: &dyn Values) -> ::askama::Result<String> {
|
||||
Ok(s.replace("oo", "aa"))
|
||||
}
|
||||
|
||||
// for test_nested_filter_ref
|
||||
pub fn mytrim(s: &dyn (::std::fmt::Display)) -> ::askama::Result<String> {
|
||||
pub fn mytrim(s: &dyn std::fmt::Display, _: &dyn Values) -> ::askama::Result<String> {
|
||||
Ok(s.to_string().trim().to_owned())
|
||||
}
|
||||
}
|
||||
|
@ -88,3 +88,71 @@ fn test_value_function_getter() {
|
||||
values.insert("a".to_string(), Box::new(false));
|
||||
assert_eq!(V.render_with_values(&values).unwrap(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_in_subtemplates() {
|
||||
// In this test we make sure that values are passed down to transcluded sub-templates,
|
||||
// even if there is a filter in the mix, e.g. the implicit `|escape` filter.
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = r#"{{ Child }}"#, ext = "html")]
|
||||
struct Parent;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = r#"Hello, {{ askama::get_value::<String>("who")? }}!"#,
|
||||
ext = "html"
|
||||
)]
|
||||
struct Child;
|
||||
|
||||
let values: (&str, &dyn Any) = ("who", &"<world>".to_owned());
|
||||
assert_eq!(
|
||||
Parent.render_with_values(&values).unwrap(),
|
||||
"Hello, &#60;world&#62;!", // sic: escaped twice
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_in_subtemplates_with_filters() {
|
||||
// In this test we make sure that values are passed down to transcluded sub-templates,
|
||||
// even if there is a filter in the mix.
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = r#"{{ Child }}"#, ext = "html")]
|
||||
struct Parent;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = r#"Hello, {{ askama::get_value::<String>("who")? | upper }}!"#,
|
||||
ext = "html"
|
||||
)]
|
||||
struct Child;
|
||||
|
||||
let values: (&str, &dyn Any) = ("who", &"<world>".to_owned());
|
||||
assert_eq!(
|
||||
Parent.render_with_values(&values).unwrap(),
|
||||
"Hello, &#60;WORLD&#62;!", // sic: escaped twice
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_in_wordcount() {
|
||||
// This test makes sure that `|wordcount` has has access to runtime values.
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = r#"{{ Child|wordcount }}"#, ext = "html")]
|
||||
struct Parent;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = r#"{{ askama::get_value::<&str>("sentence")? }}"#,
|
||||
ext = "html"
|
||||
)]
|
||||
struct Child;
|
||||
|
||||
let values: (&str, &dyn Any) = (
|
||||
"sentence",
|
||||
&"In simple terms, a sentence is a set of words.",
|
||||
);
|
||||
assert_eq!(Parent.render_with_values(&values).unwrap(), "10");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user