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