1pub mod dsl;
13pub mod templater;
14
15mod modules;
16use crate::{manager::ShurikenManager, utils::resolve_path};
17use log::info;
18use mlua::{Error as LuaError, Lua};
19use modules::{
20 make_env_module, make_fs_module, make_modules, make_ninja_module, make_proc_module,
21 make_shell_module,
22};
23use regex::Regex;
24use std::{
25 fs,
26 path::{Path, PathBuf},
27};
28
29#[derive(Clone, Debug)]
30pub struct NinjaEngine {
31 preload_dir: Option<PathBuf>,
32 #[cfg(feature = "testing")]
33 pub lua: Lua,
34 #[cfg(not(feature = "testing"))]
35 lua: Lua,
36}
37
38impl NinjaEngine {
39 fn load_preloads(&self) -> Result<(), LuaError> {
40 let Some(dir) = &self.preload_dir else {
41 return Ok(());
42 };
43
44 let lua = &self.lua;
45 let globals = lua.globals();
46
47 if !dir.exists() {
48 return Ok(());
49 }
50
51 for entry in std::fs::read_dir(dir)? {
52 let path = entry?.path();
53
54 if path.extension().and_then(|s| s.to_str()) != Some("ns") {
55 continue;
56 }
57
58 let re = Regex::new(r"export\s+function\s+([a-zA-Z_][a-zA-Z0-9_]*)")
59 .map_err(|e| LuaError::RuntimeError(e.to_string()))?;
60
61 let name = path.file_stem().unwrap().to_string_lossy();
62 let raw_script =
63 std::fs::read_to_string(&path)?.replace("export function", "function __mod.");
64 let transformed = re.replace_all(&raw_script, "function __mod.$1").to_string();
65 let script = format!("local __mod = {{}}; {}\nreturn __mod", transformed);
66
67 let module: mlua::Table = lua.load(&script).eval()?;
68
69 globals.set(name.as_ref(), module)?;
70 }
71
72 Ok(())
73 }
74 pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
77 let lua = Lua::new_with(mlua::StdLib::ALL_SAFE, mlua::LuaOptions::default())?;
78 let globals = lua.globals();
79
80 let (fs, env, shell, time, json, http, log, proc) = make_modules(&lua, None).await?;
81
82 globals.set("fs", fs)?;
83 globals.set("env", env)?;
84 globals.set("shell", shell)?;
85 globals.set("time", time)?;
86 globals.set("json", json)?;
87 globals.set("http", http)?;
88 globals.set("log", log)?;
89 globals.set("proc", proc)?;
90
91 let engine = Self {
92 lua,
93 preload_dir: Some(PathBuf::from(".ninja/preloads")),
94 };
95
96 engine.load_preloads()?;
97
98 Ok(engine)
99 }
100
101 pub async fn execute(
103 &self,
104 script: &str,
105 cwd: Option<&Path>,
106 mgr: Option<ShurikenManager>,
107 ) -> Result<(), LuaError> {
108 let globals = self.lua.globals();
109
110 if let Some(mgr) = mgr {
111 let ninja = make_ninja_module(&self.lua, mgr)?;
112 globals.set("ninja", ninja)?;
113 }
114
115 let fs = make_fs_module(&self.lua, cwd)?;
116 let env = make_env_module(&self.lua, cwd)?;
117 let shell = make_shell_module(&self.lua, cwd)?;
118 let proc = make_proc_module(&self.lua, cwd)?;
119 globals.set("fs", fs)?;
120 globals.set("env", env)?;
121 globals.set("shell", shell)?;
122 globals.set("proc", proc)?;
123
124 info!("Executing lua script.");
125 self.lua.load(script).exec_async().await
126 }
127
128 pub async fn execute_file(
130 &self,
131 path: &PathBuf,
132 cwd: Option<&Path>,
133 mgr: Option<ShurikenManager>,
134 ) -> Result<(), LuaError> {
135 info!("Executing file: {:#?}", path);
136 let globals = self.lua.globals();
137
138 if let Some(mgr) = mgr {
139 let ninja = make_ninja_module(&self.lua, mgr)?;
140 globals.set("ninja", ninja)?;
141 }
142
143 let script = if let Some(cwd) = cwd {
144 let fs = make_fs_module(&self.lua, Some(cwd))?;
145 let env = make_env_module(&self.lua, Some(cwd))?;
146 let shell = make_shell_module(&self.lua, Some(cwd))?;
147 let proc = make_proc_module(&self.lua, Some(cwd))?;
148 globals.set("fs", fs)?;
149 globals.set("env", env)?;
150 globals.set("shell", shell)?;
151 globals.set("proc", proc)?;
152 fs::read_to_string(resolve_path(cwd, path))?
153 } else {
154 fs::read_to_string(path)?
155 };
156
157 self.lua.load(script).exec_async().await
158 }
159
160 pub async fn execute_function(
165 &self,
166 function: &str,
167 path: &PathBuf,
168 cwd: Option<&Path>,
169 mgr: Option<ShurikenManager>,
170 ) -> Result<(), LuaError> {
171 let lua = &self.lua;
172 let globals = self.lua.globals();
173
174 if let Some(mgr) = mgr {
175 let ninja = make_ninja_module(&self.lua, mgr)?;
176 globals.set("ninja", ninja)?;
177 }
178
179 let script = if let Some(cwd) = cwd {
180 let fs = make_fs_module(&self.lua, Some(cwd))?;
181 let env = make_env_module(&self.lua, Some(cwd))?;
182 let shell = make_shell_module(&self.lua, Some(cwd))?;
183 let proc = make_proc_module(&self.lua, Some(cwd))?;
184 globals.set("fs", fs)?;
185 globals.set("env", env)?;
186 globals.set("shell", shell)?;
187 globals.set("proc", proc)?;
188 fs::read_to_string(resolve_path(cwd, path))?
189 } else {
190 fs::read_to_string(path)?
191 };
192
193 let env = lua.create_table()?;
195
196 let globals = lua.globals();
198 env.set_metatable(Some(lua.create_table_from([("__index", globals)])?))?;
199
200 let chunk = lua.load(&script).set_environment(env.clone());
202
203 let result: mlua::Value = chunk.eval_async().await?;
205
206 let func: mlua::Function = if let mlua::Value::Table(table) = result {
208 table.get(function)?
210 } else {
211 env.get(function)?
213 };
214
215 func.call_async::<()>(()).await
217 }
218}