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