Skip to content

Commit 67620f4

Browse files
committed
Add filter |default
Also, `enum Pluralize<S, P>` is renamed into `enum Either<L, R>` and exported.
1 parent 73ba176 commit 67620f4

File tree

7 files changed

+744
-16
lines changed

7 files changed

+744
-16
lines changed

askama/src/filters/builtin.rs

Lines changed: 250 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use core::cell::Cell;
22
use core::convert::Infallible;
33
use core::fmt::{self, Write};
4+
use core::num::{Saturating, Wrapping};
45
use core::ops::Deref;
56
use core::pin::Pin;
67

78
use super::MAX_LEN;
8-
use crate::{Error, FastWritable, Result, Values};
9+
use crate::{Error, FastWritable, Result, Values, impl_for_ref};
910

1011
/// Limit string length, appends '...' if truncated
1112
///
@@ -330,13 +331,13 @@ impl<T: fmt::Display> fmt::Display for Center<T> {
330331
/// # }
331332
/// ```
332333
#[inline]
333-
pub fn pluralize<C, S, P>(count: C, singular: S, plural: P) -> Result<Pluralize<S, P>, C::Error>
334+
pub fn pluralize<C, S, P>(count: C, singular: S, plural: P) -> Result<Either<S, P>, C::Error>
334335
where
335336
C: PluralizeCount,
336337
{
337338
match count.is_singular()? {
338-
true => Ok(Pluralize::Singular(singular)),
339-
false => Ok(Pluralize::Plural(plural)),
339+
true => Ok(Either::Left(singular)),
340+
false => Ok(Either::Right(plural)),
340341
}
341342
}
342343

@@ -426,31 +427,267 @@ const _: () = {
426427
}
427428
};
428429

