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