diff --git a/askama/src/filters/core.rs b/askama/src/filters/core.rs index 466e61de..8dcf34c4 100644 --- a/askama/src/filters/core.rs +++ b/askama/src/filters/core.rs @@ -2,13 +2,12 @@ use core::cell::Cell; use core::convert::Infallible; use core::fmt::{self, Write}; use core::mem::replace; -use core::num::{Saturating, Wrapping}; use core::ops::Deref; use core::pin::Pin; use super::MAX_LEN; use crate::filters::HtmlSafeOutput; -use crate::{Error, FastWritable, Result, Values, impl_for_ref}; +use crate::{Error, FastWritable, Result, Values}; /// Limit string length, appends '...' if truncated /// @@ -429,237 +428,6 @@ const _: () = { } }; -/// Render `value` if it is not its "default" value, see [`DefaultFilterable`], -/// otherwise `fallback`. -#[inline] -pub fn assigned_or( - value: &L, - fallback: R, -) -> Result, R>, L::Error> { - match value.as_filtered()? { - Some(value) => Ok(Either::Left(value)), - None => Ok(Either::Right(fallback)), - } -} - -/// A type (or a reference to it) that can be used in [`|assigned_or`](assigned_or). -/// -/// The type is either a monad such as [`Option`] or [`Result`], or a type that has a well defined, -/// trivial default value, e.g. an [empty](str::is_empty) [`str`] or `0` for integer types. -#[diagnostic::on_unimplemented( - label = "`{Self}` is not `|assigned_or` filterable", - message = "`{Self}` is not `|assigned_or` filterable" -)] -pub trait DefaultFilterable { - /// The contained value - type Filtered<'a> - where - Self: 'a; - - /// An error that prevented [`as_filtered()`](DefaultFilterable::as_filtered) to succeed, - /// e.g. a poisoned state or an unacquirable lock. - type Error: Into; - - /// Return the contained value, if a value was contained, and it's not the default value. - /// - /// Returns `Ok(None)` if the value could not be unwrapped. - fn as_filtered(&self) -> Result>, Self::Error>; -} - -const _: () = { - impl_for_ref! { - impl DefaultFilterable for T { - type Filtered<'a> = T::Filtered<'a> - where - Self: 'a; - - type Error = T::Error; - - #[inline] - fn as_filtered(&self) -> Result>, Self::Error> { - ::as_filtered(self) - } - } - } - - impl DefaultFilterable for Pin - where - T: Deref, - ::Target: DefaultFilterable, - { - type Filtered<'a> - = <::Target as DefaultFilterable>::Filtered<'a> - where - Self: 'a; - - type Error = <::Target as DefaultFilterable>::Error; - - #[inline] - fn as_filtered(&self) -> Result>, Self::Error> { - self.as_ref().get_ref().as_filtered() - } - } - - impl DefaultFilterable for Option { - type Filtered<'a> - = &'a T - where - Self: 'a; - - type Error = Infallible; - - #[inline] - fn as_filtered(&self) -> Result, Infallible> { - Ok(self.as_ref()) - } - } - - impl DefaultFilterable for Result { - type Filtered<'a> - = &'a T - where - Self: 'a; - - type Error = Infallible; - - #[inline] - fn as_filtered(&self) -> Result, Infallible> { - Ok(self.as_ref().ok()) - } - } - - impl DefaultFilterable for str { - type Filtered<'a> - = &'a str - where - Self: 'a; - - type Error = Infallible; - - #[inline] - fn as_filtered(&self) -> Result, Infallible> { - match self.is_empty() { - false => Ok(Some(self)), - true => Ok(None), - } - } - } - - #[cfg(feature = "alloc")] - impl DefaultFilterable for alloc::string::String { - type Filtered<'a> - = &'a str - where - Self: 'a; - - type Error = Infallible; - - #[inline] - fn as_filtered(&self) -> Result, Infallible> { - self.as_str().as_filtered() - } - } - - #[cfg(feature = "alloc")] - impl DefaultFilterable - for alloc::borrow::Cow<'_, T> - { - type Filtered<'a> - = T::Filtered<'a> - where - Self: 'a; - - type Error = T::Error; - - #[inline] - fn as_filtered(&self) -> Result>, Self::Error> { - self.as_ref().as_filtered() - } - } - - impl DefaultFilterable for Wrapping { - type Filtered<'a> - = T::Filtered<'a> - where - Self: 'a; - - type Error = T::Error; - - #[inline] - fn as_filtered(&self) -> Result>, Self::Error> { - self.0.as_filtered() - } - } - - impl DefaultFilterable for Saturating { - type Filtered<'a> - = T::Filtered<'a> - where - Self: 'a; - - type Error = T::Error; - - #[inline] - fn as_filtered(&self) -> Result>, Self::Error> { - self.0.as_filtered() - } - } - - macro_rules! impl_for_int { - ($($ty:ty)*) => { $( - impl DefaultFilterable for $ty { - type Filtered<'a> = $ty; - type Error = Infallible; - - #[inline] - fn as_filtered(&self) -> Result, Infallible> { - match *self { - 0 => Ok(None), - value => Ok(Some(value)), - } - } - } - )* }; - } - - impl_for_int!( - u8 u16 u32 u64 u128 usize - i8 i16 i32 i64 i128 isize - ); - - macro_rules! impl_for_non_zero { - ($($name:ident : $ty:ty)*) => { $( - impl DefaultFilterable for core::num::$name { - type Filtered<'a> = $ty; - type Error = Infallible; - - #[inline] - fn as_filtered(&self) -> Result, Infallible> { - Ok(Some(self.get())) - } - } - )* }; - } - - impl_for_non_zero!( - NonZeroU8:u8 NonZeroU16:u16 NonZeroU32:u32 NonZeroU64:u64 NonZeroU128:u128 NonZeroUsize:usize - NonZeroI8:i8 NonZeroI16:i16 NonZeroI32:i32 NonZeroI64:i64 NonZeroI128:i128 NonZeroIsize:isize - ); - - impl DefaultFilterable for bool { - type Filtered<'a> = bool; - type Error = Infallible; - - #[inline] - fn as_filtered(&self) -> Result, Infallible> { - match *self { - true => Ok(Some(true)), - false => Ok(None), - } - } - } -}; - /// Render either `L` or `R` pub enum Either { /// First variant diff --git a/askama/src/filters/default.rs b/askama/src/filters/default.rs new file mode 100644 index 00000000..55dc71e1 --- /dev/null +++ b/askama/src/filters/default.rs @@ -0,0 +1,236 @@ +use core::convert::Infallible; +use core::num::{Saturating, Wrapping}; +use core::ops::Deref; +use core::pin::Pin; + +use crate::filters::Either; +use crate::{Result, impl_for_ref}; + +/// Render `value` if it is not its "default" value, see [`DefaultFilterable`], +/// otherwise `fallback`. +#[inline] +pub fn assigned_or( + value: &L, + fallback: R, +) -> Result, R>, L::Error> { + match value.as_filtered()? { + Some(value) => Ok(Either::Left(value)), + None => Ok(Either::Right(fallback)), + } +} + +/// A type (or a reference to it) that can be used in [`|assigned_or`](assigned_or). +/// +/// The type is either a monad such as [`Option`] or [`Result`], or a type that has a well defined, +/// trivial default value, e.g. an [empty](str::is_empty) [`str`] or `0` for integer types. +#[diagnostic::on_unimplemented( + label = "`{Self}` is not `|assigned_or` filterable", + message = "`{Self}` is not `|assigned_or` filterable" +)] +pub trait DefaultFilterable { + /// The contained value + type Filtered<'a> + where + Self: 'a; + + /// An error that prevented [`as_filtered()`](DefaultFilterable::as_filtered) to succeed, + /// e.g. a poisoned state or an unacquirable lock. + type Error: Into; + + /// Return the contained value, if a value was contained, and it's not the default value. + /// + /// Returns `Ok(None)` if the value could not be unwrapped. + fn as_filtered(&self) -> Result>, Self::Error>; +} + +impl_for_ref! { + impl DefaultFilterable for T { + type Filtered<'a> = T::Filtered<'a> + where + Self: 'a; + + type Error = T::Error; + + #[inline] + fn as_filtered(&self) -> Result>, Self::Error> { + ::as_filtered(self) + } + } +} + +impl DefaultFilterable for Pin +where + T: Deref, + ::Target: DefaultFilterable, +{ + type Filtered<'a> + = <::Target as DefaultFilterable>::Filtered<'a> + where + Self: 'a; + + type Error = <::Target as DefaultFilterable>::Error; + + #[inline] + fn as_filtered(&self) -> Result>, Self::Error> { + self.as_ref().get_ref().as_filtered() + } +} + +impl DefaultFilterable for Option { + type Filtered<'a> + = &'a T + where + Self: 'a; + + type Error = Infallible; + + #[inline] + fn as_filtered(&self) -> Result, Infallible> { + Ok(self.as_ref()) + } +} + +impl DefaultFilterable for Result { + type Filtered<'a> + = &'a T + where + Self: 'a; + + type Error = Infallible; + + #[inline] + fn as_filtered(&self) -> Result, Infallible> { + Ok(self.as_ref().ok()) + } +} + +impl DefaultFilterable for str { + type Filtered<'a> + = &'a str + where + Self: 'a; + + type Error = Infallible; + + #[inline] + fn as_filtered(&self) -> Result, Infallible> { + match self.is_empty() { + false => Ok(Some(self)), + true => Ok(None), + } + } +} + +#[cfg(feature = "alloc")] +impl DefaultFilterable for alloc::string::String { + type Filtered<'a> + = &'a str + where + Self: 'a; + + type Error = Infallible; + + #[inline] + fn as_filtered(&self) -> Result, Infallible> { + self.as_str().as_filtered() + } +} + +#[cfg(feature = "alloc")] +impl DefaultFilterable + for alloc::borrow::Cow<'_, T> +{ + type Filtered<'a> + = T::Filtered<'a> + where + Self: 'a; + + type Error = T::Error; + + #[inline] + fn as_filtered(&self) -> Result>, Self::Error> { + self.as_ref().as_filtered() + } +} + +impl DefaultFilterable for Wrapping { + type Filtered<'a> + = T::Filtered<'a> + where + Self: 'a; + + type Error = T::Error; + + #[inline] + fn as_filtered(&self) -> Result>, Self::Error> { + self.0.as_filtered() + } +} + +impl DefaultFilterable for Saturating { + type Filtered<'a> + = T::Filtered<'a> + where + Self: 'a; + + type Error = T::Error; + + #[inline] + fn as_filtered(&self) -> Result>, Self::Error> { + self.0.as_filtered() + } +} + +macro_rules! impl_for_int { + ($($ty:ty)*) => { $( + impl DefaultFilterable for $ty { + type Filtered<'a> = $ty; + type Error = Infallible; + + #[inline] + fn as_filtered(&self) -> Result, Infallible> { + match *self { + 0 => Ok(None), + value => Ok(Some(value)), + } + } + } + )* }; +} + +impl_for_int!( + u8 u16 u32 u64 u128 usize + i8 i16 i32 i64 i128 isize +); + +macro_rules! impl_for_non_zero { + ($($name:ident : $ty:ty)*) => { $( + impl DefaultFilterable for core::num::$name { + type Filtered<'a> = $ty; + type Error = Infallible; + + #[inline] + fn as_filtered(&self) -> Result, Infallible> { + Ok(Some(self.get())) + } + } + )* }; +} + +impl_for_non_zero!( + NonZeroU8:u8 NonZeroU16:u16 NonZeroU32:u32 NonZeroU64:u64 NonZeroU128:u128 NonZeroUsize:usize + NonZeroI8:i8 NonZeroI16:i16 NonZeroI32:i32 NonZeroI64:i64 NonZeroI128:i128 NonZeroIsize:isize +); + +impl DefaultFilterable for bool { + type Filtered<'a> = bool; + type Error = Infallible; + + #[inline] + fn as_filtered(&self) -> Result, Infallible> { + match *self { + true => Ok(Some(true)), + false => Ok(None), + } + } +} diff --git a/askama/src/filters/mod.rs b/askama/src/filters/mod.rs index 3a068562..7d7de986 100644 --- a/askama/src/filters/mod.rs +++ b/askama/src/filters/mod.rs @@ -13,6 +13,7 @@ #[cfg(feature = "alloc")] mod alloc; mod core; +mod default; mod escape; mod humansize; mod indent; @@ -28,9 +29,10 @@ pub use self::alloc::{ capitalize, fmt, format, lower, lowercase, title, titlecase, trim, upper, uppercase, }; pub use self::core::{ - DefaultFilterable, Either, PluralizeCount, assigned_or, center, join, linebreaks, linebreaksbr, - paragraphbreaks, pluralize, reject, reject_with, truncate, wordcount, + Either, PluralizeCount, center, join, linebreaks, linebreaksbr, paragraphbreaks, pluralize, + reject, reject_with, truncate, wordcount, }; +pub use self::default::{DefaultFilterable, assigned_or}; pub use self::escape::{ AutoEscape, AutoEscaper, Escaper, Html, HtmlSafe, HtmlSafeOutput, MaybeSafe, Safe, Text, Unsafe, Writable, WriteWritable, e, escape, safe, diff --git a/testing/tests/ui/default.stderr b/testing/tests/ui/default.stderr index 881dc513..ed4a0255 100644 --- a/testing/tests/ui/default.stderr +++ b/testing/tests/ui/default.stderr @@ -81,7 +81,7 @@ error[E0277]: `Mutex` is not `|assigned_or` filterable and $N others = note: required for `&Mutex` to implement `DefaultFilterable` note: required by a bound in `assigned_or` - --> $WORKSPACE/askama/src/filters/core.rs + --> $WORKSPACE/askama/src/filters/default.rs | | pub fn assigned_or( | ^^^^^^^^^^^^^^^^^ required by this bound in `assigned_or`