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