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, 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}