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;
79#[cfg(feature = "std")]
80use std::io;
81
82#[cfg(feature = "derive")]
83pub use askama_derive::Template;
84
85#[doc(hidden)]
86pub use crate as shared;
87pub use crate::error::{Error, Result};
88pub use crate::helpers::PrimitiveType;
89pub use crate::values::{NO_VALUES, Value, Values, get_value};
90
91/// Main `Template` trait; implementations are generally derived
92///
93/// If you need an object-safe template, use [`DynTemplate`].
94///
95/// ## Rendering performance
96///
97/// When rendering a askama template, you should prefer the methods
98///
99/// * [`.render()`][Template::render] (to render the content into a new string),
100/// * [`.render_into()`][Template::render_into] (to render the content into an [`fmt::Write`]
101///   object, e.g. [`String`]) or
102/// * [`.write_into()`][Template::write_into] (to render the content into an [`io::Write`] object,
103///   e.g. [`Vec<u8>`][alloc::vec::Vec])
104///
105/// over [`.to_string()`][std::string::ToString::to_string] or [`format!()`][alloc::format].
106/// While `.to_string()` and `format!()` give you the same result, they generally perform much worse
107/// than askama's own methods, because [`fmt::Write`] uses [dynamic methods calls] instead of
108/// monomorphised code. On average, expect `.to_string()` to be 100% to 200% slower than
109/// `.render()`.
110///
111/// [dynamic methods calls]: <https://doc.rust-lang.org/stable/std/keyword.dyn.html>
112pub trait Template: fmt::Display + filters::FastWritable {
113    /// Helper method which allocates a new `String` and renders into it.
114    #[inline]
115    #[cfg(feature = "alloc")]
116    fn render(&self) -> Result<String> {
117        self.render_with_values(NO_VALUES)
118    }
119
120    /// Helper method which allocates a new `String` and renders into it with provided [`Values`].
121    #[inline]
122    #[cfg(feature = "alloc")]
123    fn render_with_values(&self, values: &dyn Values) -> Result<String> {
124        let mut buf = String::new();
125        let _ = buf.try_reserve(Self::SIZE_HINT);
126        self.render_into_with_values(&mut buf, values)?;
127        Ok(buf)
128    }
129
130    /// Renders the template to the given `writer` fmt buffer.
131    #[inline]
132    fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
133        self.render_into_with_values(writer, NO_VALUES)
134    }
135
136    /// Renders the template to the given `writer` fmt buffer with provided [`Values`].
137    fn render_into_with_values<W: fmt::Write + ?Sized>(
138        &self,
139        writer: &mut W,
140        values: &dyn Values,
141    ) -> Result<()>;
142
143    /// Renders the template to the given `writer` io buffer.
144    #[inline]
145    #[cfg(feature = "std")]
146    fn write_into<W: io::Write + ?Sized>(&self, writer: &mut W) -> io::Result<()> {
147        self.write_into_with_values(writer, NO_VALUES)
148    }
149
150    /// Renders the template to the given `writer` io buffer with provided [`Values`].
151    #[cfg(feature = "std")]
152    fn write_into_with_values<W: io::Write + ?Sized>(
153        &self,
154        writer: &mut W,
155        values: &dyn Values,
156    ) -> io::Result<()> {
157        struct Wrapped<W: io::Write> {
158            writer: W,
159            err: Option<io::Error>,
160        }
161
162        impl<W: io::Write> fmt::Write for Wrapped<W> {
163            fn write_str(&mut self, s: &str) -> fmt::Result {
164                if let Err(err) = self.writer.write_all(s.as_bytes()) {
165                    self.err = Some(err);
166                    Err(fmt::Error)
167                } else {
168                    Ok(())
169                }
170            }
171        }
172
173        let mut wrapped = Wrapped { writer, err: None };
174        if self.render_into_with_values(&mut wrapped, values).is_ok() {
175            Ok(())
176        } else {
177            let err = wrapped.err.take();
178            Err(err.unwrap_or_else(|| io::Error::new(io::ErrorKind::Other, fmt::Error)))
179        }
180    }
181
182    /// Provides a rough estimate of the expanded length of the rendered template. Larger
183    /// values result in higher memory usage but fewer reallocations. Smaller values result in the
184    /// opposite. This value only affects [`render`]. It does not take effect when calling
185    /// [`render_into`], [`write_into`], the [`fmt::Display`] implementation, or the blanket
186    /// [`ToString::to_string`] implementation.
187    ///
188    /// [`render`]: Template::render
189    /// [`render_into`]: Template::render_into
190    /// [`write_into`]: Template::write_into
191    /// [`ToString::to_string`]: alloc::string::ToString::to_string
192    const SIZE_HINT: usize;
193}
194
195impl<T: Template + ?Sized> Template for &T {
196    #[inline]
197    #[cfg(feature = "alloc")]
198    fn render(&self) -> Result<String> {
199        <T as Template>::render(self)
200    }
201
202    #[inline]
203    #[cfg(feature = "alloc")]
204    fn render_with_values(&self, values: &dyn Values) -> Result<String> {
205        <T as Template>::render_with_values(self, values)
206    }
207
208    #[inline]
209    fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
210        <T as Template>::render_into(self, writer)
211    }
212
213    #[inline]
214    fn render_into_with_values<W: fmt::Write + ?Sized>(
215        &self,
216        writer: &mut W,
217        values: &dyn Values,
218    ) -> Result<()> {
219        <T as Template>::render_into_with_values(self, writer, values)
220    }
221
222    #[inline]
223    #[cfg(feature = "std")]
224    fn write_into<W: io::Write + ?Sized>(&self, writer: &mut W) -> io::Result<()> {
225        <T as Template>::write_into(self, writer)
226    }
227
228    #[inline]
229    #[cfg(feature = "std")]
230    fn write_into_with_values<W: io::Write + ?Sized>(
231        &self,
232        writer: &mut W,
233        values: &dyn Values,
234    ) -> io::Result<()> {
235        <T as Template>::write_into_with_values(self, writer, values)
236    }
237
238    const SIZE_HINT: usize = T::SIZE_HINT;
239}
240
241/// [`dyn`-compatible] wrapper trait around [`Template`] implementers
242///
243/// This trades reduced performance (mostly due to writing into `dyn Write`) for dyn-compatibility.
244///
245/// [`dyn`-compatible]: https://doc.rust-lang.org/stable/reference/items/traits.html#dyn-compatibility
246pub trait DynTemplate {
247    /// Helper method which allocates a new `String` and renders into it.
248    #[cfg(feature = "alloc")]
249    fn dyn_render(&self) -> Result<String>;
250
251    /// Helper method which allocates a new `String` and renders into it with provided [`Values`].
252    #[cfg(feature = "alloc")]
253    fn dyn_render_with_values(&self, values: &dyn Values) -> Result<String>;
254
255    /// Renders the template to the given `writer` fmt buffer.
256    fn dyn_render_into(&self, writer: &mut dyn fmt::Write) -> Result<()>;
257
258    /// Renders the template to the given `writer` fmt buffer with provided [`Values`].
259    fn dyn_render_into_with_values(
260        &self,
261        writer: &mut dyn fmt::Write,
262        values: &dyn Values,
263    ) -> Result<()>;
264
265    /// Renders the template to the given `writer` io buffer.
266    #[cfg(feature = "std")]
267    fn dyn_write_into(&self, writer: &mut dyn io::Write) -> io::Result<()>;
268
269    /// Renders the template to the given `writer` io buffer with provided [`Values`].
270    #[cfg(feature = "std")]
271    fn dyn_write_into_with_values(
272        &self,
273        writer: &mut dyn io::Write,
274        values: &dyn Values,
275    ) -> io::Result<()>;
276
277    /// Provides a conservative estimate of the expanded length of the rendered template.
278    fn size_hint(&self) -> usize;
279}
280
281impl<T: Template> DynTemplate for T {
282    #[inline]
283    #[cfg(feature = "alloc")]
284    fn dyn_render(&self) -> Result<String> {
285        <Self as Template>::render(self)
286    }
287
288    #[cfg(feature = "alloc")]
289    fn dyn_render_with_values(&self, values: &dyn Values) -> Result<String> {
290        <Self as Template>::render_with_values(self, values)
291    }
292
293    #[inline]
294    fn dyn_render_into(&self, writer: &mut dyn fmt::Write) -> Result<()> {
295        <Self as Template>::render_into(self, writer)
296    }
297
298    fn dyn_render_into_with_values(
299        &self,
300        writer: &mut dyn fmt::Write,
301        values: &dyn Values,
302    ) -> Result<()> {
303        <Self as Template>::render_into_with_values(self, writer, values)
304    }
305
306    #[cfg(feature = "std")]
307    fn dyn_write_into(&self, writer: &mut dyn io::Write) -> io::Result<()> {
308        <Self as Template>::write_into(self, writer)
309    }
310
311    #[inline]
312    #[cfg(feature = "std")]
313    fn dyn_write_into_with_values(
314        &self,
315        writer: &mut dyn io::Write,
316        values: &dyn Values,
317    ) -> io::Result<()> {
318        <Self as Template>::write_into_with_values(self, writer, values)
319    }
320
321    #[inline]
322    fn size_hint(&self) -> usize {
323        <Self as Template>::SIZE_HINT
324    }
325}
326
327impl fmt::Display for dyn DynTemplate {
328    #[inline]
329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330        self.dyn_render_into(f).map_err(|_| fmt::Error {})
331    }
332}
333
334/// Implement the trait `$Trait` for a list of reference (wrapper) types to `$T: $Trait + ?Sized`
335macro_rules! impl_for_ref {
336    (impl $Trait:ident for $T:ident $body:tt) => {
337        const _: () = {
338            crate::impl_for_ref! {
339                impl<$T> $Trait for [
340                    &T
341                    &mut T
342                    core::cell::Ref<'_, T>
343                    core::cell::RefMut<'_, T>
344                ] $body
345            }
346        };
347        #[cfg(feature = "alloc")]
348        const _: () = {
349            crate::impl_for_ref! {
350                impl<$T> $Trait for [
351                    alloc::boxed::Box<T>
352                    alloc::rc::Rc<T>
353                    alloc::sync::Arc<T>
354                ] $body
355            }
356        };
357        #[cfg(feature = "std")]
358        const _: () = {
359            crate::impl_for_ref! {
360                impl<$T> $Trait for [
361                    std::sync::MutexGuard<'_, T>
362                    std::sync::RwLockReadGuard<'_, T>
363                    std::sync::RwLockWriteGuard<'_, T>
364                ] $body
365            }
366        };
367    };
368    (impl<$T:ident> $Trait:ident for [$($ty:ty)*] $body:tt) => {
369        $(impl<$T: $Trait + ?Sized> $Trait for $ty $body)*
370    }
371}
372
373pub(crate) use impl_for_ref;
374
375#[cfg(all(test, feature = "alloc"))]
376mod tests {
377    use std::fmt;
378
379    use super::*;
380    use crate::{DynTemplate, Template};
381
382    #[test]
383    fn dyn_template() {
384        use alloc::string::ToString;
385
386        struct Test;
387
388        impl Template for Test {
389            fn render_into_with_values<W: fmt::Write + ?Sized>(
390                &self,
391                writer: &mut W,
392                _values: &dyn Values,
393            ) -> Result<()> {
394                Ok(writer.write_str("test")?)
395            }
396
397            const SIZE_HINT: usize = 4;
398        }
399
400        impl fmt::Display for Test {
401            #[inline]
402            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403                self.render_into(f).map_err(|_| fmt::Error {})
404            }
405        }
406
407        impl filters::FastWritable for Test {
408            #[inline]
409            fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> {
410                self.render_into(f)
411            }
412        }
413
414        fn render(t: &dyn DynTemplate) -> String {
415            t.dyn_render().unwrap()
416        }
417
418        let test = &Test as &dyn DynTemplate;
419
420        assert_eq!(render(test), "test");
421
422        assert_eq!(test.to_string(), "test");
423
424        assert_eq!(alloc::format!("{test}"), "test");
425
426        let mut vec = alloc::vec![];
427        test.dyn_write_into(&mut vec).unwrap();
428        assert_eq!(vec, alloc::vec![b't', b'e', b's', b't']);
429    }
430}