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, infallible)]
145    pub(crate) fn lua_undefined_behavior(&self) -> LuaUndefinedBehavior {
146        self.0.undefined_behavior().into()
147    }
148
149    #[lua(name = "undefined_behavior", setter)]
150    pub(crate) fn lua_set_undefined_behavior(
151        &mut self,
152        val: LuaUndefinedBehavior,
153    ) -> mlua::Result<()> {
154        self.0.set_undefined_behavior(val.into());
155
156        Ok(())
157    }
158
159    #[lua(name = "add_template", infallible)]
160    pub(crate) fn lua_add_template(
161        &mut self,
162        lua: &mlua::Lua,
163        name: String,
164        source: String,
165    ) -> mlua::Result<()> {
166        bind_lua(lua, || {
167            self.0
168                .add_template_owned(name, source)
169                .map_err(mlua::Error::external)
170        })
171    }
172
173    #[lua(name = "remove_template", infallible)]
174    pub(crate) fn lua_remove_template(&mut self, lua: &mlua::Lua, name: &str) {
175        bind_lua(lua, || self.0.remove_template(name))
176    }
177
178    #[lua(name = "clear_templates", infallible)]
179    pub(crate) fn lua_clear_templates(&mut self, lua: &mlua::Lua) {
180        bind_lua(lua, || self.0.clear_templates())
181    }
182
183    #[lua(name = "undeclared_variables")]
184    pub(crate) fn lua_undeclared_variables(
185        &mut self,
186        lua: &mlua::Lua,
187        name: &str,
188        nested: Option<bool>,
189    ) -> mlua::Result<mlua::Value> {
190        bind_lua(lua, || {
191            let nested = nested.unwrap_or(false);
192
193            let vars = self
194                .0
195                .get_template(name)
196                .map_err(mlua::Error::external)?
197                .undeclared_variables(nested);
198
199            lua.to_value(&vars)
200        })
201    }
202
203    #[lua(name = "set_loader")]
204    pub(crate) fn lua_set_loader(
205        &mut self,
206        lua: &mlua::Lua,
207        callback: mlua::Function,
208    ) -> mlua::Result<()> {
209        let func = LuaFunctionObject::from_value(lua, &callback)?;
210
211        self.0.set_loader(move |name| {
212            func.with_func::<Option<mlua::String>>(args!(name), None)
213                .map(|v| v.and_then(|v| v.as_str().map(|s| s.to_string())))
214        });
215
216        Ok(())
217    }
218
219    #[lua(name = "set_path_join_callback")]
220    pub(crate) fn lua_set_path_join_callback(
221        &mut self,
222        lua: &mlua::Lua,
223        callback: mlua::Function,
224    ) -> mlua::Result<()> {
225        let func = LuaFunctionObject::from_value(lua, &callback)?;
226
227        self.0.set_path_join_callback(move |name, parent| {
228            func.with_func::<String>(args!(name, parent), None)
229                .ok()
230                .flatten()
231                .and_then(|v| v.as_str().map(|s| Cow::Owned(s.to_string())))
232                .unwrap_or(Cow::Borrowed(name))
233        });
234
235        Ok(())
236    }
237
238    #[lua(name = "set_unknown_method_callback")]
239    pub(crate) fn lua_set_unknown_method_callback(
240        &mut self,
241        lua: &mlua::Lua,
242        callback: mlua::Function,
243    ) -> mlua::Result<()> {
244        let mut func = LuaFunctionObject::from_value(lua, &callback)?;
245        func.set_pass_state(true);
246
247        self.0
248            .set_unknown_method_callback(move |state, value, method, args| {
249                func.with_func::<mlua::MultiValue>(args!(value, method, ..args), Some(state))
250                    .map(|v| v.unwrap_or_default())
251            });
252
253        Ok(())
254    }
255
256    #[cfg(feature = "minijinja-contrib")]
257    #[lua(name = "set_pycompat", infallible)]
258    pub(crate) fn lua_set_pycompat(&mut self, enable: Option<bool>) {
259        match enable {
260            Some(true) | None => self
261                .0
262                .set_unknown_method_callback(minijinja_contrib::pycompat::unknown_method_callback),
263            Some(false) => self.0.set_unknown_method_callback(|_, _, _, _| {
264                Err(JinjaError::from(JinjaErrorKind::UnknownMethod))
265            }),
266        }
267    }
268
269    #[lua(name = "set_auto_escape_callback")]
270    pub(crate) fn lua_set_auto_escape_callback(
271        &mut self,
272        lua: &mlua::Lua,
273        callback: mlua::Function,
274    ) -> mlua::Result<()> {
275        let func = LuaFunctionObject::from_value(lua, &callback)?;
276
277        self.0
278            .set_auto_escape_callback(move |name| -> minijinja::AutoEscape {
279                func.with_func_ser::<LuaAutoEscape>(args!(name), None)
280                    .unwrap_or_default()
281                    .into()
282            });
283
284        Ok(())
285    }
286
287    #[lua(name = "set_formatter")]
288    pub(crate) fn lua_set_formatter(
289        &mut self,
290        lua: &mlua::Lua,
291        callback: mlua::Function,
292    ) -> mlua::Result<()> {
293        let mut func = LuaFunctionObject::from_value(lua, &callback)?;
294        func.set_pass_state(true);
295
296        self.0.set_formatter(move |out, state, value| {
297            func.with_func::<Option<String>>(args!(value), Some(state))
298                .ok()
299                .flatten()
300                .map(|val| {
301                    let s = val.as_str().ok_or_else(|| {
302                        JinjaError::new(
303                            JinjaErrorKind::WriteFailure,
304                            "formatter must return a string",
305                        )
306                    })?;
307                    out.write_str(s).map_err(|err| {
308                        JinjaError::new(JinjaErrorKind::WriteFailure, err.to_string())
309                    })
310                })
311                .unwrap_or(Ok(()))
312        });
313
314        Ok(())
315    }
316
317    #[lua(name = "set_syntax")]
318    pub(crate) fn lua_set_syntax(&mut self, syntax: LuaSyntaxConfig) -> mlua::Result<()> {
319        self.0.set_syntax(syntax.into());
320
321        Ok(())
322    }
323
324    #[lua(name = "render_template")]
325    pub(crate) fn lua_render_template(
326        &mut self,
327        lua: &mlua::Lua,
328        name: &str,
329        ctx: Option<mlua::Table>,
330    ) -> mlua::Result<String> {
331        let ctx: Option<JinjaValue> = ctx
332            .and_then(|t| LuaTableObject::from_value(lua, &t).ok())
333            .map(|obj| obj.into());
334
335        bind_lua(lua, || {
336            self.0
337                .get_template(name)
338                .map_err(mlua::Error::external)?
339                .render(ctx)
340                .map_err(mlua::Error::external)
341        })
342    }
343
344    #[lua(name = "render_str")]
345    pub(crate) fn lua_render_str(
346        &self,
347        lua: &mlua::Lua,
348        source: &str,
349        ctx: Option<mlua::Table>,
350        name: Option<String>,
351    ) -> mlua::Result<String> {
352        let ctx: Option<JinjaValue> = ctx
353            .and_then(|t| LuaTableObject::from_value(lua, &t).ok())
354            .map(|obj| obj.into());
355
356        let name = name.unwrap_or("<string>".to_string());
357
358        bind_lua(lua, || {
359            self.0
360                .render_named_str(&name, source, ctx)
361                .map_err(mlua::Error::external)
362        })
363    }
364
365    #[lua(name = "render_captured")]
366    pub(crate) fn lua_render_captured(
367        &mut self,
368        lua: &mlua::Lua,
369        name: &str,
370        ctx: Option<mlua::Table>,
371        callback: mlua::Function,
372    ) -> mlua::Result<mlua::MultiValue> {
373        let mut func = LuaFunctionObject::from_value(lua, &callback)?;
374        func.set_pass_state(true);
375
376        let ctx: Option<JinjaValue> = ctx
377            .and_then(|t| LuaTableObject::from_value(lua, &t).ok())
378            .map(|obj| obj.into());
379
380        bind_lua(lua, || {
381            let mut captured = self
382                .0
383                .get_template(name)
384                .map_err(mlua::Error::external)?
385                .render_captured(ctx)
386                .map_err(mlua::Error::external)?;
387
388            let mut mv = captured
389                .with_state_mut(|state| func.with_func_mut::<mlua::MultiValue>(&[], Some(state)))
390                .map_err(mlua::Error::external)?
391                .and_then(|v| minijinja_to_lua(lua, &v))
392                .unwrap_or_default();
393
394            let rendered = captured.into_output();
395
396            mv.push_front(mlua::Value::String(lua.create_string(rendered)?));
397
398            Ok(mv)
399        })
400    }
401
402    #[lua(name = "eval")]
403    pub(crate) fn lua_eval(
404        &self,
405        lua: &mlua::Lua,
406        source: &str,
407        ctx: Option<mlua::Table>,
408    ) -> mlua::Result<mlua::MultiValue> {
409        let ctx: Option<JinjaValue> = ctx
410            .and_then(|t| LuaTableObject::from_value(lua, &t).ok())
411            .map(|obj| obj.into());
412
413        bind_lua(lua, || {
414            let expr = self
415                .0
416                .compile_expression(source)
417                .map_err(mlua::Error::external)?
418                .eval(ctx)
419                .map_err(mlua::Error::external)?;
420
421            minijinja_to_lua(lua, &expr).ok_or_else(|| {
422                mlua::Error::DeserializeError("could not convert output to lua".to_string())
423            })
424        })
425    }
426
427    #[lua(name = "add_filter")]
428    pub(crate) fn lua_add_filter(
429        &mut self,
430        lua: &mlua::Lua,
431        name: String,
432        filter: mlua::Function,
433        pass_state: Option<bool>,
434    ) -> mlua::Result<()> {
435        let mut func = LuaFunctionObject::from_value(lua, &filter)?;
436        func.set_pass_state(pass_state.unwrap_or(true));
437
438        self.0
439            .add_filter(name, move |state: &State, args: JinjaRest<JinjaValue>| {
440                func.with_func::<mlua::MultiValue>(&args, Some(state))
441            });
442
443        Ok(())
444    }
445
446    #[lua(name = "remove_filter", infallible)]
447    pub(crate) fn lua_remove_filter(&mut self, name: String) {
448        self.0.remove_filter(&name)
449    }
450
451    #[lua(name = "add_test")]
452    pub(crate) fn lua_add_test(
453        &mut self,
454        lua: &mlua::Lua,
455        name: String,
456        test: mlua::Function,
457        pass_state: Option<bool>,
458    ) -> mlua::Result<()> {
459        let mut func = LuaFunctionObject::from_value(lua, &test)?;
460        func.set_pass_state(pass_state.unwrap_or(true));
461
462        self.0
463            .add_test(name, move |state: &State, args: JinjaRest<JinjaValue>| {
464                func.with_func::<bool>(&args, Some(state))
465            });
466
467        Ok(())
468    }
469
470    #[lua(name = "remove_test", infallible)]
471    pub(crate) fn lua_remove_test(&mut self, name: String) {
472        self.0.remove_test(&name)
473    }
474
475    #[lua(name = "add_global")]
476    pub(crate) fn add_global(
477        &mut self,
478        lua: &mlua::Lua,
479        name: String,
480        val: mlua::Value,
481        pass_state: Option<bool>,
482    ) -> mlua::Result<()> {
483        match val {
484            mlua::Value::Function(f) => {
485                let mut func = LuaFunctionObject::from_value(lua, &f)?;
486                func.set_pass_state(pass_state.unwrap_or(true));
487
488                self.0
489                    .add_function(name, move |state: &State, args: JinjaRest<JinjaValue>| {
490                        func.with_func::<mlua::MultiValue>(&args, Some(state))
491                    })
492            },
493            _ => self.0.add_global(name, lua_to_minijinja(lua, &val)),
494        };
495
496        Ok(())
497    }
498
499    #[lua(name = "remove_global", infallible)]
500    pub(crate) fn lua_remove_global(&mut self, name: &str) {
501        self.0.remove_global(name)
502    }
503
504    #[lua(name = "globals")]
505    pub(crate) fn lua_globals(&self, lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
506        let table = lua.create_table()?;
507
508        for (name, value) in self.0.globals() {
509            minijinja_to_lua(lua, &value)
510                .and_then(|mut v| table.set(name, v.pop_front().unwrap_or_default()).ok());
511        }
512
513        Ok(table)
514    }
515}