1use crate::core::element::{Element, Render};
2pub use mlua::{IntoLua, Lua, LuaSerdeExt, Result as LuaResult, Value as LuaValue};
3use mlua::{ObjectLike, prelude::*};
4use serde::{Serialize, de::DeserializeOwned};
5use std::path::PathBuf;
6use uuid::Uuid;
7
8#[cfg(feature = "web")]
9use std::sync::{LazyLock, RwLock};
10#[cfg(feature = "web")]
11use tera::Tera;
12
13#[cfg(feature = "web")]
14static TERA: LazyLock<RwLock<Tera>> = LazyLock::new(|| RwLock::new(Tera::default()));
15
16#[macro_export]
17macro_rules! wrap_lua_runtime_err {
18 ($x:expr) => {
19 match $x {
20 Ok(x) => x,
21 Err(e) => {
22 return Err(LuaError::RuntimeError(e.to_string()));
23 }
24 }
25 };
26
27 ($x:expr, Option) => {
28 match $x {
29 Some(x) => x,
30 None => {
31 return Err(LuaError::RuntimeError("Unknown error".to_string()));
32 }
33 }
34 };
35}
36
37pub struct LuaContext();
39
40impl LuaContext {
41 fn lua_fs(lua: &Lua) -> LuaResult<LuaTable> {
43 let v = lua.create_table()?;
44 v.set(
45 "read",
46 lua.create_function(|l, p: String| {
47 Ok(wrap_lua_runtime_err!(std::fs::read_to_string(p)).into_lua(l)?)
48 })?,
49 )?;
50 v.set(
51 "write",
52 lua.create_function(|_, (p, c): (String, String)| {
53 wrap_lua_runtime_err!(std::fs::write(p, c));
54 Ok(())
55 })?,
56 )?;
57 v.set(
58 "copy",
59 lua.create_function(|_, (f, t): (String, String)| {
60 wrap_lua_runtime_err!(std::fs::copy(f, t));
61 Ok(())
62 })?,
63 )?;
64 v.set(
65 "create_dir",
66 lua.create_function(|_, p: String| {
67 wrap_lua_runtime_err!(std::fs::create_dir(p));
68 Ok(())
69 })?,
70 )?;
71 v.set(
72 "create_dir_all",
73 lua.create_function(|_, p: String| {
74 wrap_lua_runtime_err!(std::fs::create_dir_all(p));
75 Ok(())
76 })?,
77 )?;
78 v.set(
79 "remove_file",
80 lua.create_function(|_, p: String| {
81 wrap_lua_runtime_err!(std::fs::remove_file(p));
82 Ok(())
83 })?,
84 )?;
85 v.set(
86 "remove_dir",
87 lua.create_function(|_, p: String| {
88 wrap_lua_runtime_err!(std::fs::remove_dir(p));
89 Ok(())
90 })?,
91 )?;
92 v.set(
93 "rename",
94 lua.create_function(|_, (f, t): (String, String)| {
95 wrap_lua_runtime_err!(std::fs::rename(f, t));
96 Ok(())
97 })?,
98 )?;
99 v.set(
100 "exists",
101 lua.create_function(|_, path: String| {
102 Ok(wrap_lua_runtime_err!(std::fs::exists(path)))
103 })?,
104 )?;
105 v.set(
106 "read_dir",
107 lua.create_function(|_, path: String| {
108 Ok(wrap_lua_runtime_err!(std::fs::read_dir(path))
109 .map(|x| x.unwrap().path().to_str().unwrap().to_string())
110 .collect::<Vec<String>>())
111 })?,
112 )?;
113 v.set(
114 "stat",
115 lua.create_function(|l, path: String| {
116 let v = l.create_table()?;
117 let stat = wrap_lua_runtime_err!(std::fs::metadata(path));
118
119 v.set("is_file", stat.is_file())?;
120 v.set("is_dir", stat.is_dir())?;
121 v.set("is_symlink", stat.is_symlink())?;
122 v.set("len", stat.len())?;
123
124 Ok(v)
125 })?,
126 )?;
127 Ok(v)
128 }
129
130 fn lua_wclm(lua: &Lua) -> LuaResult<LuaTable> {
132 let v = lua.create_table()?;
133 v.set(
134 "parse",
135 lua.create_function(|l, v: LuaString| {
136 Ok(l.to_value(&crate::parse(&wrap_lua_runtime_err!(v.to_str())))?)
137 })?,
138 )?;
139 v.set(
140 "render",
141 lua.create_function(|l, v: LuaTable| {
142 Ok(l.from_value::<Element>(v.to_value())?.render_safe())
143 })?,
144 )?;
145 Ok(v)
146 }
147
148 #[cfg(feature = "web")]
150 fn lua_tera(lua: &Lua) -> LuaResult<LuaTable> {
151 let v = lua.create_table()?;
152 v.set(
153 "add",
154 lua.create_function(|_, (name, value): (String, String)| {
155 let mut tera = TERA.write().expect("failed to lock tera state for write");
156 wrap_lua_runtime_err!(tera.add_raw_template(&name, &value));
157 Ok(())
158 })?,
159 )?;
160 v.set(
161 "render",
162 lua.create_function(|l, (tmpl, ctx): (String, LuaTable)| {
163 let tera = TERA.read().expect("failed to lock tera state for read");
164 Ok(wrap_lua_runtime_err!(tera.render(
165 &tmpl,
166 &wrap_lua_runtime_err!(tera::Context::from_value(
167 l.from_value::<serde_json::Value>(ctx.to_value())?,
168 )),
169 ))
170 .into_lua(l)?)
171 })?,
172 )?;
173 Ok(v)
174 }
175
176 fn lua_features(lua: &Lua, args: String) -> LuaResult<LuaTable> {
178 let v = lua.create_table()?;
179 v.set("NULL", lua.null())?;
180 v.set("args", {
181 let t = lua.create_table()?;
182
183 for arg in args.split(" ") {
184 t.push(arg)?;
185 }
186
187 t
188 })?;
189 v.set("rargs", args)?;
190 v.set(
191 "match",
192 lua.create_function(|_, (value, matches): (LuaValue, LuaTable)| {
193 if let Ok(x) = matches.get::<LuaValue>(&value)
195 && !x.is_nil()
196 {
197 if x.is_function() {
198 return Ok(x.as_function().unwrap().call::<LuaValue>(())?);
199 } else {
200 return Ok(x);
201 }
202 }
203
204 if let Ok(x) = matches.get::<LuaValue>("_")
206 && !x.is_nil()
207 {
208 if x.is_function() {
209 return Ok(x.as_function().unwrap().call::<LuaValue>(())?);
210 } else {
211 return Ok(x);
212 }
213 }
214
215 Err(LuaError::RuntimeError(format!(
217 "No matches available ({})",
218 value.as_string().unwrap().to_str()?
219 )))
220 })?,
221 )?;
222 v.set(
223 "throw",
224 lua.create_function(|_, value: String| {
225 Err::<(), LuaError>(LuaError::RuntimeError(value))
226 })?,
227 )?;
228 v.set("macros", {
229 let v = lua.create_table()?;
230 v.set(
231 "define",
232 lua.create_function(|l, (name, func): (String, LuaFunction)| {
233 l.globals().set(format!("__WEBC_MACROS_{name}"), func)?;
234 Ok(())
235 })?,
236 )?;
237 v.set(
238 "call",
239 lua.create_async_function(async |l, (name, args): (String, LuaTable)| {
240 let f: LuaFunction = l.globals().get(format!("__WEBC_MACROS_{name}"))?;
241 Ok(f.call_async::<LuaTable>(args).await?)
242 })?,
243 )?;
244 v
245 })?;
246 Ok(v)
247 }
248
249 fn lua_tools(lua: &Lua) -> LuaResult<LuaTable> {
250 let v = lua.create_table()?;
251 v.set(
252 "sha256",
253 lua.create_function(|_, input: String| Ok(crate::globber::hash(input)))?,
254 )?;
255 v.set(
256 "uuid",
257 lua.create_function(|_, ()| Ok(Uuid::new_v4().to_string()))?,
258 )?;
259 v.set(
260 "uuid7",
261 lua.create_function(|_, ()| Ok(Uuid::now_v7().to_string()))?,
262 )?;
263 v.set(
264 "id",
265 lua.create_function(|_, ()| Ok(tritools::id::Id::new().printable()))?,
266 )?;
267 v.set(
268 "salt",
269 lua.create_function(|_, ()| Ok(tritools::encoding::salt()))?,
270 )?;
271 v.set(
272 "salt_len",
273 lua.create_function(|_, x: usize| Ok(tritools::encoding::salt_len(x)))?,
274 )?;
275 v.set(
276 "unix_epoch_timestamp",
277 lua.create_function(|_, ()| Ok(tritools::time::unix_epoch_timestamp()))?,
278 )?;
279 v.set(
280 "epoch_timestamp",
281 lua.create_function(|_, e: u32| Ok(tritools::time::epoch_timestamp(e)))?,
282 )?;
283 v.set(
284 "compress",
285 lua.create_function(|_, x: Vec<u8>| Ok(tritools::encoding::compress(x)))?,
286 )?;
287 v.set(
288 "decompress",
289 lua.create_function(|_, x: Vec<u8>| Ok(tritools::encoding::decompress(x)))?,
290 )?;
291 Ok(v)
292 }
293
294 fn lua_string(lua: &Lua) -> LuaResult<LuaTable> {
295 let v = lua.create_table()?;
296 v.set(
297 "replace",
298 lua.create_function(|_, (haystack, from, to): (String, String, String)| {
299 Ok(haystack.replace(&from, &to).to_string())
300 })?,
301 )?;
302 v.set(
303 "replacen",
304 lua.create_function(
305 |_, (haystack, from, to, n): (String, String, String, usize)| {
306 Ok(haystack.replacen(&from, &to, n).to_string())
307 },
308 )?,
309 )?;
310 v.set(
311 "trim",
312 lua.create_function(|_, input: String| Ok(input.trim().to_string()))?,
313 )?;
314 v.set(
315 "trim_end",
316 lua.create_function(|_, input: String| Ok(input.trim_end().to_string()))?,
317 )?;
318 v.set(
319 "trim_start",
320 lua.create_function(|_, input: String| Ok(input.trim_start().to_string()))?,
321 )?;
322 v.set(
323 "regex_does_match",
324 lua.create_function(|_, (pat, haystack): (String, String)| {
325 let regex = regex::RegexBuilder::new(&pat)
326 .multi_line(true)
327 .build()
328 .unwrap();
329
330 Ok(regex.captures(&haystack).is_some())
331 })?,
332 )?;
333 v.set(
334 "chars",
335 lua.create_function(|_, input: String| Ok(input.chars().collect::<Vec<char>>()))?,
336 )?;
337 v.set(
338 "mime",
339 lua.create_function(|_, input: String| {
340 Ok(mime_guess::from_path(input).first().unwrap().to_string())
341 })?,
342 )?;
343 v.set(
344 "mime_ext",
345 lua.create_function(|_, input: String| {
346 Ok(mime_guess::from_ext(&input).first().unwrap().to_string())
347 })?,
348 )?;
349 Ok(v)
350 }
351
352 fn lua_array(lua: &Lua) -> LuaResult<LuaTable> {
353 let v = lua.create_table()?;
354 v.set(
355 "contains",
356 lua.create_function(|_, (input, v): (LuaTable, LuaValue)| {
357 Ok(input
358 .pairs::<LuaNumber, LuaValue>()
359 .find(|x| v == x.as_ref().unwrap().1)
360 .is_some())
361 })?,
362 )?;
363 v.set(
364 "push",
365 lua.create_function(|_, (input, v): (LuaTable, LuaValue)| Ok(input.push(v)))?,
366 )?;
367 v.set(
368 "pop",
369 lua.create_function(|_, input: LuaTable| Ok(input.pop::<LuaValue>()))?,
370 )?;
371 v.set(
372 "remove",
373 lua.create_function(|_, (input, k): (LuaTable, LuaValue)| Ok(input.raw_remove(k)))?,
374 )?;
375 v.set(
376 "concat",
377 lua.create_function(|_, (a, b): (LuaTable, LuaTable)| {
378 let a: Vec<(LuaValue, LuaValue)> = a.pairs().map(|x| x.unwrap()).collect();
379 let b: Vec<(LuaValue, LuaValue)> = b.pairs().map(|x| x.unwrap()).collect();
380 let c = &[a, b].concat();
381 Ok(c.iter().map(|x| x.1.to_owned()).collect::<Vec<LuaValue>>())
382 })?,
383 )?;
384 v.set(
385 "concat_table",
386 lua.create_function(|l, (a, b): (LuaTable, LuaTable)| {
387 let a: Vec<(LuaValue, LuaValue)> = a.pairs().map(|x| x.unwrap()).collect();
388 let b: Vec<(LuaValue, LuaValue)> = b.pairs().map(|x| x.unwrap()).collect();
389 let c = &[a, b].concat();
390 let d = l.create_table()?;
391
392 for (x, y) in c {
393 d.set(x, y)?;
394 }
395
396 Ok(d)
397 })?,
398 )?;
399 Ok(v)
400 }
401
402 fn lua_memory(lua: &Lua) -> LuaResult<LuaTable> {
403 let v = lua.create_table()?;
404 v.set(
405 "clone",
406 lua.create_function(|_, v: LuaValue| Ok(v.clone()))?,
407 )?;
408 v.set(
409 "str_bytes",
410 lua.create_function(|_, v: String| Ok(v.as_bytes().to_vec()))?,
411 )?;
412 v.set(
413 "str_from_bytes",
414 lua.create_function(|_, v: Vec<u8>| Ok(String::from_utf8_lossy(&v).to_string()))?,
415 )?;
416 v.set(
417 "num_le_bytes",
418 lua.create_function(|_, v: LuaNumber| Ok(v.to_le_bytes().to_vec()))?,
419 )?;
420 v.set(
421 "num_be_bytes",
422 lua.create_function(|_, v: LuaNumber| Ok(v.to_be_bytes().to_vec()))?,
423 )?;
424 v.set("used", lua.create_function(|l, ()| Ok(l.used_memory()))?)?;
425 v.set(
426 "size_of_val",
427 lua.create_function(|_, v: LuaValue| Ok(std::mem::size_of_val(&v)))?,
428 )?;
429 v.set(
430 "compile",
431 lua.create_function(|_, v: String| {
432 let c = mlua::Compiler::new();
433 Ok(wrap_lua_runtime_err!(c.compile(v)))
434 })?,
435 )?;
436 Ok(v)
437 }
438
439 pub fn lua() -> Lua {
441 Lua::new()
442 }
443
444 pub fn lua_exec_with(
446 lua: &Lua,
447 script: Vec<u8>,
448 entry_path: String,
449 args: String,
450 ) -> LuaResult<LuaValue> {
451 let entry_path_path = PathBuf::from(entry_path.clone());
452
453 lua.register_module("@webc/fs", Self::lua_fs(&lua)?)?;
455 lua.register_module("@webc/json", crate::serde::lua_json(&lua)?)?;
456 lua.register_module("@webc/toml", crate::serde::lua_toml(&lua)?)?;
457 lua.register_module("@webc/wclm", Self::lua_wclm(&lua)?)?;
458 lua.register_module("@webc/features", Self::lua_features(&lua, args)?)?;
459 lua.register_module("@webc/async", crate::tokio::lua_async(&lua)?)?;
460 lua.register_module("@webc/tools", Self::lua_tools(&lua)?)?;
461 lua.register_module("@webc/string", Self::lua_string(&lua)?)?;
462 lua.register_module("@webc/array", Self::lua_array(&lua)?)?;
463 lua.register_module("@webc/memory", Self::lua_memory(&lua)?)?;
464 lua.register_module("@webc/object", crate::object::lua_object(&lua)?)?;
465 lua.register_module("@webc/process", crate::process::lua_process(&lua)?)?;
466 #[cfg(feature = "web")]
467 lua.register_module("@webc/tera", Self::lua_tera(&lua)?)?;
468 #[cfg(feature = "web")]
469 lua.register_module("@webc/http", crate::http::lua_http(&lua)?)?;
470 #[cfg(feature = "globber")]
471 lua.register_module("@webc/globber", crate::globber::lua_globber(&lua)?)?;
472 #[cfg(feature = "db")]
473 lua.register_module("@webc/sqlite", crate::db::sqlite::lua_sqlite(&lua)?)?;
474 #[cfg(feature = "db")]
475 lua.register_module("@webc/psql", crate::db::psql::lua_psql(&lua)?)?;
476 #[cfg(feature = "db")]
477 lua.register_module("@webc/redis", crate::db::redis::lua_redis(&lua)?)?;
478
479 crate::collections::array::LuaArray::create(&lua);
481 crate::collections::hashmap::LuaHashMap::create(&lua);
482
483 let temp = entry_path_path.canonicalize().unwrap();
485 let entry_path_path_str = temp.to_str().unwrap();
486
487 Ok(lua
488 .load(script)
489 .set_name(format!(
490 "@{}",
491 entry_path_path_str
492 .strip_suffix(".luau")
493 .unwrap_or(entry_path_path_str)
494 ))
495 .eval()?)
496 }
497
498 pub fn lua_exec_capture_with<T>(
500 lua: &Lua,
501 script: Vec<u8>,
502 entry_path: String,
503 args: String,
504 ) -> LuaResult<T>
505 where
506 T: Serialize + DeserializeOwned,
507 {
508 lua.from_value(Self::lua_exec_with(lua, script, entry_path, args)?)
509 }
510
511 pub fn lua_exec(script: Vec<u8>, entry_path: String, args: String) -> LuaResult<LuaValue> {
513 Self::lua_exec_with(&Self::lua(), script, entry_path, args)
514 }
515
516 pub fn lua_exec_with_instance(
518 lua: &Lua,
519 script: Vec<u8>,
520 entry_path: String,
521 args: String,
522 ) -> LuaResult<LuaValue> {
523 Self::lua_exec_with(&lua, script, entry_path, args)
524 }
525
526 pub fn lua_exec_capture<T>(script: Vec<u8>, entry_path: String, args: String) -> LuaResult<T>
528 where
529 T: Serialize + DeserializeOwned,
530 {
531 Self::lua_exec_capture_with(&Self::lua(), script, entry_path, args)
532 }
533
534 pub fn lua_exec_on_with<T>(
536 lua: Lua,
537 ctx: T,
538 script: Vec<u8>,
539 entry_path: String,
540 args: String,
541 ) -> LuaResult<T>
542 where
543 T: Serialize + DeserializeOwned,
544 {
545 lua.globals().set("ctx", lua.to_value(&ctx)?)?;
546 Self::lua_exec_capture_with(&lua, script, entry_path, args)
547 }
548
549 pub fn lua_exec_on<T>(ctx: T, script: Vec<u8>, entry_path: String, args: String) -> LuaResult<T>
551 where
552 T: Serialize + DeserializeOwned,
553 {
554 let lua = Self::lua();
555 lua.globals().set("ctx", lua.to_value(&ctx)?)?;
556 Self::lua_exec_capture_with(&lua, script, entry_path, args)
557 }
558}