| 1 | //! [](https://crates.io/crates/rinja) |
| 2 | //! [](https://github.com/rinja-rs/rinja/actions/workflows/rust.yml) |
| 3 | //! [](https://rinja.readthedocs.io/) |
| 4 | //! [](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 <EM>RINJA</EM> 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 | |
| 62 | mod error; |
| 63 | pub mod filters; |
| 64 | #[doc (hidden)] |
| 65 | pub mod helpers; |
| 66 | mod html; |
| 67 | |
| 68 | use std::{fmt, io}; |
| 69 | |
| 70 | pub use rinja_derive::Template; |
| 71 | |
| 72 | #[doc (hidden)] |
| 73 | pub use crate as shared; |
| 74 | pub 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> |
| 97 | pub 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 | |
| 154 | impl<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. |
| 180 | pub 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 | |
| 200 | impl<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 | |
| 227 | impl 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` |
| 234 | macro_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 | |
| 256 | pub(crate) use impl_for_ref; |
| 257 | |
| 258 | #[cfg (test)] |
| 259 | mod 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 | |