diff --git a/src/repo/hooks.rs b/src/repo/hooks.rs index 83e2e06..7567874 100644 --- a/src/repo/hooks.rs +++ b/src/repo/hooks.rs @@ -1,5 +1,6 @@ use embed_nu::{CommandGroupConfig, Context}; use rusty_value::*; +use serde::Serialize; use std::{ fs, mem, path::{Path, PathBuf}, @@ -23,13 +24,13 @@ impl std::fmt::Debug for HookScript { } } -#[derive(Clone, Debug, RustyValue)] +#[derive(Clone, Debug, RustyValue, Serialize)] pub struct ApplyAllContext { pub repo: PathBuf, pub paths: Vec, } -#[derive(Clone, Debug, RustyValue)] +#[derive(Clone, Debug, RustyValue, Serialize)] pub struct ApplyEachContext { pub repo: PathBuf, pub src: PathBuf, diff --git a/src/scripting/utils_module.rs b/src/scripting/utils_module.rs index 4c04a1a..e9b42ef 100644 --- a/src/scripting/utils_module.rs +++ b/src/scripting/utils_module.rs @@ -1,16 +1,46 @@ -use std::fs; +use std::{ + fs, + process::{Command, Stdio}, +}; -use mlua::{Lua, LuaSerdeExt, Result, Table}; +use mlua::{Function, Lua, LuaSerdeExt, Result, Table}; +use serde::Serialize; +/// Utility functions pub fn utils_module(lua: &Lua) -> Result { let exports = lua.create_table()?; exports.set("merge", lua.create_function(lua_merge)?)?; - exports.set("load_toml", lua.create_function(lua_read_toml)?)?; + exports.set("from_json", lua.create_function(lua_from_json)?)?; + exports.set("load_json", lua.create_function(lua_load_json)?)?; + exports.set("from_toml", lua.create_function(lua_from_toml)?)?; + exports.set("load_toml", lua.create_function(lua_load_toml)?)?; + exports.set("ext", lua.create_function(lua_ext)?)?; + exports.set("ext_piped", lua.create_function(lua_ext_piped)?)?; + + if let Ok(nu_path) = which::which("nu") { + exports.set( + "nu", + lua.create_function(move |lua, expr| { + let output = Command::new(&nu_path) + .arg("-c") + .arg::(expr) + .stdout(Stdio::piped()) + .spawn()? + .wait_with_output()?; + + let output_string = + String::from_utf8(output.stdout).map_err(mlua::Error::external)?; + + lua.to_value(&output_string) + })?, + )?; + } Ok(exports) } +/// Merges two values fn lua_merge<'a>(lua: &'a Lua, (a, b): (mlua::Value, mlua::Value)) -> Result> { let val_a: serde_json::Value = lua.from_value(a)?; let val_b: serde_json::Value = lua.from_value(b)?; @@ -19,9 +49,74 @@ fn lua_merge<'a>(lua: &'a Lua, (a, b): (mlua::Value, mlua::Value)) -> Result(lua: &'a Lua, path: String) -> Result> { +/// Parse a json string into a lua value +fn lua_from_json<'a>(lua: &'a Lua, json_string: String) -> Result> { + let toml_value: serde_json::Value = + serde_json::from_str(&json_string).map_err(mlua::Error::external)?; + + lua.to_value(&toml_value) +} + +/// Reads a json file and parses it as a lua value +fn lua_load_json<'a>(lua: &'a Lua, path: String) -> Result> { let contents = fs::read_to_string(path)?; - let toml_value: toml::Value = toml::from_str(&contents).map_err(mlua::Error::external)?; + lua_from_json(lua, contents) +} + +/// Parse a toml string into a lua value +fn lua_from_toml<'a>(lua: &'a Lua, toml_string: String) -> Result> { + let toml_value: toml::Value = toml::from_str(&toml_string).map_err(mlua::Error::external)?; lua.to_value(&toml_value) } + +/// Reads a toml file and parses it as a lua value +fn lua_load_toml<'a>(lua: &'a Lua, path: String) -> Result> { + let contents = fs::read_to_string(path)?; + lua_from_toml(lua, contents) +} + +/// Creates a new executable that can be called with a variable number of args +fn lua_ext<'a>(lua: &'a Lua, program: String) -> Result> { + lua.create_function(move |_lua, args| { + let exit_status = Command::new(&program) + .args::, _>(args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn()? + .wait()?; + if exit_status.success() { + Ok(()) + } else { + Err(mlua::Error::external(format!( + "External command `{program}` failed" + ))) + } + }) +} + +#[derive(Serialize)] +struct CommandOutput { + code: i32, + stdout: String, + stderr: String, +} + +/// Creates a new executable that can be called with a variable number of args +fn lua_ext_piped<'a>(lua: &'a Lua, program: String) -> Result> { + lua.create_function(move |lua, args| { + let cmd = Command::new(&program) + .args::, _>(args) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + let output = cmd.wait_with_output()?; + let output = CommandOutput { + code: output.status.code().unwrap_or(0), + stdout: String::from_utf8(output.stdout).map_err(mlua::Error::external)?, + stderr: String::from_utf8(output.stderr).map_err(mlua::Error::external)?, + }; + + lua.to_value(&output) + }) +}