1//! [![Crates.io](https://img.shields.io/crates/v/rinja?logo=rust&style=flat-square&logoColor=white "Crates.io")](https://crates.io/crates/rinja)
2//! [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/rinja-rs/rinja/rust.yml?branch=master&logo=github&style=flat-square&logoColor=white "GitHub Workflow Status")](https://github.com/rinja-rs/rinja/actions/workflows/rust.yml)
3//! [![Book](https://img.shields.io/readthedocs/rinja?label=book&logo=readthedocs&style=flat-square&logoColor=white "Book")](https://rinja.readthedocs.io/)
4//! [![docs.rs](https://img.shields.io/docsrs/rinja?logo=docsdotrs&style=flat-square&logoColor=white "docs.rs")](https://docs.rs/rinja/)
5//!
6//! Rinja 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` defining the template context.
9//! This is done using a custom derive implementation (implemented
10//! in [`rinja_derive`](https://crates.io/crates/rinja_derive)).
11//!
12//! For feature highlights and a quick start, please review the
13//! [README](https://github.com/rinja-rs/rinja/blob/master/README.md).
14//!
15//! You can find the documentation about our syntax, features, configuration in our book:
16//! [rinja.readthedocs.io](https://rinja.readthedocs.io/).
17//!
18//! # Creating Rinja templates
19//!
20//! The main feature of Rinja is the [`Template`] derive macro
21//! which reads your template code, so your `struct` can implement
22//! the [`Template`] trait and [`Display`][std::fmt::Display], type-safe and fast:
23//!
24//! ```rust
25//! # use rinja::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: 2024, enterprise: "<em>Rinja</em> developers" }.to_string(),
38//! "<p>© 2024 &#60;EM&#62;RINJA&#60;/EM&#62; DEVELOPERS</p>",
39//! );
40//! // In here you see can Rinja'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//! A Rinja template is a `struct` definition which provides the template
46//! context combined with a UTF-8 encoded text file (or inline source).
47//! Rinja 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](http://twig.sensiolabs.org/) 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
62mod error;
63pub mod filters;
64#[doc(hidden)]
65pub mod helpers;
66mod html;
67
68use std::{fmt, io};
69
70pub use rinja_derive::Template;
71
72#[doc(hidden)]
73pub use crate as shared;
74pub use crate::error::{Error, Result};
75
76/// Main `Template` trait; implementations are generally derived
77///
78/// If you need an object-safe template, use [`DynTemplate`].
79///
80/// ## Rendering performance
81///
82/// When rendering a rinja template, you should prefer the methods
83///
84/// * [`.render()`][Template::render] (to render the content into a new string),
85/// * [`.render_into()`][Template::render_into] (to render the content into an [`fmt::Write`]
86/// object, e.g. [`String`]) or
87/// * [`.write_into()`][Template::write_into] (to render the content into an [`io::Write`] object,
88/// e.g. [`Vec<u8>`])
89///
90/// over [`.to_string()`][std::string::ToString::to_string] or [`format!()`].
91/// While `.to_string()` and `format!()` give you the same result, they generally perform much worse
92/// than rinja's own methods, because [`fmt::Write`] uses [dynamic methods calls] instead of
93/// monomorphised code. On average, expect `.to_string()` to be 100% to 200% slower than
94/// `.render()`.
95///
96/// [dynamic methods calls]: <https://doc.rust-lang.org/stable/std/keyword.dyn.html>
97pub trait Template: fmt::Display {
98 /// Helper method which allocates a new `String` and renders into it
99 fn render(&self) -> Result<String> {
100 let mut buf = String::new();
101 let _ = buf.try_reserve(Self::SIZE_HINT);
102 self.render_into(&mut buf)?;
103 Ok(buf)
104 }
105
106 /// Renders the template to the given `writer` fmt buffer
107 fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()>;
108
109 /// Renders the template to the given `writer` io buffer
110 fn write_into<W: io::Write + ?Sized>(&self, writer: &mut W) -> io::Result<()> {
111 struct Wrapped<W: io::Write> {
112 writer: W,
113 err: Option<io::Error>,
114 }
115
116 impl<W: io::Write> fmt::Write for Wrapped<W> {
117 fn write_str(&mut self, s: &str) -> fmt::Result {
118 if let Err(err) = self.writer.write_all(s.as_bytes()) {
119 self.err = Some(err);
120 Err(fmt::Error)
121 } else {
122 Ok(())
123 }
124 }
125 }
126
127 let mut wrapped = Wrapped { writer, err: None };
128 if self.render_into(&mut wrapped).is_ok() {
129 Ok(())
130 } else {
131 let err = wrapped.err.take();
132 Err(err.unwrap_or_else(|| io::Error::new(io::ErrorKind::Other, fmt::Error)))
133 }
134 }
135
136 /// The template's extension, if provided
137 const EXTENSION: Option<&'static str>;
138
139 /// Provides a rough estimate of the expanded length of the rendered template. Larger
140 /// values result in higher memory usage but fewer reallocations. Smaller values result in the
141 /// opposite. This value only affects [`render`]. It does not take effect when calling
142 /// [`render_into`], [`write_into`], the [`fmt::Display`] implementation, or the blanket
143 /// [`ToString::to_string`] implementation.
144 ///
145 /// [`render`]: Template::render
146 /// [`render_into`]: Template::render_into
147 /// [`write_into`]: Template::write_into
148 const SIZE_HINT: usize;
149
150 /// The MIME type (Content-Type) of the data that gets rendered by this Template
151 const MIME_TYPE: &'static str;
152}
153
154impl<T: Template + ?Sized> Template for &T {
155 #[inline]
156 fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
157 T::render_into(self, writer)
158 }
159
160 #[inline]
161 fn render(&self) -> Result<String> {
162 T::render(self)
163 }
164
165 #[inline]
166 fn write_into<W: io::Write + ?Sized>(&self, writer: &mut W) -> io::Result<()> {
167 T::write_into(self, writer)
168 }
169
170 const EXTENSION: Option<&'static str> = T::EXTENSION;
171
172 const SIZE_HINT: usize = T::SIZE_HINT;
173
174 const MIME_TYPE: &'static str = T::MIME_TYPE;
175}
176
177/// Object-safe wrapper trait around [`Template`] implementers
178///
179/// This trades reduced performance (mostly due to writing into `dyn Write`) for object safety.
180pub trait DynTemplate {
181 /// Helper method which allocates a new `String` and renders into it
182 fn dyn_render(&self) -> Result<String>;
183
184 /// Renders the template to the given `writer` fmt buffer
185 fn dyn_render_into(&self, writer: &mut dyn fmt::Write) -> Result<()>;
186
187 /// Renders the template to the given `writer` io buffer
188 fn dyn_write_into(&self, writer: &mut dyn io::Write) -> io::Result<()>;
189
190 /// Helper function to inspect the template's extension
191 fn extension(&self) -> Option<&'static str>;
192
193 /// Provides a conservative estimate of the expanded length of the rendered template
194 fn size_hint(&self) -> usize;
195
196 /// The MIME type (Content-Type) of the data that gets rendered by this Template
197 fn mime_type(&self) -> &'static str;
198}
199
200impl<T: Template> DynTemplate for T {
201 fn dyn_render(&self) -> Result<String> {
202 <Self as Template>::render(self)
203 }
204
205 fn dyn_render_into(&self, writer: &mut dyn fmt::Write) -> Result<()> {
206 <Self as Template>::render_into(self, writer)
207 }
208
209 #[inline]
210 fn dyn_write_into(&self, writer: &mut dyn io::Write) -> io::Result<()> {
211 <Self as Template>::write_into(self, writer)
212 }
213
214 fn extension(&self) -> Option<&'static str> {
215 Self::EXTENSION
216 }
217
218 fn size_hint(&self) -> usize {
219 Self::SIZE_HINT
220 }
221
222 fn mime_type(&self) -> &'static str {
223 Self::MIME_TYPE
224 }
225}
226
227impl fmt::Display for dyn DynTemplate {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 self.dyn_render_into(f).map_err(|_| fmt::Error {})
230 }
231}
232
233/// Implement the trait `$Trait` for a list of reference (wrapper) types to `$T: $Trait + ?Sized`
234macro_rules! impl_for_ref {
235 (impl $Trait:ident for $T:ident $body:tt) => {
236 crate::impl_for_ref! {
237 impl<$T> $Trait for [
238 &T
239 &mut T
240 Box<T>
241 std::cell::Ref<'_, T>
242 std::cell::RefMut<'_, T>
243 std::rc::Rc<T>
244 std::sync::Arc<T>
245 std::sync::MutexGuard<'_, T>
246 std::sync::RwLockReadGuard<'_, T>
247 std::sync::RwLockWriteGuard<'_, T>
248 ] $body
249 }
250 };
251 (impl<$T:ident> $Trait:ident for [$($ty:ty)*] $body:tt) => {
252 $(impl<$T: $Trait + ?Sized> $Trait for $ty $body)*
253 }
254}
255
256pub(crate) use impl_for_ref;
257
258#[cfg(test)]
259mod tests {
260 use std::fmt;
261
262 use super::*;
263 use crate::{DynTemplate, Template};
264
265 #[test]
266 fn dyn_template() {
267 struct Test;
268 impl Template for Test {
269 fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
270 Ok(writer.write_str("test")?)
271 }
272
273 const EXTENSION: Option<&'static str> = Some("txt");
274
275 const SIZE_HINT: usize = 4;
276
277 const MIME_TYPE: &'static str = "text/plain; charset=utf-8";
278 }
279
280 impl fmt::Display for Test {
281 #[inline]
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 self.render_into(f).map_err(|_| fmt::Error {})
284 }
285 }
286
287 fn render(t: &dyn DynTemplate) -> String {
288 t.dyn_render().unwrap()
289 }
290
291 let test = &Test as &dyn DynTemplate;
292
293 assert_eq!(render(test), "test");
294
295 assert_eq!(test.to_string(), "test");
296
297 assert_eq!(format!("{test}"), "test");
298
299 let mut vec = Vec::new();
300 test.dyn_write_into(&mut vec).unwrap();
301 assert_eq!(vec, vec![b't', b'e', b's', b't']);
302 }
303}
304