1use crate::LuaEnvironment;
4
5pub(crate) fn minijinja_types(val: mlua::Value) -> mlua::Result<String> {
9 let name = match val {
10 mlua::Value::UserData(ud) if ud.is::<LuaEnvironment>() => "environment",
11 mlua::Value::UserData(ud) if ud.type_name().is_ok_and(|s| s == "state") => "state",
12 val if val.is_null() => "none",
13 _ => val.type_name(),
14 };
15
16 Ok(name.to_string())
17}
18
19pub(crate) fn minijinja_path_loader(lua: &mlua::Lua) -> mlua::Result<mlua::Function> {
23 lua.load(
24 r#"
25 local function path_loader(paths)
26 if type(paths) == "string" then
27 paths = { paths }
28 end
29
30 local function loader(name)
31 if name:match("\\") then return nil end
32
33 name = name:gsub("^/+", ""):gsub("/+$", "")
34
35 local sep = package.config:sub(1,1)
36 local pattern = "([^" .. sep .. "]+)"
37
38 local splits = {}
39 for piece in name:gmatch(pattern) do
40 if ".." == piece then return nil end
41 table.insert(splits, piece)
42 end
43
44 for _, path in ipairs(paths) do
45 local p = path .. sep .. table.concat(splits, sep)
46 local file = io.open(p, "r")
47
48 if file then
49 local source = file:read("*a")
50 file:close()
51
52 return source
53 end
54 end
55 end
56
57 return loader
58 end
59
60 return path_loader
61 "#,
62 )
63 .eval()
64}
65
66#[cfg(feature = "json")]
68pub mod json {
69 use minijinja::{Error as JinjaError, ErrorKind as JinjaErrorKind, State, Value as JinjaValue};
70
71 use crate::convert::err_to_minijinja_err;
72
73 pub fn add_to_environment(env: &mut minijinja::Environment) {
75 env.add_filter("fromjson", fromjson);
76 }
77
78 pub fn fromjson(_: &State, json: &[u8]) -> Result<JinjaValue, JinjaError> {
82 serde_json::from_slice(json)
83 .map_err(|err| err_to_minijinja_err(err, JinjaErrorKind::BadSerialization))
84 }
85}
86
87#[cfg(feature = "datetime")]
89pub mod datetime {
90 use jiff::civil::{Date, Time};
91 use minijinja::{
92 Error as JinjaError,
93 ErrorKind as JinjaErrorKind,
94 State,
95 Value as JinjaValue,
96 value::Kwargs,
97 };
98
99 use crate::convert::err_to_minijinja_err;
100
101 pub fn add_to_environment(env: &mut minijinja::Environment) {
103 env.add_filter("datefmt", datefmt);
104 env.add_filter("timefmt", timefmt);
105 }
106
107 pub fn datefmt(_: &State, value: JinjaValue, kwargs: Kwargs) -> Result<String, JinjaError> {
119 let format = kwargs.get::<Option<&str>>("format")?;
120 let patterns = kwargs.get::<Option<Vec<String>>>("patterns")?;
121 kwargs.assert_all_used()?;
122
123 let date = match value.as_str() {
124 Some(s) => {
125 if let Some(date) = patterns
127 .iter()
128 .flatten()
129 .find_map(|f| Date::strptime(f, s).ok())
130 {
131 Ok(date)
132 } else {
133 s.parse::<Date>()
135 .map_err(|err| err_to_minijinja_err(err, JinjaErrorKind::CannotDeserialize))
136 }
137 },
138 None => Err(JinjaError::new(
139 JinjaErrorKind::CannotDeserialize,
140 "could not parse value as a string",
141 )),
142 }?;
143
144 Ok(match format {
145 Some(f) => date.strftime(f).to_string(),
146 None => date.to_string(),
147 })
148 }
149
150 pub fn timefmt(_: &State, value: JinjaValue, kwargs: Kwargs) -> Result<String, JinjaError> {
162 let format = kwargs.get::<Option<&str>>("format")?;
163 let patterns = kwargs.get::<Option<Vec<String>>>("patterns")?;
164 kwargs.assert_all_used()?;
165
166 let time = match value.as_str() {
167 Some(s) => {
168 if let Some(date) = patterns
170 .iter()
171 .flatten()
172 .find_map(|f| Time::strptime(f, s).ok())
173 {
174 Ok(date)
175 } else {
176 s.parse::<Time>()
178 .map_err(|err| err_to_minijinja_err(err, JinjaErrorKind::CannotDeserialize))
179 }
180 },
181 None => Err(JinjaError::new(
182 JinjaErrorKind::CannotDeserialize,
183 "could not parse value as a string",
184 )),
185 }?;
186
187 Ok(match format {
188 Some(f) => time.strftime(f).to_string(),
189 None => time.to_string(),
190 })
191 }
192}
193
194#[cfg(test)]
195mod test {
196 use minijinja::context;
197 use serde_json::json;
198
199 use super::*;
200 use crate::state::{LuaStateMut, LuaStateRef};
201
202 #[test]
203 fn test_minijinja_types_environment() {
204 let lua = mlua::Lua::new();
205 let env = lua.create_userdata(LuaEnvironment::lua_new()).unwrap();
206
207 assert_eq!(
208 minijinja_types(mlua::Value::UserData(env)).unwrap(),
209 "environment"
210 );
211 }
212
213 #[test]
214 fn test_minijinja_types_state() {
215 let lua = mlua::Lua::new();
216 let env = minijinja::Environment::new();
217 let state = &env.empty_state();
218
219 lua.scope(|scope| {
220 let ud = scope.create_userdata::<LuaStateRef>(state.into()).unwrap();
221 assert_eq!(minijinja_types(mlua::Value::UserData(ud)).unwrap(), "state");
222 Ok(())
223 })
224 .unwrap();
225 }
226
227 #[test]
228 fn test_minijinja_types_state_mut() {
229 let lua = mlua::Lua::new();
230 let env = minijinja::Environment::new();
231 let state = &mut env.empty_state();
232
233 lua.scope(|scope| {
234 let ud = scope.create_userdata::<LuaStateMut>(state.into()).unwrap();
235 assert_eq!(minijinja_types(mlua::Value::UserData(ud)).unwrap(), "state");
236 Ok(())
237 })
238 .unwrap();
239 }
240
241 #[test]
242 fn test_minijinja_types_none() {
243 assert_eq!(minijinja_types(mlua::Value::NULL).unwrap(), "none");
244 }
245
246 #[test]
247 fn test_minijinja_types_lua() {
248 let lua = mlua::Lua::new();
249
250 assert_eq!(minijinja_types(mlua::Value::Nil).unwrap(), "nil");
251 assert_eq!(
252 minijinja_types(mlua::Value::Boolean(true)).unwrap(),
253 "boolean"
254 );
255 assert_eq!(
256 minijinja_types(mlua::Value::Function(
257 lua.create_function(|_, ()| Ok(())).unwrap()
258 ))
259 .unwrap(),
260 "function"
261 );
262 assert_eq!(
263 minijinja_types(mlua::Value::Integer(99)).unwrap(),
264 "integer"
265 );
266 assert_eq!(
267 minijinja_types(mlua::Value::Number(99.99)).unwrap(),
268 "number"
269 );
270 assert_eq!(
271 minijinja_types(mlua::Value::String(lua.create_string("foo").unwrap())).unwrap(),
272 "string"
273 );
274 assert_eq!(
275 minijinja_types(mlua::Value::Table(lua.create_table().unwrap())).unwrap(),
276 "table"
277 );
278 assert_eq!(
279 minijinja_types(mlua::Value::Thread(
280 lua.create_thread(lua.create_function(|_, ()| Ok(())).unwrap())
281 .unwrap()
282 ))
283 .unwrap(),
284 "thread"
285 );
286 }
287
288 #[test]
289 #[cfg(feature = "json")]
290 fn test_minijinja_from_json_filter() {
291 let mut env = minijinja::Environment::new();
292 json::add_to_environment(&mut env);
293
294 let ex = json!({"1": 1, "2": 2, "three": [1,2,3]});
295 let expr = env.compile_expression("te | fromjson").unwrap();
296
297 let res = expr.eval(context! { te => ex.to_string() }).unwrap();
298
299 assert_eq!(res, minijinja::Value::from_serialize(ex));
300 }
301
302 #[test]
303 #[cfg(feature = "datetime")]
304 fn test_minijinja_datefmt_filter() {
305 let mut env = minijinja::Environment::new();
306 datetime::add_to_environment(&mut env);
307
308 let date = "2000-01-01";
309 let ex = "2000-01-01";
310
311 let expr = env.compile_expression("te | datefmt").unwrap();
312 let res = expr.eval(context! { te => date }).unwrap();
313
314 assert_eq!(res.as_str().unwrap(), ex, "{} should parse to {}", date, ex);
315 }
316
317 #[test]
318 #[cfg(feature = "datetime")]
319 fn test_minijinja_datefmt_filter_format() {
320 let mut env = minijinja::Environment::new();
321 datetime::add_to_environment(&mut env);
322
323 let date: &str = "2000-01-01T11:12:13";
324 let ex = "January 1, 2000";
325 let fmt = "%B %-d, %Y";
326
327 let te = format!("te | datefmt(format='{}')", fmt);
328 let expr = env.compile_expression(&te).unwrap();
329
330 let res = expr.eval(context! { te => date }).unwrap();
331
332 assert_eq!(res.as_str().unwrap(), ex, "{} should parse to {}", date, ex);
333 }
334
335 #[test]
336 #[cfg(feature = "datetime")]
337 fn test_minijinja_datefmt_filter_parse() {
338 let mut env = minijinja::Environment::new();
339 datetime::add_to_environment(&mut env);
340
341 let date = "2026 1 January";
342 let ex = "2026-01-01";
343 let patt = "%Y %-d %B";
344
345 let te = format!("te | datefmt(patterns=['{}'])", patt);
346 let expr = env.compile_expression(&te).unwrap();
347
348 let res = expr.eval(context! { te => date }).unwrap();
349
350 assert_eq!(res.as_str().unwrap(), ex, "{} should parse to {}", date, ex);
351 }
352
353 #[test]
354 #[cfg(feature = "datetime")]
355 fn test_minijinja_timefmt_filter() {
356 let mut env = minijinja::Environment::new();
357 datetime::add_to_environment(&mut env);
358
359 let time = "2000-01-01T11:12:13";
360 let ex = "11:12:13";
361
362 let expr = env.compile_expression("te | timefmt").unwrap();
363 let res = expr.eval(context! { te => time }).unwrap();
364
365 assert_eq!(res.as_str().unwrap(), ex, "{} should parse to {}", time, ex);
366 }
367
368 #[test]
369 #[cfg(feature = "datetime")]
370 fn test_minijinja_timefmt_filter_format() {
371 let mut env = minijinja::Environment::new();
372 datetime::add_to_environment(&mut env);
373
374 let time = "12:02:31";
375 let ex = "31:02:12";
376 let fmt = "%S:%M:%H";
377
378 let te = format!("te | timefmt(format='{}')", fmt);
379 let expr = env.compile_expression(&te).unwrap();
380
381 let res = expr.eval(context! { te => time }).unwrap();
382
383 assert_eq!(res.as_str().unwrap(), ex, "{} should parse to {}", time, ex);
384 }
385
386 #[test]
387 #[cfg(feature = "datetime")]
388 fn test_minijinja_timefmt_filter_parse() {
389 let mut env = minijinja::Environment::new();
390 datetime::add_to_environment(&mut env);
391
392 let time = "04 02 09";
393 let ex = "02:04:09";
394 let patt = "%M %H %S";
395
396 let te = format!("te | timefmt(patterns=['{}'])", patt);
397 let expr = env.compile_expression(&te).unwrap();
398
399 let res = expr.eval(context! { te => time }).unwrap();
400
401 assert_eq!(res.as_str().unwrap(), ex, "{} should parse to {}", time, ex);
402 }
403}