1use 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#[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 #[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 #[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}