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