Skip to main content

minijinja_lua/
environment.rs

1// SPDX-License-Identifier: MIT
2
3use std::{borrow::Cow, fmt, ops::Deref};
4
5use minijinja::{
6    Environment,
7    Error as JinjaError,
8    ErrorKind as JinjaErrorKind,
9    State,
10    args,
11    value::{Rest as JinjaRest, Value as JinjaValue},
12};
13use mlua::LuaSerdeExt;
14
15use crate::{
16    convert::{
17        LuaAutoEscape,
18        LuaFunctionObject,
19        LuaSyntaxConfig,
20        LuaTableObject,
21        LuaUndefinedBehavior,
22        lua_to_minijinja,
23        minijinja_to_lua,
24    },
25    lua::bind_lua,
26};
27
28/// A wrapper around a [`minijinja::Environment`]. This wrapper can be serialized into
29/// an [`mlua::UserData`] object for use within mlua::Lua.
30#[derive(mlua::UserData, Debug)]
31pub struct LuaEnvironment(Environment<'static>);
32
33impl From<Environment<'static>> for LuaEnvironment {
34    fn from(value: Environment<'static>) -> Self {
35        LuaEnvironment(value)
36    }
37}
38
39impl From<LuaEnvironment> for Environment<'static> {
40    fn from(value: LuaEnvironment) -> Self {
41        value.0
42    }
43}
44
45impl Deref for LuaEnvironment {
46    type Target = Environment<'static>;
47
48    fn deref(&self) -> &Self::Target {
49        &self.0
50    }
51}
52
53impl fmt::Display for LuaEnvironment {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        write!(f, "Environment")
56    }
57}
58
59#[mlua::userdata_impl]
60impl LuaEnvironment {
61    /// Get a new environment
62    #[lua(name = "new", infallible)]
63    pub(crate) fn lua_new() -> Self {
64        let mut env = Environment::new();
65
66        #[cfg(feature = "minijinja-contrib")]
67        minijinja_contrib::add_to_environment(&mut env);
68
69        #[cfg(feature = "json")]
70        crate::contrib::json::add_to_environment(&mut env);
71
72        #[cfg(feature = "datetime")]
73        crate::contrib::datetime::add_to_environment(&mut env);
74
75        env.into()
76    }
77
78    /// Get a new empty environment
79    #[lua(name = "empty", infallible)]
80    pub(crate) fn lua_empty() -> Self {
81        Environment::empty().into()
82    }
83
84    #[lua(name = "keep_trailing_newline", getter, infallible)]
85    pub(crate) fn lua_keep_trailing_newline(&self) -> bool {
86        self.0.keep_trailing_newline()
87    }
88
89    #[lua(name = "keep_trailing_newline", setter, infallible)]
90    pub(crate) fn lua_set_keep_trailing_newline(&mut self, val: bool) {
91        self.0.set_keep_trailing_newline(val)
92    }
93
94    #[lua(name = "trim_blocks", getter, infallible)]
95    pub(crate) fn lua_trim_blocks(&self) -> bool {
96        self.0.trim_blocks()
97    }
98
99    #[lua(name = "trim_blocks", setter, infallible)]
100    pub(crate) fn lua_set_trim_blocks(&mut self, val: bool) {
101        self.0.set_trim_blocks(val)
102    }
103
104    #[lua(name = "lstrip_blocks", getter, infallible)]
105    pub(crate) fn lua_lstrip_blocks(&self) -> bool {
106        self.0.lstrip_blocks()
107    }
108
109    #[lua(name = "lstrip_blocks", setter, infallible)]
110    pub(crate) fn lua_set_lstrip_blocks(&mut self, val: bool) {
111        self.0.set_lstrip_blocks(val)
112    }
113
114    #[lua(name = "debug", getter, infallible)]
115    pub(crate) fn lua_debug(&self) -> bool {
116        self.0.debug()
117    }
118
119    #[lua(name = "debug", setter, infallible)]
120    pub(crate) fn lua_set_debug(&mut self, val: bool) {
121        self.0.set_debug(val)
122    }
123
124    #[lua(name = "fuel", getter, infallible)]
125    pub(crate) fn lua_fuel(&self) -> Option<u64> {
126        self.0.fuel()
127    }
128
129    #[lua(name = "fuel", setter, infallible)]
130    pub(crate) fn lua_set_fuel(&mut self, val: Option<u64>) {
131        self.0.set_fuel(val)
132    }
133
134    #[lua(name = "recursion_limit", getter, infallible)]
135    pub(crate) fn lua_recursion_limit(&self) -> usize {
136        self.0.recursion_limit()
137    }
138
139    #[lua(name = "recursion_limit", setter, infallible)]
140    pub(crate) fn lua_set_recursion_limit(&mut self, val: usize) {
141        self.0.set_recursion_limit(val)
142    }
143
144    #[lua(name = "undefined_behavior", getter)]
145    pub(crate) fn lua_undefined_behavior(&self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
146        let ub: LuaUndefinedBehavior = self.0.undefined_behavior().into();
147        lua.to_value(&ub)
148    }
149
150    #[lua(name = "undefined_behavior", setter)]
151    pub(crate) fn lua_set_undefined_behavior(
152        &mut self,
153        lua: &mlua::Lua,
154        val: mlua::Value,
155    ) -> mlua::Result<()> {
156        let val: LuaUndefinedBehavior = lua.from_value(val)?;
157        self.0.set_undefined_behavior(val.into());
158
159        Ok(())
160    }
161
162    #[lua(name = "add_template", infallible)]
163    pub(crate) fn lua_add_template(
164        &mut self,
165        lua: &mlua::Lua,
166        name: String,
167        source: String,
168    ) -> mlua::Result<()> {
169        bind_lua(lua, || {
170            self.0
171                .add_template_owned(name, source)
172                .map_err(mlua::Error::external)
173        })
174    }
175
176    #[lua(name = "remove_template", infallible)]
177    pub(crate) fn lua_remove_template(&mut self, lua: &mlua::Lua, name: &str) {
178        bind_lua(lua, || self.0.remove_template(name))
179    }
180
181    #[lua(name = "clear_templates", infallible)]
182    pub(crate) fn lua_clear_templates(&mut self, lua: &mlua::Lua) {
183        bind_lua(lua, || self.0.clear_templates())
184    }
185
186    #[lua(name = "undeclared_variables")]
187    pub(crate) fn lua_undeclared_variables(
188        &mut self,
189        lua: &mlua::Lua,
190        name: &str,
191        nested: Option<bool>,
192    ) -> mlua::Result<mlua::Value> {
193        bind_lua(lua, || {
194            let nested = nested.unwrap_or(false);
195
196            let vars = self
197                .0
198                .get_template(name)
199                .map_err(mlua::Error::external)?
200                .undeclared_variables(nested);
201
202            lua.to_value(&vars)
203        })
204    }
205
206    #[lua(name = "set_loader")]
207    pub(crate) fn lua_set_loader(
208        &mut self,
209        lua: &mlua::Lua,
210        callback: mlua::Function,
211    ) -> mlua::Result<()> {
212        let func = LuaFunctionObject::from_value(lua, &callback)?;
213
214        self.0.set_loader(move |name| {
215            func.with_func::<Option<mlua::String>>(args!(name), None)
216                .map(|v| v.and_then(|v| v.as_str().map(|s| s.to_string())))
217        });
218
219        Ok(())
220    }
221
222    #[lua(name = "set_path_join_callback")]
223    pub(crate) fn lua_set_path_join_callback(
224        &mut self,
225        lua: &mlua::Lua,
226        callback: mlua::Function,
227    ) -> mlua::Result<()> {
228        let func = LuaFunctionObject::from_value(lua, &callback)?;
229
230        self.0.set_path_join_callback(move |name, parent| {
231            func.with_func::<String>(args!(name, parent), None)
232                .ok()
233                .flatten()
234                .and_then(|v| v.as_str().map(|s| Cow::Owned(s.to_string())))
235                .unwrap_or(Cow::Borrowed(name))
236        });
237
238        Ok(())
239    }
240
241    #[lua(name = "set_unknown_method_callback")]
242    pub(crate) fn lua_set_unknown_method_callback(
243        &mut self,
244        lua: &mlua::Lua,
245        callback: mlua::Function,
246    ) -> mlua::Result<()> {
247        let mut func = LuaFunctionObject::from_value(lua, &callback)?;
248        func.set_pass_state(true);
249
250        self.0
251            .set_unknown_method_callback(move |state, value, method, args| {
252                func.with_func::<mlua::MultiValue>(args!(value, method, ..args), Some(state))
253                    .map(|v| v.unwrap_or_default())
254            });
255
256        Ok(())
257    }
258
259    #[cfg(feature = "minijinja-contrib")]
260    #[lua(name = "set_pycompat", infallible)]
261    pub(crate) fn lua_set_pycompat(&mut self, enable: Option<bool>) {
262        match enable {
263            Some(true) | None => self
264                .0
265                .set_unknown_method_callback(minijinja_contrib::pycompat::unknown_method_callback),
266            Some(false) => self.0.set_unknown_method_callback(|_, _, _, _| {
267                Err(JinjaError::from(JinjaErrorKind::UnknownMethod))
268            }),
269        }
270    }
271
272    #[lua(name = "set_auto_escape_callback")]
273    pub(crate) fn lua_set_auto_escape_callback(
274        &mut self,
275        lua: &mlua::Lua,
276        callback: mlua::Function,
277    ) -> mlua::Result<()> {
278        let func = LuaFunctionObject::from_value(lua, &callback)?;
279
280        self.0
281            .set_auto_escape_callback(move |name| -> minijinja::AutoEscape {
282                func.with_func_ser::<LuaAutoEscape>(args!(name), None)
283                    .unwrap_or_default()
284                    .into()
285            });
286
287        Ok(())
288    }
289
290    #[lua(name = "set_formatter")]
291    pub(crate) fn lua_set_formatter(
292        &mut self,
293        lua: &mlua::Lua,
294        callback: mlua::Function,
295    ) -> mlua::Result<()> {
296        let mut func = LuaFunctionObject::from_value(lua, &callback)?;
297        func.set_pass_state(true);
298
299        self.0.set_formatter(move |out, state, value| {
300            func.with_func::<Option<String>>(args!(value), Some(state))
301                .ok()
302                .flatten()
303                .map(|val| {
304                    let s = val.as_str().ok_or_else(|| {
305                        JinjaError::new(
306                            JinjaErrorKind::WriteFailure,
307                            "formatter must return a string",
308                        )
309                    })?;
310                    out.write_str(s).map_err(|err| {
311                        JinjaError::new(JinjaErrorKind::WriteFailure, err.to_string())
312                    })
313                })
314                .unwrap_or(Ok(()))
315        });
316
317        Ok(())
318    }
319
320    #[lua(name = "set_syntax")]
321    pub(crate) fn lua_set_syntax(&mut self, syntax: LuaSyntaxConfig) -> mlua::Result<()> {
322        self.0.set_syntax(syntax.into());
323
324        Ok(())
325    }
326
327    #[lua(name = "render_template")]
328    pub(crate) fn lua_render_template(
329        &mut self,
330        lua: &mlua::Lua,
331        name: &str,
332        ctx: Option<mlua::Table>,
333    ) -> mlua::Result<String> {
334        let ctx: Option<JinjaValue> = ctx
335            .and_then(|t| LuaTableObject::from_value(lua, &t).ok())
336            .map(|obj| obj.into());
337
338        bind_lua(lua, || {
339            self.0
340                .get_template(name)
341                .map_err(mlua::Error::external)?
342                .render(ctx)
343                .map_err(mlua::Error::external)
344        })
345    }
346
347    #[lua(name = "render_str")]
348    pub(crate) fn lua_render_str(
349        &self,
350        lua: &mlua::Lua,
351        source: &str,
352        ctx: Option<mlua::Table>,
353        name: Option<String>,
354    ) -> mlua::Result<String> {
355        let ctx: Option<JinjaValue> = ctx
356            .and_then(|t| LuaTableObject::from_value(lua, &t).ok())
357            .map(|obj| obj.into());
358
359        let name = name.unwrap_or("<string>".to_string());
360
361        bind_lua(lua, || {
362            self.0
363                .render_named_str(&name, source, ctx)
364                .map_err(mlua::Error::external)
365        })
366    }
367
368    #[lua(name = "render_captured")]
369    pub(crate) fn lua_render_captured(
370        &mut self,
371        lua: &mlua::Lua,
372        name: &str,
373        ctx: Option<mlua::Table>,
374        callback: mlua::Function,
375    ) -> mlua::Result<mlua::MultiValue> {
376        let mut func = LuaFunctionObject::from_value(lua, &callback)?;
377        func.set_pass_state(true);
378
379        let ctx: Option<JinjaValue> = ctx
380            .and_then(|t| LuaTableObject::from_value(lua, &t).ok())
381            .map(|obj| obj.into());
382
383        bind_lua(lua, || {
384            let mut captured = self
385                .0
386                .get_template(name)
387                .map_err(mlua::Error::external)?
388                .render_captured(ctx)
389                .map_err(mlua::Error::external)?;
390
391            let mut mv = captured
392                .with_state_mut(|state| func.with_func_mut::<mlua::MultiValue>(&[], Some(state)))
393                .map_err(mlua::Error::external)?
394                .and_then(|v| minijinja_to_lua(lua, &v))
395                .unwrap_or_default();
396
397            let rendered = captured.into_output();
398
399            mv.push_front(mlua::Value::String(lua.create_string(rendered)?));
400
401            Ok(mv)
402        })
403    }
404
405    #[lua(name = "eval")]
406    pub(crate) fn lua_eval(
407        &self,
408        lua: &mlua::Lua,
409        source: &str,
410        ctx: Option<mlua::Table>,
411    ) -> mlua::Result<mlua::MultiValue> {
412        let ctx: Option<JinjaValue> = ctx
413            .and_then(|t| LuaTableObject::from_value(lua, &t).ok())
414            .map(|obj| obj.into());
415
416        bind_lua(lua, || {
417            let expr = self
418                .0
419                .compile_expression(source)
420                .map_err(mlua::Error::external)?
421                .eval(ctx)
422                .map_err(mlua::Error::external)?;
423
424            minijinja_to_lua(lua, &expr).ok_or_else(|| {
425                mlua::Error::DeserializeError("could not convert output to lua".to_string())
426            })
427        })
428    }
429
430    #[lua(name = "add_filter")]
431    pub(crate) fn lua_add_filter(
432        &mut self,
433        lua: &mlua::Lua,
434        name: String,
435        filter: mlua::Function,
436        pass_state: Option<bool>,
437    ) -> mlua::Result<()> {
438        let mut func = LuaFunctionObject::from_value(lua, &filter)?;
439        func.set_pass_state(pass_state.unwrap_or(true));
440
441        self.0
442            .add_filter(name, move |state: &State, args: JinjaRest<JinjaValue>| {
443                func.with_func::<mlua::MultiValue>(&args, Some(state))
444            });
445
446        Ok(())
447    }
448
449    #[lua(name = "remove_filter", infallible)]
450    pub(crate) fn lua_remove_filter(&mut self, name: String) {
451        self.0.remove_filter(&name)
452    }
453
454    #[lua(name = "add_test")]
455    pub(crate) fn lua_add_test(
456        &mut self,
457        lua: &mlua::Lua,
458        name: String,
459        test: mlua::Function,
460        pass_state: Option<bool>,
461    ) -> mlua::Result<()> {
462        let mut func = LuaFunctionObject::from_value(lua, &test)?;
463        func.set_pass_state(pass_state.unwrap_or(true));
464
465        self.0
466            .add_test(name, move |state: &State, args: JinjaRest<JinjaValue>| {
467                func.with_func::<bool>(&args, Some(state))
468            });
469
470        Ok(())
471    }
472
473    #[lua(name = "remove_test", infallible)]
474    pub(crate) fn lua_remove_test(&mut self, name: String) {
475        self.0.remove_test(&name)
476    }
477
478    #[lua(name = "add_global")]
479    pub(crate) fn add_global(
480        &mut self,
481        lua: &mlua::Lua,
482        name: String,
483        val: mlua::Value,
484        pass_state: Option<bool>,
485    ) -> mlua::Result<()> {
486        match val {
487            mlua::Value::Function(f) => {
488                let mut func = LuaFunctionObject::from_value(lua, &f)?;
489                func.set_pass_state(pass_state.unwrap_or(true));
490
491                self.0
492                    .add_function(name, move |state: &State, args: JinjaRest<JinjaValue>| {
493                        func.with_func::<mlua::MultiValue>(&args, Some(state))
494                    })
495            },
496            _ => self.0.add_global(name, lua_to_minijinja(lua, &val)),
497        };
498
499        Ok(())
500    }
501
502    #[lua(name = "remove_global", infallible)]
503    pub(crate) fn lua_remove_global(&mut self, name: &str) {
504        self.0.remove_global(name)
505    }
506
507    #[lua(name = "globals")]
508    pub(crate) fn lua_globals(&self, lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
509        let table = lua.create_table()?;
510
511        for (name, value) in self.0.globals() {
512            minijinja_to_lua(lua, &value)
513                .and_then(|mut v| table.set(name, v.pop_front().unwrap_or_default()).ok());
514        }
515
516        Ok(table)
517    }
518}