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