minijinja/
template.rs

1use std::collections::{BTreeMap, HashSet};
2use std::ops::Deref;
3use std::sync::Arc;
4use std::{fmt, io};
5
6use serde::Serialize;
7
8use crate::compiler::codegen::CodeGenerator;
9use crate::compiler::instructions::Instructions;
10use crate::compiler::lexer::WhitespaceConfig;
11use crate::compiler::meta::find_undeclared;
12use crate::compiler::parser::parse;
13use crate::environment::Environment;
14use crate::error::{attach_basic_debug_info, Error};
15use crate::output::{Output, WriteWrapper};
16use crate::syntax::SyntaxConfig;
17use crate::utils::AutoEscape;
18use crate::value::Value;
19use crate::vm::{prepare_blocks, Context, State, Vm};
20
21/// Callback for auto escape determination
22pub type AutoEscapeFunc = dyn Fn(&str) -> AutoEscape + Sync + Send;
23
24/// Internal struct that holds template loading level config values.
25#[derive(Clone)]
26pub struct TemplateConfig {
27    /// The syntax used for the template.
28    pub syntax_config: SyntaxConfig,
29    /// Controls whitespace behavior.
30    pub ws_config: WhitespaceConfig,
31    /// The callback that determines the initial auto escaping for templates.
32    pub default_auto_escape: Arc<AutoEscapeFunc>,
33}
34
35impl TemplateConfig {
36    pub(crate) fn new(default_auto_escape: Arc<AutoEscapeFunc>) -> TemplateConfig {
37        TemplateConfig {
38            syntax_config: SyntaxConfig::default(),
39            ws_config: WhitespaceConfig::default(),
40            default_auto_escape,
41        }
42    }
43}
44
45/// Represents a handle to a template.
46///
47/// Templates are stored in the [`Environment`] as bytecode instructions.  With the
48/// [`Environment::get_template`] method that is looked up and returned in form of
49/// this handle.  Such a template can be cheaply copied as it only holds references.
50///
51/// To render the [`render`](Template::render) method can be used.
52#[derive(Clone)]
53pub struct Template<'env: 'source, 'source> {
54    env: &'env Environment<'env>,
55    pub(crate) compiled: CompiledTemplateRef<'env, 'source>,
56}
57
58impl fmt::Debug for Template<'_, '_> {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        let mut ds = f.debug_struct("Template");
61        ds.field("name", &self.name());
62        #[cfg(feature = "internal_debug")]
63        {
64            ds.field("instructions", &self.compiled.instructions);
65            ds.field("blocks", &self.compiled.blocks);
66        }
67        ds.field("initial_auto_escape", &self.compiled.initial_auto_escape);
68        ds.finish()
69    }
70}
71
72impl<'env, 'source> Template<'env, 'source> {
73    pub(crate) fn new(
74        env: &'env Environment<'env>,
75        compiled: CompiledTemplateRef<'env, 'source>,
76    ) -> Template<'env, 'source> {
77        Template { env, compiled }
78    }
79
80    /// Returns the name of the template.
81    pub fn name(&self) -> &str {
82        self.compiled.instructions.name()
83    }
84
85    /// Returns the source code of the template.
86    pub fn source(&self) -> &str {
87        self.compiled.instructions.source()
88    }
89
90    /// Renders the template into a string.
91    ///
92    /// The provided value is used as the initial context for the template.  It
93    /// can be any object that implements [`Serialize`](serde::Serialize).  You
94    /// can either create your own struct and derive `Serialize` for it or the
95    /// [`context!`](crate::context) macro can be used to create an ad-hoc context.
96    ///
97    /// For very large contexts and to avoid the overhead of serialization of
98    /// potentially unused values, you might consider using a dynamic
99    /// [`Object`](crate::value::Object) as value.  For more
100    /// information see [Map as Context](crate::value::Object#map-as-context).
101    ///
102    /// ```
103    /// # use minijinja::{Environment, context};
104    /// # let mut env = Environment::new();
105    /// # env.add_template("hello", "Hello {{ name }}!").unwrap();
106    /// let tmpl = env.get_template("hello").unwrap();
107    /// println!("{}", tmpl.render(context!(name => "John")).unwrap());
108    /// ```
109    ///
110    /// To render a single block use [`eval_to_state`](Self::eval_to_state) in
111    /// combination with [`State::render_block`].
112    ///
113    /// **Note on values:** The [`Value`] type implements `Serialize` and can be
114    /// efficiently passed to render.  It does not undergo actual serialization.
115    pub fn render<S: Serialize>(&self, ctx: S) -> Result<String, Error> {
116        // reduce total amount of code faling under mono morphization into
117        // this function, and share the rest in _render.
118        self._render(Value::from_serialize(&ctx)).map(|x| x.0)
119    }
120
121    /// Like [`render`](Self::render) but also return the evaluated [`State`].
122    ///
123    /// This can be used to inspect the [`State`] of the template post evaluation
124    /// for instance to get fuel consumption numbers or to access globally set
125    /// variables.
126    ///
127    /// ```
128    /// # use minijinja::{Environment, context, value::Value};
129    /// # let mut env = Environment::new();
130    /// let tmpl = env.template_from_str("{% set x = 42 %}Hello {{ what }}!").unwrap();
131    /// let (rv, state) = tmpl.render_and_return_state(context!{ what => "World" }).unwrap();
132    /// assert_eq!(rv, "Hello World!");
133    /// assert_eq!(state.lookup("x"), Some(Value::from(42)));
134    /// ```
135    ///
136    /// **Note on values:** The [`Value`] type implements `Serialize` and can be
137    /// efficiently passed to render.  It does not undergo actual serialization.
138    pub fn render_and_return_state<S: Serialize>(
139        &self,
140        ctx: S,
141    ) -> Result<(String, State<'_, 'env>), Error> {
142        // reduce total amount of code faling under mono morphization into
143        // this function, and share the rest in _render.
144        self._render(Value::from_serialize(&ctx))
145    }
146
147    fn _render(&self, root: Value) -> Result<(String, State<'_, 'env>), Error> {
148        let mut rv = String::with_capacity(self.compiled.buffer_size_hint);
149        self._eval(root, &mut Output::new(&mut rv))
150            .map(|(_, state)| (rv, state))
151    }
152
153    /// Renders the template into an [`io::Write`].
154    ///
155    /// This works exactly like [`render`](Self::render) but instead writes the template
156    /// as it's evaluating into an [`io::Write`].  It also returns the [`State`] like
157    /// [`render_and_return_state`](Self::render_and_return_state) does.
158    ///
159    /// ```
160    /// # use minijinja::{Environment, context};
161    /// # let mut env = Environment::new();
162    /// # env.add_template("hello", "Hello {{ name }}!").unwrap();
163    /// use std::io::stdout;
164    ///
165    /// let tmpl = env.get_template("hello").unwrap();
166    /// tmpl.render_to_write(context!(name => "John"), &mut stdout()).unwrap();
167    /// ```
168    ///
169    /// **Note on values:** The [`Value`] type implements `Serialize` and can be
170    /// efficiently passed to render.  It does not undergo actual serialization.
171    pub fn render_to_write<S: Serialize, W: io::Write>(
172        &self,
173        ctx: S,
174        w: W,
175    ) -> Result<State<'_, 'env>, Error> {
176        let mut wrapper = WriteWrapper { w, err: None };
177        self._eval(Value::from_serialize(&ctx), &mut Output::new(&mut wrapper))
178            .map(|(_, state)| state)
179            .map_err(|err| wrapper.take_err(err))
180    }
181
182    /// Evaluates the template into a [`State`].
183    ///
184    /// This evaluates the template, discards the output and returns the final
185    /// `State` for introspection.  From there global variables or blocks
186    /// can be accessed.  What this does is quite similar to how the engine
187    /// internally works with templates that are extended or imported from.
188    ///
189    /// ```
190    /// # use minijinja::{Environment, context};
191    /// # fn test() -> Result<(), minijinja::Error> {
192    /// # let mut env = Environment::new();
193    /// # env.add_template("hello", "")?;
194    /// let tmpl = env.get_template("hello")?;
195    /// let state = tmpl.eval_to_state(context!(name => "John"))?;
196    /// println!("{:?}", state.exports());
197    /// # Ok(()) }
198    /// ```
199    ///
200    /// If you also want to render, use [`render_and_return_state`](Self::render_and_return_state).
201    ///
202    /// For more information see [`State`].
203    pub fn eval_to_state<S: Serialize>(&self, ctx: S) -> Result<State<'_, 'env>, Error> {
204        let root = Value::from_serialize(&ctx);
205        let mut out = Output::null();
206        let vm = Vm::new(self.env);
207        let state = ok!(vm.eval(
208            &self.compiled.instructions,
209            root,
210            &self.compiled.blocks,
211            &mut out,
212            self.compiled.initial_auto_escape,
213        ))
214        .1;
215        Ok(state)
216    }
217
218    fn _eval(
219        &self,
220        root: Value,
221        out: &mut Output,
222    ) -> Result<(Option<Value>, State<'_, 'env>), Error> {
223        Vm::new(self.env).eval(
224            &self.compiled.instructions,
225            root,
226            &self.compiled.blocks,
227            out,
228            self.compiled.initial_auto_escape,
229        )
230    }
231
232    /// Returns a set of all undeclared variables in the template.
233    ///
234    /// This returns a set of all variables that might be looked up
235    /// at runtime by the template.  Since this is runs a static
236    /// analysis, the actual control flow is not considered.  This
237    /// also cannot take into account what happens due to includes,
238    /// imports or extending.  If `nested` is set to `true`, then also
239    /// nested trivial attribute lookups are considered and returned.
240    ///
241    /// ```rust
242    /// # use minijinja::Environment;
243    /// let mut env = Environment::new();
244    /// env.add_template("x", "{% set x = foo %}{{ x }}{{ bar.baz }}").unwrap();
245    /// let tmpl = env.get_template("x").unwrap();
246    /// let undeclared = tmpl.undeclared_variables(false);
247    /// // returns ["foo", "bar"]
248    /// let undeclared = tmpl.undeclared_variables(true);
249    /// // returns ["foo", "bar.baz"]
250    /// ```
251    ///
252    /// Note that this does not special case global variables.  This means
253    /// that for instance a template that uses `namespace()` will return
254    /// `namespace` in the return value.
255    pub fn undeclared_variables(&self, nested: bool) -> HashSet<String> {
256        match parse(
257            self.compiled.instructions.source(),
258            self.name(),
259            self.compiled.syntax_config.clone(),
260            // TODO: this is not entirely great, but good enough for this use case.
261            Default::default(),
262        ) {
263            Ok(ast) => find_undeclared(&ast, nested),
264            Err(_) => HashSet::new(),
265        }
266    }
267
268    /// Creates an empty [`State`] for this template.
269    ///
270    /// It's very rare that you need to actually do this but it can be useful when
271    /// testing values or working with macros or other callable objects from outside
272    /// the template environment.
273    pub fn new_state(&self) -> State<'_, 'env> {
274        State::new(
275            Context::new(self.env),
276            self.compiled.initial_auto_escape,
277            &self.compiled.instructions,
278            prepare_blocks(&self.compiled.blocks),
279        )
280    }
281
282    /// Returns the instructions and blocks if the template is loaded from the
283    /// environment.
284    ///
285    /// For templates loaded as string on the environment this API contract
286    /// cannot be upheld because the template might not live long enough.  Under
287    /// normal circumstances however such a template object would never make it
288    /// to the callers of this API as this API is used for including or extending,
289    /// both of which should only ever get access to a template from the environment
290    /// which holds a borrowed ref.
291    #[cfg(feature = "multi_template")]
292    pub(crate) fn instructions_and_blocks(
293        &self,
294    ) -> Result<
295        (
296            &'env Instructions<'env>,
297            &'env BTreeMap<&'env str, Instructions<'env>>,
298        ),
299        Error,
300    > {
301        match self.compiled {
302            CompiledTemplateRef::Borrowed(x) => Ok((&x.instructions, &x.blocks)),
303            CompiledTemplateRef::Owned(_) => Err(Error::new(
304                crate::ErrorKind::InvalidOperation,
305                "cannot extend or include template not borrowed from environment",
306            )),
307        }
308    }
309
310    /// Returns the initial auto escape setting.
311    #[cfg(feature = "multi_template")]
312    pub(crate) fn initial_auto_escape(&self) -> AutoEscape {
313        self.compiled.initial_auto_escape
314    }
315}
316
317#[derive(Clone)]
318pub(crate) enum CompiledTemplateRef<'env: 'source, 'source> {
319    Owned(Arc<CompiledTemplate<'source>>),
320    Borrowed(&'env CompiledTemplate<'source>),
321}
322
323impl<'source> Deref for CompiledTemplateRef<'_, 'source> {
324    type Target = CompiledTemplate<'source>;
325
326    fn deref(&self) -> &Self::Target {
327        match *self {
328            CompiledTemplateRef::Owned(ref x) => x,
329            CompiledTemplateRef::Borrowed(x) => x,
330        }
331    }
332}
333
334/// Represents a compiled template in memory.
335pub struct CompiledTemplate<'source> {
336    /// The root instructions.
337    pub instructions: Instructions<'source>,
338    /// Block local instructions.
339    pub blocks: BTreeMap<&'source str, Instructions<'source>>,
340    /// Optional size hint for string rendering.
341    pub buffer_size_hint: usize,
342    /// The syntax config that created it.
343    pub syntax_config: SyntaxConfig,
344    /// The initial setting of auto escaping.
345    pub initial_auto_escape: AutoEscape,
346}
347
348impl fmt::Debug for CompiledTemplate<'_> {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        let mut ds = f.debug_struct("CompiledTemplate");
351        #[cfg(feature = "internal_debug")]
352        {
353            ds.field("instructions", &self.instructions);
354            ds.field("blocks", &self.blocks);
355        }
356        ds.finish()
357    }
358}
359
360impl<'source> CompiledTemplate<'source> {
361    /// Creates a compiled template from name and source using the given settings.
362    pub fn new(
363        name: &'source str,
364        source: &'source str,
365        config: &TemplateConfig,
366    ) -> Result<CompiledTemplate<'source>, Error> {
367        attach_basic_debug_info(Self::_new_impl(name, source, config), source)
368    }
369
370    fn _new_impl(
371        name: &'source str,
372        source: &'source str,
373        config: &TemplateConfig,
374    ) -> Result<CompiledTemplate<'source>, Error> {
375        let ast = ok!(parse(
376            source,
377            name,
378            config.syntax_config.clone(),
379            config.ws_config
380        ));
381        let mut g = CodeGenerator::new(name, source);
382        g.compile_stmt(&ast);
383        let buffer_size_hint = g.buffer_size_hint();
384        let (instructions, blocks) = g.finish();
385        Ok(CompiledTemplate {
386            instructions,
387            blocks,
388            buffer_size_hint,
389            syntax_config: config.syntax_config.clone(),
390            initial_auto_escape: (config.default_auto_escape)(name),
391        })
392    }
393}