429-
pub enum Pluralize<S, P> {
430-
Singular(S),
431-
Plural(P),
430+
/// Render `default` if `value` is not the default value, see [`DefaultFilterable`].
431+
///
432+
/// This function is only used if the optional `boolean` argument to `|default` was true.
433+
/// Otherwise `value` is used directly.
434+
#[inline]
435+
pub fn default<L: DefaultFilterable, R>(
436+
value: &L,
437+
default: R,
438+
) -> Result<Either<L::Filtered<'_>, R>, L::Error> {
439+
match value.as_filtered()? {
440+
Some(value) => Ok(Either::Left(value)),
441+
None => Ok(Either::Right(default)),
442+
}
443+
}
444+
445+
/// A type (or a reference to it) that can be used in [`|default`](default).
446+
///
447+
/// The type is either a monad such as [`Option`] or [`Result`], or a type that has a well defined,
448+
/// trivial default value, e.g. an [empty](str::is_empty) [`str`] or `0` for integer types.
449+
#[diagnostic::on_unimplemented(
450+
label = "`{Self}` is not `|default` filterable",
451+
message = "`{Self}` is not `|default` filterable"
452+
)]
453+
pub trait DefaultFilterable {
454+
/// The contained value
455+
type Filtered<'a>
456+
where
457+
Self: 'a;
458+
459+
/// An error that prevented [`as_filtered()`](DefaultFilterable::as_filtered) to succeed,
460+
/// e.g. a poisoned state or an unacquirable lock.
461+
type Error: Into<crate::Error>;
462+
463+
/// Return the contained value, if a value was contained, and it's not the default value.
464+
///
465+
/// Returns `Ok(None)` if the value could not be unwrapped.
466+
fn as_filtered(&self) -> Result<Option<Self::Filtered<'_>>, Self::Error>;
467+
}
468+
469+
const _: () = {
470+
impl_for_ref! {
471+
impl DefaultFilterable for T {
472+
type Filtered<'a> = T::Filtered<'a>
473+
where
474+
Self: 'a;
475+
476+
type Error = T::Error;
477+
478+
#[inline]
479+
fn as_filtered(&self) -> Result<Option<Self::Filtered<'_>>, Self::Error> {
480+
<T>::as_filtered(self)
481+
}
482+
}
483+
}
484+
485+
impl<T> DefaultFilterable for Pin<T>
486+
where
487+
T: Deref,
488+
<T as Deref>::Target: DefaultFilterable,
489+
{
490+
type Filtered<'a>
491+
= <<T as Deref>::Target as DefaultFilterable>::Filtered<'a>
492+
where
493+
Self: 'a;
494+
495+
type Error = <<T as Deref>::Target as DefaultFilterable>::Error;
496+
497+
#[inline]
498+
fn as_filtered(&self) -> Result<Option<Self::Filtered<'_>>, Self::Error> {
499+
self.as_ref().get_ref().as_filtered()
500+
}
501+
}
502+
503+
impl<T> DefaultFilterable for Option<T> {
504+
type Filtered<'a>
505+
= &'a T
506+
where
507+
Self: 'a;
508+
509+
type Error = Infallible;
510+
511+
#[inline]
512+
fn as_filtered(&self) -> Result<Option<&T>, Infallible> {
513+
Ok(self.as_ref())
514+
}
515+
}
516+
517+
impl<T, E> DefaultFilterable for Result<T, E> {
518+
type Filtered<'a>
519+
= &'a T
520+
where
521+
Self: 'a;
522+
523+
type Error = Infallible;
524+
525+
#[inline]
526+
fn as_filtered(&self) -> Result<Option<&T>, Infallible> {
527+
Ok(self.as_ref().ok())
528+
}
529+
}
530+
531+
impl DefaultFilterable for str {
532+
type Filtered<'a>
533+
= &'a str
534+
where
535+
Self: 'a;
536+
537+
type Error = Infallible;
538+
539+
#[inline]
540+
fn as_filtered(&self) -> Result<Option<&str>, Infallible> {
541+
match self.is_empty() {
542+
false => Ok(Some(self)),
543+
true => Ok(None),
544+
}
545+
}
546+
}
547+
548+
#[cfg(feature = "alloc")]
549+
impl DefaultFilterable for alloc::string::String {
550+
type Filtered<'a>
551+
= &'a str
552+
where
553+
Self: 'a;
554+
555+
type Error = Infallible;
556+
557+
#[inline]
558+
fn as_filtered(&self) -> Result<Option<&str>, Infallible> {
559+
self.as_str().as_filtered()
560+
}
561+
}
562+
563+
#[cfg(feature = "alloc")]
564+
impl<T: DefaultFilterable + alloc::borrow::ToOwned + ?Sized> DefaultFilterable
565+
for alloc::borrow::Cow<'_, T>
566+
{
567+
type Filtered<'a>
568+
= T::Filtered<'a>
569+
where
570+
Self: 'a;
571+
572+
type Error = T::Error;
573+
574+
#[inline]
575+
fn as_filtered(&self) -> Result<Option<Self::Filtered<'_>>, Self::Error> {
576+
self.as_ref().as_filtered()
577+
}
578+
}
579+
580+
impl<T: DefaultFilterable> DefaultFilterable for Wrapping<T> {
581+
type Filtered<'a>
582+
= T::Filtered<'a>
583+
where
584+
Self: 'a;
585+
586+
type Error = T::Error;
587+
588+
#[inline]
589+
fn as_filtered(&self) -> Result<Option<Self::Filtered<'_>>, Self::Error> {
590+
self.0.as_filtered()
591+
}
592+
}
593+
594+
impl<T: DefaultFilterable> DefaultFilterable for Saturating<T> {
595+
type Filtered<'a>
596+
= T::Filtered<'a>
597+
where
598+
Self: 'a;
599+
600+
type Error = T::Error;
601+
602+
#[inline]
603+
fn as_filtered(&self) -> Result<Option<Self::Filtered<'_>>, Self::Error> {
604+
self.0.as_filtered()
605+
}
606+
}
607+
608+
macro_rules! impl_for_int {
609+
($($ty:ty)*) => { $(
610+
impl DefaultFilterable for $ty {
611+
type Filtered<'a> = $ty;
612+
type Error = Infallible;
613+
614+
#[inline]
615+
fn as_filtered(&self) -> Result<Option<$ty>, Infallible> {
616+
match *self {
617+
0 => Ok(None),
618+
value => Ok(Some(value)),
619+
}
620+
}
621+
}
622+
)* };
623+
}
624+
625+
impl_for_int!(
626+
u8 u16 u32 u64 u128 usize
627+
i8 i16 i32 i64 i128 isize
628+
);
629+
630+
macro_rules! impl_for_non_zero {
631+
($($name:ident : $ty:ty)*) => { $(
632+
impl DefaultFilterable for core::num::$name {
633+
type Filtered<'a> = $ty;
634+
type Error = Infallible;
635+
636+
#[inline]
637+
fn as_filtered(&self) -> Result<Option<$ty>, Infallible> {
638+
Ok(Some(self.get()))
639+
}
640+
}
641+
)* };
642+
}
643+
644+
impl_for_non_zero!(
645+
NonZeroU8:u8 NonZeroU16:u16 NonZeroU32:u32 NonZeroU64:u64 NonZeroU128:u128 NonZeroUsize:usize
646+
NonZeroI8:i8 NonZeroI16:i16 NonZeroI32:i32 NonZeroI64:i64 NonZeroI128:i128 NonZeroIsize:isize
647+
);
648+
649+
impl DefaultFilterable for bool {
650+
type Filtered<'a> = bool;
651+
type Error = Infallible;
652+
653+
#[inline]
654+
fn as_filtered(&self) -> Result<Option<bool>, Infallible> {
655+
match *self {
656+
true => Ok(Some(true)),
657+
false => Ok(None),
658+
}
659+
}
660+
}
661+
};
662+
663+
/// Render either `L` or `R`
664+
pub enum Either<L, R> {
665+
/// First variant
666+
Left(L),
667+
/// Second variant
668+
Right(R),
432669
}
433670

