askama/
lib.rs

1//! [![Crates.io](https://img.shields.io/crates/v/askama?logo=rust&style=flat-square&logoColor=white "Crates.io")](https://crates.io/crates/askama)
2//! [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/askama-rs/askama/rust.yml?branch=master&logo=github&style=flat-square&logoColor=white "GitHub Workflow Status")](https://github.com/askama-rs/askama/actions/workflows/rust.yml)
3//! [![Book](https://img.shields.io/readthedocs/askama?label=book&logo=readthedocs&style=flat-square&logoColor=white "Book")](https://askama.readthedocs.io/)
4//! [![docs.rs](https://img.shields.io/docsrs/askama?logo=docsdotrs&style=flat-square&logoColor=white "docs.rs")](https://docs.rs/askama/)
5//!
6//! Askama implements a type-safe compiler for Jinja-like templates.
7//! It lets you write templates in a Jinja-like syntax,
8//! which are linked to a `struct` or an `enum` defining the template context.
9//! This is done using a custom derive implementation (implemented
10//! in [`askama_derive`](https://crates.io/crates/askama_derive)).
11//!
12//! For feature highlights and a quick start, please review the
13//! [README](https://github.com/askama-rs/askama/blob/master/README.md).
14//!
15//! You can find the documentation about our syntax, features, configuration in our book:
16//! [askama.readthedocs.io](https://askama.readthedocs.io/).
17//!
18//! # Creating Askama templates
19//!
20//! The main feature of Askama is the [`Template`] derive macro
21//! which reads your template code, so your `struct` or `enum` can implement
22//! the [`Template`] trait and [`Display`][std::fmt::Display], type-safe and fast:
23//!
24//! ```rust
25//! # use askama::Template;
26//! #[derive(Template)]
27//! #[template(
28//!     ext = "html",
29//!     source = "<p>© {{ year }} {{ enterprise|upper }}</p>"
30//! )]
31//! struct Footer<'a> {
32//!     year: u16,
33//!     enterprise: &'a str,
34//! }
35//!
36//! assert_eq!(
37//!     Footer { year: 2025, enterprise: "<em>Askama</em> developers" }.to_string(),
38//!     "<p>© 2025 &#60;EM&#62;ASKAMA&#60;/EM&#62; DEVELOPERS</p>",
39//! );
40//! // In here you see can Askama's auto-escaping. You, the developer,
41//! // can easily disable the auto-escaping with the `|safe` filter,
42//! // but a malicious user cannot insert e.g. HTML scripts this way.
43//! ```
44//!
45//! An Askama template is a `struct` or `enum` definition which provides the template
46//! context combined with a UTF-8 encoded text file (or inline source).
47//! Askama can be used to generate any kind of text-based format.
48//! The template file's extension may be used to provide content type hints.
49//!
50//! A template consists of **text contents**, which are passed through as-is,
51//! **expressions**, which get replaced with content while being rendered, and
52//! **tags**, which control the template's logic.
53//! The template syntax is very similar to [Jinja](http://jinja.pocoo.org/),
54//! as well as Jinja-derivatives like [Twig](https://twig.symfony.com/) or
55//! [Tera](https://github.com/Keats/tera).
56
57#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
58#![deny(elided_lifetimes_in_paths)]
59#![deny(unreachable_pub)]
60#![deny(missing_docs)]
61#![no_std]
62
63#[cfg(feature = "alloc")]
64extern crate alloc;
65#[cfg(feature = "std")]
66extern crate std;
67
68mod ascii_str;
69mod error;
70pub mod filters;
71#[doc(hidden)]
72pub mod helpers;
73mod html;
74mod values;
75
76#[cfg(feature = "alloc")]
77use alloc::string::String;
78use core::fmt;
79use core::ops::Deref;
80#[cfg(feature = "std")]
81use std::io;
82
83#[cfg(feature = "derive")]
84pub use askama_derive::Template;
85
86#[doc(hidden)]
87pub use crate as shared;
88pub use crate::error::{Error, Result};
89pub use crate::helpers::PrimitiveType;
90pub use crate::values::{NO_VALUES, Value, Values, get_value};
91
92/// Main `Template` trait; implementations are generally derived
93///
94/// If you need an object-safe template, use [`DynTemplate`].
95///
96/// ## Rendering performance
97///
98/// When rendering a askama template, you should prefer the methods
99///
100/// * [`.render()`][Template::render] (to render the content into a new string),
101/// * [`.render_into()`][Template::render_into] (to render the content into an [`fmt::Write`]
102///   object, e.g. [`String`]) or
103/// * [`.write_into()`][Template::write_into] (to render the content into an [`io::Write`] object,
104///   e.g. [`Vec<u8>`][alloc::vec::Vec])
105///
106/// over [`.to_string()`][std::string::ToString::to_string] or [`format!()`][alloc::format].
107/// While `.to_string()` and `format!()` give you the same result, they generally perform much worse
108/// than askama's own methods, because [`fmt::Write`] uses [dynamic methods calls] instead of
109/// monomorphised code. On average, expect `.to_string()` to be 100% to 200% slower than
110/// `.render()`.
111///
112/// [dynamic methods calls]: <https://doc.rust-lang.org/stable/std/keyword.dyn.html>
113pub trait Template: fmt::Display + FastWritable {
114    /// Helper method which allocates a new `String` and renders into it.
115    #[inline]
116    #[cfg(feature = "alloc")]
117    fn render(&self) -> Result<String> {
118        self.render_with_values(NO_VALUES)
119    }
120
121    /// Helper method which allocates a new `String` and renders into it with provided [`Values`].
122    #[inline]
123    #[cfg(feature = "alloc")]
124    fn render_with_values(&self, values: &dyn Values) -> Result<String> {
125        let mut buf = String::new();
126        let _ = buf.try_reserve(Self::SIZE_HINT);
127        self.render_into_with_values(&mut buf, values)?;
128        Ok(buf)
129    }
130
131    /// Renders the template to the given `writer` fmt buffer.
132    #[inline]
133    fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
134        self.render_into_with_values(writer, NO_VALUES)
135    }
136
137    /// Renders the template to the given `writer` fmt buffer with provided [`Values`].
138    fn render_into_with_values<W: fmt::Write + ?Sized>(
139        &self,
140        writer: &mut W,
141        values: &dyn Values,
142    ) -> Result<()>;
143
144    /// Renders the template to the given `writer` io buffer.
145    #[inline]
146    #[cfg(feature = "std")]
147    fn write_into<W: io::Write + ?Sized>(&self, writer: &mut W) -> io::Result<()> {
148        self.write_into_with_values(writer, NO_VALUES)
149    }
150
151    /// Renders the template to the given `writer` io buffer with provided [`Values`].
152    #[cfg(feature = "std")]
153    fn write_into_with_values<W: io::Write + ?Sized>(
154        &self,
155        writer: &mut W,
156        values: &dyn Values,
157    ) -> io::Result<()> {
158        struct Wrapped<W: io::Write> {
159            writer: W,
160            err: Option<io::Error>,
161        }
162
163        impl<W: io::Write> fmt::Write for Wrapped<W> {
164            fn write_str(&mut self, s: &str) -> fmt::Result {
165                if let Err(err) = self.writer.write_all(s.as_bytes()) {
166                    self.err = Some(err);
167                    Err(fmt::Error)
168                } else {
169                    Ok(())
170                }
171            }
172        }
173
174        let mut wrapped = Wrapped { writer, err: None };
175        if self.render_into_with_values(&mut wrapped, values).is_ok() {
176            Ok(())
177        } else {
178            let err = wrapped.err.take();
179            Err(err.unwrap_or_else(|| io::Error::new(io::ErrorKind::Other, fmt::Error)))
180        }
181    }
182
183    /// Provides a rough estimate of the expanded length of the rendered template. Larger
184    /// values result in higher memory usage but fewer reallocations. Smaller values result in the
185    /// opposite. This value only affects [`render`]. It does not take effect when calling
186    /// [`render_into`], [`write_into`], the [`fmt::Display`] implementation, or the blanket
187    /// [`ToString::to_string`] implementation.
188    ///
189    /// [`render`]: Template::render
190    /// [`render_into`]: Template::render_into
191    /// [`write_into`]: Template::write_into
192    /// [`ToString::to_string`]: alloc::string::ToString::to_string
193    const SIZE_HINT: usize;
194}
195
196impl<T: Template + ?Sized> Template for &T {
197    #[inline]
198    #[cfg(feature = "alloc")]
199    fn render(&self) -> Result<String> {
200        <T as Template>::render(self)
201    }
202
203    #[inline]
204    #[cfg(feature = "alloc")]
205    fn render_with_values(&self, values: &dyn Values) -> Result<String> {
206        <T as Template>::render_with_values(self, values)
207    }
208
209    #[inline]
210    fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
211        <T as Template>::render_into(self, writer)
212    }
213
214    #[inline]
215    fn render_into_with_values<W: fmt::Write + ?Sized>(
216        &self,
217        writer: &mut W,
218        values: &dyn Values,
219    ) -> Result<()> {
220        <T as Template>::render_into_with_values(self, writer, values)
221    }
222
223    #[inline]
224    #[cfg(feature = "std")]
225    fn write_into<W: io::Write + ?Sized>(&self, writer: &mut W) -> io::Result<()> {
226        <T as Template>::write_into(self, writer)
227    }
228
229    #[inline]
230    #[cfg(feature = "std")]
231    fn write_into_with_values<W: io::Write + ?Sized>(
232        &self,
233        writer: &mut W,
234        values: &dyn Values,
235    ) -> io::Result<()> {
236        <T as Template>::write_into_with_values(self, writer, values)
237    }
238
239    const SIZE_HINT: usize = T::SIZE_HINT;
240}
241
242/// [`dyn`-compatible] wrapper trait around [`Template`] implementers
243///
244/// This trades reduced performance (mostly due to writing into `dyn Write`) for dyn-compatibility.
245///
246/// [`dyn`-compatible]: https://doc.rust-lang.org/stable/reference/items/traits.html#dyn-compatibility
247pub trait DynTemplate {
248    /// Helper method which allocates a new `String` and renders into it.
249    #[cfg(feature = "alloc")]
250    fn dyn_render(&self) -> Result<String>;
251
252    /// Helper method which allocates a new `String` and renders into it with provided [`Values`].
253    #[cfg(feature = "alloc")]
254    fn dyn_render_with_values(&self, values: &dyn Values) -> Result<String>;
255
256    /// Renders the template to the given `writer` fmt buffer.
257    fn dyn_render_into(&self, writer: &mut dyn fmt::Write) -> Result<()>;
258
259    /// Renders the template to the given `writer` fmt buffer with provided [`Values`].
260    fn dyn_render_into_with_values(
261        &self,
262        writer: &mut dyn fmt::Write,
263        values: &dyn Values,
264    ) -> Result<()>;
265
266    /// Renders the template to the given `writer` io buffer.
267    #[cfg(feature = "std")]
268    fn dyn_write_into(&self, writer: &mut dyn io::Write) -> io::Result<()>;
269
270    /// Renders the template to the given `writer` io buffer with provided [`Values`].
271    #[cfg(feature = "std")]
272    fn dyn_write_into_with_values(
273        &self,
274        writer: &mut dyn io::Write,
275        values: &dyn Values,
276    ) -> io::Result<()>;
277
278    /// Provides a conservative estimate of the expanded length of the rendered template.
279    fn size_hint(&self) -> usize;
280}
281
282impl<T: Template> DynTemplate for T {
283    #[inline]
284    #[cfg(feature = "alloc")]
285    fn dyn_render(&self) -> Result<String> {
286        <Self as Template>::render(self)
287    }
288
289    #[cfg(feature = "alloc")]
290    fn dyn_render_with_values(&self, values: &dyn Values) -> Result<String> {
291        <Self as Template>::render_with_values(self, values)
292    }
293
294    #[inline]
295    fn dyn_render_into(&self, writer: &mut dyn fmt::Write) -> Result<()> {
296        <Self as Template>::render_into(self, writer)
297    }
298
299    fn dyn_render_into_with_values(
300        &self,
301        writer: &mut dyn fmt::Write,
302        values: &dyn Values,
303    ) -> Result<()> {
304        <Self as Template>::render_into_with_values(self, writer, values)
305    }
306
307    #[cfg(feature = "std")]
308    fn dyn_write_into(&self, writer: &mut dyn io::Write) -> io::Result<()> {
309        <Self as Template>::write_into(self, writer)
310    }
311
312    #[inline]
313    #[cfg(feature = "std")]
314    fn dyn_write_into_with_values(
315        &self,
316        writer: &mut dyn io::Write,
317        values: &dyn Values,
318    ) -> io::Result<()> {
319        <Self as Template>::write_into_with_values(self, writer, values)
320    }
321
322    #[inline]
323    fn size_hint(&self) -> usize {
324        <Self as Template>::SIZE_HINT
325    }
326}
327
328impl fmt::Display for dyn DynTemplate {
329    #[inline]
330    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331        self.dyn_render_into(f).map_err(|_| fmt::Error {})
332    }
333}
334
335/// Implement the trait `$Trait` for a list of reference (wrapper) types to `$T: $Trait + ?Sized`
336macro_rules! impl_for_ref {
337    (impl $Trait:ident for $T:ident $body:tt) => {
338        const _: () = {
339            crate::impl_for_ref! {
340                impl<$T> $Trait for [
341                    &T
342                    &mut T
343                    core::cell::Ref<'_, T>
344                    core::cell::RefMut<'_, T>
345                ] $body
346            }
347        };
348        #[cfg(feature = "alloc")]
349        const _: () = {
350            crate::impl_for_ref! {
351                impl<$T> $Trait for [
352                    alloc::boxed::Box<T>
353                    alloc::rc::Rc<T>
354                    alloc::sync::Arc<T>
355                ] $body
356            }
357        };
358        #[cfg(feature = "std")]
359        const _: () = {
360            crate::impl_for_ref! {
361                impl<$T> $Trait for [
362                    std::sync::MutexGuard<'_, T>
363                    std::sync::RwLockReadGuard<'_, T>
364                    std::sync::RwLockWriteGuard<'_, T>
365                ] $body
366            }
367        };
368    };
369    (impl<$T:ident> $Trait:ident for [$($ty:ty)*] $body:tt) => {
370        $(impl<$T: $Trait + ?Sized> $Trait for $ty $body)*
371    }
372}
373
374/// Types implementing this trait can be written without needing to employ an [`fmt::Formatter`].
375pub trait FastWritable {
376    /// Used internally by askama to speed up writing some types.
377    fn write_into<W: fmt::Write + ?Sized>(
378        &self,
379        dest: &mut W,
380        values: &dyn Values,
381    ) -> crate::Result<()>;
382}
383
384const _: () = {
385    crate::impl_for_ref! {
386        impl FastWritable for T {
387            #[inline]
388            fn write_into<W: fmt::Write + ?Sized>(
389                &self,
390                dest: &mut W,
391                values: &dyn Values,
392            ) -> crate::Result<()> {
393                <T>::write_into(self, dest, values)
394            }
395        }
396    }
397
398    impl<T> FastWritable for core::pin::Pin<T>
399    where
400        T: Deref,
401        <T as Deref>::Target: FastWritable,
402    {
403        #[inline]
404        fn write_into<W: fmt::Write + ?Sized>(
405            &self,
406            dest: &mut W,
407            values: &dyn Values,
408        ) -> crate::Result<()> {
409            self.as_ref().get_ref().write_into(dest, values)
410        }
411    }
412
413    #[cfg(feature = "alloc")]
414    impl<T: FastWritable + alloc::borrow::ToOwned> FastWritable for alloc::borrow::Cow<'_, T> {
415        #[inline]
416        fn write_into<W: fmt::Write + ?Sized>(
417            &self,
418            dest: &mut W,
419            values: &dyn Values,
420        ) -> crate::Result<()> {
421            T::write_into(self.as_ref(), dest, values)
422        }
423    }
424
425    // implement FastWritable for a list of types
426    macro_rules! impl_for_int {
427        ($($ty:ty)*) => { $(
428            impl FastWritable for $ty {
429                #[inline]
430                fn write_into<W: fmt::Write + ?Sized>(
431                    &self,
432                    dest: &mut W,
433                    values: &dyn Values,
434                ) -> crate::Result<()> {
435                    itoa::Buffer::new().format(*self).write_into(dest, values)
436                }
437            }
438        )* };
439    }
440
441    impl_for_int!(
442        u8 u16 u32 u64 u128 usize
443        i8 i16 i32 i64 i128 isize
444    );
445
446    // implement FastWritable for a list of non-zero integral types
447    macro_rules! impl_for_nz_int {
448        ($($id:ident)*) => { $(
449            impl FastWritable for core::num::$id {
450                #[inline]
451                fn write_into<W: fmt::Write + ?Sized>(
452                    &self,
453                    dest: &mut W,
454                    values: &dyn Values,
455                ) -> crate::Result<()> {
456                    self.get().write_into(dest, values)
457                }
458            }
459        )* };
460    }
461
462    impl_for_nz_int!(
463        NonZeroU8 NonZeroU16 NonZeroU32 NonZeroU64 NonZeroU128 NonZeroUsize
464        NonZeroI8 NonZeroI16 NonZeroI32 NonZeroI64 NonZeroI128 NonZeroIsize
465    );
466
467    impl FastWritable for str {
468        #[inline]
469        fn write_into<W: fmt::Write + ?Sized>(
470            &self,
471            dest: &mut W,
472            _: &dyn Values,
473        ) -> crate::Result<()> {
474            Ok(dest.write_str(self)?)
475        }
476    }
477
478    #[cfg(feature = "alloc")]
479    impl FastWritable for alloc::string::String {
480        #[inline]
481        fn write_into<W: fmt::Write + ?Sized>(
482            &self,
483            dest: &mut W,
484            values: &dyn Values,
485        ) -> crate::Result<()> {
486            self.as_str().write_into(dest, values)
487        }
488    }
489
490    impl FastWritable for bool {
491        #[inline]
492        fn write_into<W: fmt::Write + ?Sized>(
493            &self,
494            dest: &mut W,
495            _: &dyn Values,
496        ) -> crate::Result<()> {
497            Ok(dest.write_str(match self {
498                true => "true",
499                false => "false",
500            })?)
501        }
502    }
503
504    impl FastWritable for char {
505        #[inline]
506        fn write_into<W: fmt::Write + ?Sized>(
507            &self,
508            dest: &mut W,
509            _: &dyn Values,
510        ) -> crate::Result<()> {
511            Ok(dest.write_char(*self)?)
512        }
513    }
514
515    impl FastWritable for fmt::Arguments<'_> {
516        fn write_into<W: fmt::Write + ?Sized>(
517            &self,
518            dest: &mut W,
519            _: &dyn Values,
520        ) -> crate::Result<()> {
521            Ok(match self.as_str() {
522                Some(s) => dest.write_str(s),
523                None => dest.write_fmt(*self),
524            }?)
525        }
526    }
527
528    impl<S: crate::Template + ?Sized> filters::WriteWritable for &filters::Writable<'_, S> {
529        #[inline]
530        fn askama_write<W: fmt::Write + ?Sized>(
531            &self,
532            dest: &mut W,
533            values: &dyn Values,
534        ) -> crate::Result<()> {
535            self.0.render_into_with_values(dest, values)
536        }
537    }
538
539    impl<S: FastWritable + ?Sized> filters::WriteWritable for &&filters::Writable<'_, S> {
540        #[inline]
541        fn askama_write<W: fmt::Write + ?Sized>(
542            &self,
543            dest: &mut W,
544            values: &dyn Values,
545        ) -> crate::Result<()> {
546            self.0.write_into(dest, values)
547        }
548    }
549
550    impl<S: fmt::Display + ?Sized> filters::WriteWritable for &&&filters::Writable<'_, S> {
551        #[inline]
552        fn askama_write<W: fmt::Write + ?Sized>(
553            &self,
554            dest: &mut W,
555            _: &dyn Values,
556        ) -> crate::Result<()> {
557            Ok(write!(dest, "{}", self.0)?)
558        }
559    }
560};
561
562pub(crate) use impl_for_ref;
563
564#[cfg(all(test, feature = "alloc"))]
565mod tests {
566    use std::fmt;
567
568    use super::*;
569    use crate::{DynTemplate, Template};
570
571    #[test]
572    fn dyn_template() {
573        use alloc::string::ToString;
574
575        struct Test;
576
577        impl Template for Test {
578            fn render_into_with_values<W: fmt::Write + ?Sized>(
579                &self,
580                writer: &mut W,
581                _values: &dyn Values,
582            ) -> Result<()> {
583                Ok(writer.write_str("test")?)
584            }
585
586            const SIZE_HINT: usize = 4;
587        }
588
589        impl fmt::Display for Test {
590            #[inline]
591            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
592                self.render_into(f).map_err(|_| fmt::Error {})
593            }
594        }
595
596        impl FastWritable for Test {
597            #[inline]
598            fn write_into<W: fmt::Write + ?Sized>(
599                &self,
600                f: &mut W,
601                values: &dyn Values,
602            ) -> crate::Result<()> {
603                self.render_into_with_values(f, values)
604            }
605        }
606
607        fn render(t: &dyn DynTemplate) -> String {
608            t.dyn_render().unwrap()
609        }
610
611        let test = &Test as &dyn DynTemplate;
612
613        assert_eq!(render(test), "test");
614
615        assert_eq!(test.to_string(), "test");
616
617        assert_eq!(alloc::format!("{test}"), "test");
618
619        let mut vec = alloc::vec![];
620        test.dyn_write_into(&mut vec).unwrap();
621        assert_eq!(vec, alloc::vec![b't', b'e', b's', b't']);
622    }
623}