diff --git a/andax/src/fns/io.rs b/andax/src/fns/io.rs index 46ef9da..2e14198 100644 --- a/andax/src/fns/io.rs +++ b/andax/src/fns/io.rs @@ -9,14 +9,18 @@ use rhai::{ use std::io::Write; use std::process::Command; use tracing::{debug, instrument}; + macro_rules! _sh_out { ($ctx:expr, $o:expr) => { Ok(( - $o.status.code().ok_or::>("No exit code".into())?, + _sh_out!($o)?, String::from_utf8($o.stdout).ehdl($ctx)?, String::from_utf8($o.stderr).ehdl($ctx)?, )) }; + ($o:expr) => {{ + $o.status.code().ok_or::>("No exit code".into()) + }}; } macro_rules! _cmd { ($cmd:expr) => {{ @@ -32,6 +36,12 @@ macro_rules! _cmd { }}; } +macro_rules! _stream_cmd { + ($cmd:expr) => {{ + _cmd!($cmd).stdout(Stdio::inherit()).stderr(Stdio::inherit()) + }}; +} + type T = Result<(i32, String, String), Box>; /// for andax, shell(): @@ -46,6 +56,22 @@ type T = Result<(i32, String, String), Box>; /// We will let rhai handle all the nasty things. #[export_module] pub mod ar { + use core::str::FromStr; + use std::process::Stdio; + + macro_rules! die { + ($id:literal, $expect:expr, $found:expr) => {{ + let mut e = rhai::Map::new(); + let mut inner = std::collections::BTreeMap::new(); + e.insert("outcome".into(), rhai::Dynamic::from_str("fatal").unwrap()); + inner.insert("kind".into(), rhai::Dynamic::from_str($id).unwrap()); + inner.insert("expect".into(), rhai::Dynamic::from_str($expect).unwrap()); + inner.insert("found".into(), rhai::Dynamic::from_str($found).unwrap()); + e.insert("ctx".into(), rhai::Dynamic::from_map(inner)); + e + }}; + } + /// get the return code from the return value of `sh()` #[rhai_fn(global)] pub fn sh_rc(o: (i32, String, String)) -> i32 { @@ -62,6 +88,87 @@ pub mod ar { o.2 } + fn _parse_io_opt(opt: Option<&mut rhai::Dynamic>) -> Result, rhai::Map> { + let Some(s) = opt else { return Ok(Stdio::inherit()) }; + let s = match std::mem::take(s).into_string() { + Ok(s) => s, + Err(e) => return Err(die!("bad_stdio_type", r#""inherit" | "null" | "piped""#, e)), + }; + Ok(match &*s { + "inherit" => Stdio::inherit(), + "null" => Stdio::null(), + "piped" => Stdio::piped(), + _ => return Err(die!("bad_stdio_opt", r#""inherit" | "null" | "piped""#, &s)), + }) + } + + /// Run a command + #[instrument] + #[rhai_fn(global, name = "sh")] + pub fn exec_cmd(command: Dynamic, mut opts: rhai::Map) -> rhai::Map { + let mut cmd: Command; + if command.is_string() { + cmd = Command::new("sh"); + cmd.arg("-c").arg(command.into_string().unwrap()) + } else { + let res = command.into_typed_array(); + let Ok(arr) = res else { + return die!("bad_param_type", "String | Vec", res.unwrap_err()); + }; + let [exec, args @ ..]: &[&str] = &arr[..] else { + return die!("empty_cmd_arr", "cmd.len() >= 1", "cmd.len() == 0"); + }; + cmd = Command::new(exec); + cmd.args(args) + }; + + cmd.stdout(match _parse_io_opt(opts.get_mut("stdout")) { + Ok(io) => io, + Err(e) => return e, + }); + cmd.stderr(match _parse_io_opt(opts.get_mut("stderr")) { + Ok(io) => io, + Err(e) => return e, + }); + + if let Some(cwd) = opts.get_mut("cwd") { + match std::mem::take(cwd).into_string() { + Ok(cwd) => _ = cmd.current_dir(cwd), + Err(e) => return die!("bad_cwd_type", "String", e), + } + } + + let out = match cmd.output() { + Ok(x) => x, + Err(err) => { + let mut e = rhai::Map::new(); + let mut inner = rhai::Map::new(); + e.insert("outcome".into(), rhai::Dynamic::from_str("failure").unwrap()); + inner.insert("error".into(), rhai::Dynamic::from_str(&err.to_string()).unwrap()); + e.insert("ctx".into(), rhai::Dynamic::from_map(inner)); + return e; + } + }; + + let mut ret = rhai::Map::new(); + let mut inner = rhai::Map::new(); + ret.insert("outcome".into(), rhai::Dynamic::from_str("success").unwrap()); + inner.insert( + "stdout".into(), + rhai::Dynamic::from_str(&String::from_utf8_lossy(&out.stdout)).unwrap(), + ); + inner.insert( + "stderr".into(), + rhai::Dynamic::from_str(&String::from_utf8_lossy(&out.stderr)).unwrap(), + ); + inner.insert( + "rc".into(), + rhai::Dynamic::from_int(i64::from(out.status.code().unwrap_or(0))), + ); + ret.insert("ctx".into(), rhai::Dynamic::from_map(inner)); + ret + } + /// run a command using `cmd` on Windows and `sh` on other systems #[instrument(skip(ctx))] #[rhai_fn(return_raw, name = "sh", global)] diff --git a/andax/src/run.rs b/andax/src/run.rs index 8b9966b..f8bdb1e 100644 --- a/andax/src/run.rs +++ b/andax/src/run.rs @@ -294,10 +294,10 @@ fn hint_ear(sl: &str, lns: &str, ear: &EvalAltResult, rhai_fn: &str) -> Option