rwf/view/template/
mod.rs

1//! Implementation of the Rwf templating language.
2//!
3//! Templates are effectively
4//! a translation of predefined functions and operations into equivalent Rust code.
5//! Coupled with Rust memory management, this makes this template engine pretty fast.
6//!
7//! The interpreter has a lexer, parser, and an executor. For a language usage examples,
8//! see [documentation](https://levkk.github.io/rwf/).
9pub mod context;
10pub mod error;
11pub mod language;
12pub mod lexer;
13
14pub use context::Context;
15pub use error::Error;
16pub use lexer::{Lexer, ToTemplateValue, Token, TokenWithContext, Tokenize, Value};
17
18use crate::http::Response;
19use crate::view::Templates;
20
21use language::Program;
22
23use std::fs::read_to_string;
24use std::path::{Path, PathBuf};
25use std::sync::Arc;
26
27/// Rwf template.
28///
29/// Contains the executable AST.
30#[allow(dead_code)]
31#[derive(Clone, Debug)]
32pub struct Template {
33    program: Program,
34    path: Option<PathBuf>,
35}
36
37impl Template {
38    /// Read and compile a template from disk.
39    pub fn new(path: impl AsRef<Path> + std::marker::Copy) -> Result<Self, Error> {
40        let text = match read_to_string(path) {
41            Ok(text) => text,
42            Err(_) => return Err(Error::TemplateDoesNotExist(path.as_ref().to_owned())),
43        };
44
45        Ok(Template {
46            program: Program::from_str(&text)?,
47            path: Some(path.as_ref().to_owned()),
48        })
49    }
50
51    /// Read and compile a template from a string.
52    ///
53    /// # Example
54    ///
55    /// ```
56    /// # use rwf::view::template::*;
57    /// let template = Template::from_str("<%= 1 + 5 %>").unwrap();
58    /// let result = template.render_default().unwrap();
59    ///
60    /// assert_eq!(result, "6");
61    /// ```
62    pub fn from_str(template: &str) -> Result<Self, Error> {
63        Ok(Template {
64            program: Program::from_str(template)?,
65            path: None,
66        })
67    }
68
69    /// Execute a template, provided with the context, and produce a rendering. The rendering
70    /// is a string.
71    pub fn render(&self, context: impl TryInto<Context, Error = Error>) -> Result<String, Error> {
72        let context: Context = context.try_into()?;
73
74        match self.program.evaluate(&context) {
75            Ok(result) => Ok(result),
76            Err(err) => {
77                if let Some(path) = &self.path {
78                    Err(err.pretty_from_path(path))
79                } else {
80                    Err(err)
81                }
82            }
83        }
84    }
85
86    /// [`Self::render`] with an empty context. Used for templates that don't use any variables, or only
87    /// have globally defined variables.
88    pub fn render_default(&self) -> Result<String, Error> {
89        self.render(&Context::default())
90    }
91
92    /// Fetch the template from cache. If the template is not in cache, load it
93    /// from disk and store it in the cache for future use.
94    pub fn cached(path: impl AsRef<Path> + Copy) -> Result<Arc<Self>, Error> {
95        match Templates::cache().get(path) {
96            Ok(template) => Ok(template),
97            Err(err) => Err(err.pretty_from_path(path)),
98        }
99    }
100
101    /// Load the template from disk and store it in the cache for future use. Alias for [`Self::cached`].
102    pub fn load(path: impl AsRef<Path> + Copy) -> Result<Arc<Self>, Error> {
103        Self::cached(path)
104    }
105
106    /// Set global default values for variables. If the variable isn't defined
107    /// in a template context, and a default exists, the default value will be used instead.
108    pub fn defaults(context: Context) {
109        Context::defaults(context);
110    }
111
112    /// Render a static template (without variables). If the template doesn't exist
113    /// and combined with the `?` operator,
114    /// automatically return `500 - Internal Server Error`.
115    ///
116    /// Useful inside controllers.
117    ///
118    pub fn cached_static(path: impl AsRef<Path> + Copy) -> Result<Response, Error> {
119        match Self::cached(path) {
120            Ok(template) => Ok(template.try_into()?),
121            Err(err) => Ok(Response::internal_error(err)),
122        }
123    }
124}
125
126impl TryFrom<&Template> for Response {
127    type Error = Error;
128
129    fn try_from(template: &Template) -> Result<Response, Self::Error> {
130        let text = template.render_default()?;
131        Ok(Response::new().html(text))
132    }
133}
134
135impl TryFrom<Arc<Template>> for Response {
136    type Error = Error;
137
138    fn try_from(template: Arc<Template>) -> Result<Response, Self::Error> {
139        use std::ops::Deref;
140        template.deref().try_into()
141    }
142}