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}