pub mod dsl;
pub mod templater;
mod modules;
use crate::{manager::ShurikenManager, utils::resolve_path};
use log::info;
use mlua::{Error as LuaError, Lua};
use modules::{
make_env_module, make_fs_module, make_modules, make_ninja_module, make_proc_module,
make_shell_module,
};
use regex::Regex;
use std::{
fs,
path::{Path, PathBuf},
};
#[derive(Clone, Debug)]
pub struct NinjaEngine {
preload_dir: Option<PathBuf>,
#[cfg(feature = "testing")]
pub lua: Lua,
#[cfg(not(feature = "testing"))]
lua: Lua,
}
impl NinjaEngine {
fn load_preloads(&self) -> Result<(), LuaError> {
let Some(dir) = &self.preload_dir else {
return Ok(());
};
let lua = &self.lua;
let globals = lua.globals();
if !dir.exists() {
return Ok(());
}
for entry in std::fs::read_dir(dir)? {
let path = entry?.path();
if path.extension().and_then(|s| s.to_str()) != Some("ns") {
continue;
}
let re = Regex::new(r"export\s+function\s+([a-zA-Z_][a-zA-Z0-9_]*)")
.map_err(|e| LuaError::RuntimeError(e.to_string()))?;
let name = path.file_stem().unwrap().to_string_lossy();
let raw_script =
std::fs::read_to_string(&path)?.replace("export function", "function __mod.");
let transformed = re.replace_all(&raw_script, "function __mod.$1").to_string();
let script = format!("local __mod = {{}}; {}\nreturn __mod", transformed);
let module: mlua::Table = lua.load(&script).eval()?;
globals.set(name.as_ref(), module)?;
}
Ok(())
}
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
let lua = Lua::new_with(mlua::StdLib::ALL_SAFE, mlua::LuaOptions::default())?;
let globals = lua.globals();
let (fs, env, shell, time, json, http, log, proc) = make_modules(&lua, None).await?;
globals.set("fs", fs)?;
globals.set("env", env)?;
globals.set("shell", shell)?;
globals.set("time", time)?;
globals.set("json", json)?;
globals.set("http", http)?;
globals.set("log", log)?;
globals.set("proc", proc)?;
let engine = Self {
lua,
preload_dir: Some(PathBuf::from(".ninja/preloads")),
};
engine.load_preloads()?;
Ok(engine)
}
pub async fn execute(
&self,
script: &str,
cwd: Option<&Path>,
mgr: Option<ShurikenManager>,
) -> Result<(), LuaError> {
let globals = self.lua.globals();
if let Some(mgr) = mgr {
let ninja = make_ninja_module(&self.lua, mgr)?;
globals.set("ninja", ninja)?;
}
let fs = make_fs_module(&self.lua, cwd)?;
let env = make_env_module(&self.lua, cwd)?;
let shell = make_shell_module(&self.lua, cwd)?;
let proc = make_proc_module(&self.lua, cwd)?;
globals.set("fs", fs)?;
globals.set("env", env)?;
globals.set("shell", shell)?;
globals.set("proc", proc)?;
info!("Executing lua script.");
self.lua.load(script).exec_async().await
}
pub async fn execute_file(
&self,
path: &PathBuf,
cwd: Option<&Path>,
mgr: Option<ShurikenManager>,
) -> Result<(), LuaError> {
info!("Executing file: {:#?}", path);
let globals = self.lua.globals();
if let Some(mgr) = mgr {
let ninja = make_ninja_module(&self.lua, mgr)?;
globals.set("ninja", ninja)?;
}
let script = if let Some(cwd) = cwd {
let fs = make_fs_module(&self.lua, Some(cwd))?;
let env = make_env_module(&self.lua, Some(cwd))?;
let shell = make_shell_module(&self.lua, Some(cwd))?;
let proc = make_proc_module(&self.lua, Some(cwd))?;
globals.set("fs", fs)?;
globals.set("env", env)?;
globals.set("shell", shell)?;
globals.set("proc", proc)?;
fs::read_to_string(resolve_path(cwd, path))?
} else {
fs::read_to_string(path)?
};
self.lua.load(script).exec_async().await
}
pub async fn execute_function(
&self,
function: &str,
path: &PathBuf,
cwd: Option<&Path>,
mgr: Option<ShurikenManager>,
) -> Result<(), LuaError> {
let lua = &self.lua;
let globals = self.lua.globals();
if let Some(mgr) = mgr {
let ninja = make_ninja_module(&self.lua, mgr)?;
globals.set("ninja", ninja)?;
}
let script = if let Some(cwd) = cwd {
let fs = make_fs_module(&self.lua, Some(cwd))?;
let env = make_env_module(&self.lua, Some(cwd))?;
let shell = make_shell_module(&self.lua, Some(cwd))?;
let proc = make_proc_module(&self.lua, Some(cwd))?;
globals.set("fs", fs)?;
globals.set("env", env)?;
globals.set("shell", shell)?;
globals.set("proc", proc)?;
fs::read_to_string(resolve_path(cwd, path))?
} else {
fs::read_to_string(path)?
};
let env = lua.create_table()?;
let globals = lua.globals();
env.set_metatable(Some(lua.create_table_from([("__index", globals)])?))?;
let chunk = lua.load(&script).set_environment(env.clone());
let result: mlua::Value = chunk.eval_async().await?;
let func: mlua::Function = if let mlua::Value::Table(table) = result {
table.get(function)?
} else {
env.get(function)?
};
func.call_async::<()>(()).await
}
}