434-
impl<S: fmt::Display, P: fmt::Display> fmt::Display for Pluralize<S, P> {
671+
impl<L: fmt::Display, R: fmt::Display> fmt::Display for Either<L, R> {
435672
#[inline]
436673
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437674
match self {
438-
Pluralize::Singular(value) => write!(f, "{value}"),
439-
Pluralize::Plural(value) => write!(f, "{value}"),
675+
Either::Left(value) => write!(f, "{value}"),
676+
Either::Right(value) => write!(f, "{value}"),
440677
}
441678
}
442679
}
443680

444-
impl<S: FastWritable, P: FastWritable> FastWritable for Pluralize<S, P> {
681+
impl<L: FastWritable, R: FastWritable> FastWritable for Either<L, R> {
445682
#[inline]
446683
fn write_into<W: fmt::Write + ?Sized>(
447684
&self,
448685
dest: &mut W,
449686
values: &dyn Values,
450687
) -> crate::Result<()> {
451688
match self {
452-
Pluralize::Singular(value) => value.write_into(dest, values),
453-
Pluralize::Plural(value) => value.write_into(dest, values),
689+
Either::Left(value) => value.write_into(dest, values),
690+
Either::Right(value) => value.write_into(dest, values),
454691
}
455692
}
456693
}

askama/src/filters/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ pub use self::alloc::{
2727
AsIndent, capitalize, fmt, format, indent, linebreaks, linebreaksbr, lower, lowercase,
2828
paragraphbreaks, title, titlecase, trim, upper, uppercase, wordcount,
2929
};
30-
pub use self::builtin::{PluralizeCount, center, join, pluralize, truncate};
30+
pub use self::builtin::{
31+
DefaultFilterable, Either, PluralizeCount, center, default, join, pluralize, truncate,
32+
};
3133
pub use self::escape::{
3234
AutoEscape, AutoEscaper, Escaper, Html, HtmlSafe, HtmlSafeOutput, MaybeSafe, Safe, Text,
3335
Unsafe, Writable, WriteWritable, e, escape, safe,

0 commit comments

Comments
 (0)