From c0ee563b448974b3902031c26835e7e5c967cbf6 Mon Sep 17 00:00:00 2001 From: madomado Date: Sun, 10 Dec 2023 16:20:51 +0800 Subject: [PATCH 1/3] feat(andax/fns/io): `sh_stream` --- andax/src/fns/io.rs | 44 ++++++++++++++++++++++++++++++++++++++-- andax/src/fns/tsunagu.rs | 6 ++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/andax/src/fns/io.rs b/andax/src/fns/io.rs index 90ce44c..f7d2a4e 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,7 +36,14 @@ macro_rules! _cmd { }}; } +macro_rules! _stream_cmd { + ($cmd:expr) => {{ + _cmd!($cmd).stdout(Stdio::inherit()).stderr(Stdio::inherit()) + }}; +} + type T = Result<(i32, String, String), Box>; +type U = Result>; /// for andax, shell(): /// ``` @@ -51,7 +62,7 @@ pub mod ar { pub fn sh_rc(o: (i32, String, String)) -> i32 { o.0 } - /// get stdout from the return value of `sh()` + /// get stdout from the return value of `sh()` #[rhai_fn(global)] pub fn sh_stdout(o: (i32, String, String)) -> String { o.1 @@ -62,6 +73,35 @@ pub mod ar { o.2 } + /// run a command using `cmd` on Windows and `sh` on other systems + #[instrument(skip(ctx))] + #[rhai_fn(return_raw, name = "sh_stream", global)] + pub fn shell_stream(ctx: NativeCallContext, cmd: &str) -> U { + debug!("Running in shell"); + _sh_out!(_cmd!(cmd).output().ehdl(&ctx)?) + } + /// run a command using `cmd` on Windows and `sh` on other systems in working dir + #[instrument(skip(ctx))] + #[rhai_fn(return_raw, name = "sh_stream", global)] + pub fn shell_cwd_stream(ctx: NativeCallContext, cmd: &str, cwd: &str) -> U { + debug!("Running in shell"); + _sh_out!(_cmd!(cmd).current_dir(cwd).output().ehdl(&ctx)?) + } + /// run an executable + #[instrument(skip(ctx))] + #[rhai_fn(return_raw, name = "sh_stream", global)] + pub fn sh_stream(ctx: NativeCallContext, cmd: Vec<&str>) -> U { + debug!("Running executable"); + _sh_out!(Command::new(cmd[0]).args(&cmd[1..]).output().ehdl(&ctx)?) + } + /// run an executable in working directory + #[instrument(skip(ctx))] + #[rhai_fn(return_raw, name = "sh_stream", global)] + pub fn sh_cwd_stream(ctx: NativeCallContext, cmd: Vec<&str>, cwd: &str) -> U { + debug!("Running executable"); + _sh_out!(Command::new(cmd[0]).args(&cmd[1..]).current_dir(cwd).output().ehdl(&ctx)?) + } + /// 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/fns/tsunagu.rs b/andax/src/fns/tsunagu.rs index 398be95..255d138 100644 --- a/andax/src/fns/tsunagu.rs +++ b/andax/src/fns/tsunagu.rs @@ -119,6 +119,12 @@ pub mod ar { Err(VarError::NotUnicode(o)) => Err(format!("env(`{key}`): invalid UTF: {o:?}").into()), } } + + #[rhai_fn(return_raw, global)] + pub fn rust2rpm(name: &str) -> Res<()> { + let cmd = std::process::Command::new("rust2rpm").arg(name).output(); + Ok(()) + } } #[derive(Clone)] From e0a5b7955f32e73d311a8927aa17caccfe72d565 Mon Sep 17 00:00:00 2001 From: madomado Date: Sun, 10 Dec 2023 16:22:16 +0800 Subject: [PATCH 2/3] chore: revert `rust2rpm()` --- andax/src/fns/tsunagu.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/andax/src/fns/tsunagu.rs b/andax/src/fns/tsunagu.rs index 255d138..398be95 100644 --- a/andax/src/fns/tsunagu.rs +++ b/andax/src/fns/tsunagu.rs @@ -119,12 +119,6 @@ pub mod ar { Err(VarError::NotUnicode(o)) => Err(format!("env(`{key}`): invalid UTF: {o:?}").into()), } } - - #[rhai_fn(return_raw, global)] - pub fn rust2rpm(name: &str) -> Res<()> { - let cmd = std::process::Command::new("rust2rpm").arg(name).output(); - Ok(()) - } } #[derive(Clone)] From 6534d17991e52f748a162634a1041de9b89ae372 Mon Sep 17 00:00:00 2001 From: madomado Date: Tue, 19 Dec 2023 23:02:22 +0800 Subject: [PATCH 3/3] feat: make better exec() fn for funny --- anda-config/src/config.rs | 4 +- andax/src/error.rs | 4 +- andax/src/fns/io.rs | 121 +++++++++++++++++++++++++++++--------- andax/src/fns/rpm.rs | 23 ++++++-- andax/src/run.rs | 4 +- 5 files changed, 117 insertions(+), 39 deletions(-) diff --git a/anda-config/src/config.rs b/anda-config/src/config.rs index 6e3800c..2ccb42a 100644 --- a/anda-config/src/config.rs +++ b/anda-config/src/config.rs @@ -38,7 +38,7 @@ impl Manifest { self.project.get(key).map_or_else( || { self.project.iter().find_map(|(_k, v)| { - let Some(alias) = &v.alias else { return None }; + let alias = &v.alias.as_ref()?; if alias.contains(&key.to_string()) { Some(v) } else { @@ -272,7 +272,7 @@ pub fn load_from_string(config: &str) -> Result { } /// Lints and checks the config for errors. -/// +/// /// # Errors /// - nothing. This function literally does nothing. For now. pub const fn check_config(config: Manifest) -> Result { diff --git a/andax/src/error.rs b/andax/src/error.rs index b46aaf3..abff6c6 100644 --- a/andax/src/error.rs +++ b/andax/src/error.rs @@ -59,7 +59,7 @@ where } } -pub const EARTH: &str = r#" +pub const EARTH: &str = r" . . * . . . . * . . . . . . * . . . . * . . * . . . * . . * . . . * . . . . * . . . . . * . . . .-o--. . * . @@ -84,4 +84,4 @@ ____ * . . . . . . . . : O. Oo; . . | `._\-' ' . . . | `.__, \ * . . *. . | \ \. . . - | \ \ . * jrei *"#; + | \ \ . * jrei *"; diff --git a/andax/src/fns/io.rs b/andax/src/fns/io.rs index f7d2a4e..6c7d0d6 100644 --- a/andax/src/fns/io.rs +++ b/andax/src/fns/io.rs @@ -43,7 +43,6 @@ macro_rules! _stream_cmd { } type T = Result<(i32, String, String), Box>; -type U = Result>; /// for andax, shell(): /// ``` @@ -57,6 +56,22 @@ type U = Result>; /// 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 { @@ -73,33 +88,85 @@ pub mod ar { o.2 } - /// run a command using `cmd` on Windows and `sh` on other systems - #[instrument(skip(ctx))] - #[rhai_fn(return_raw, name = "sh_stream", global)] - pub fn shell_stream(ctx: NativeCallContext, cmd: &str) -> U { - debug!("Running in shell"); - _sh_out!(_cmd!(cmd).output().ehdl(&ctx)?) - } - /// run a command using `cmd` on Windows and `sh` on other systems in working dir - #[instrument(skip(ctx))] - #[rhai_fn(return_raw, name = "sh_stream", global)] - pub fn shell_cwd_stream(ctx: NativeCallContext, cmd: &str, cwd: &str) -> U { - debug!("Running in shell"); - _sh_out!(_cmd!(cmd).current_dir(cwd).output().ehdl(&ctx)?) - } - /// run an executable - #[instrument(skip(ctx))] - #[rhai_fn(return_raw, name = "sh_stream", global)] - pub fn sh_stream(ctx: NativeCallContext, cmd: Vec<&str>) -> U { - debug!("Running executable"); - _sh_out!(Command::new(cmd[0]).args(&cmd[1..]).output().ehdl(&ctx)?) + 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 an executable in working directory - #[instrument(skip(ctx))] - #[rhai_fn(return_raw, name = "sh_stream", global)] - pub fn sh_cwd_stream(ctx: NativeCallContext, cmd: Vec<&str>, cwd: &str) -> U { - debug!("Running executable"); - _sh_out!(Command::new(cmd[0]).args(&cmd[1..]).current_dir(cwd).output().ehdl(&ctx)?) + + /// 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 diff --git a/andax/src/fns/rpm.rs b/andax/src/fns/rpm.rs index 948cee5..5a2d33c 100644 --- a/andax/src/fns/rpm.rs +++ b/andax/src/fns/rpm.rs @@ -3,7 +3,7 @@ use std::{ fs, path::{Path, PathBuf}, }; -use tracing::{info, error}; +use tracing::{error, info}; lazy_static::lazy_static! { static ref RE_RELEASE: regex::Regex = regex::Regex::new(r"Release:(\s+)(.+?)\n").unwrap(); @@ -29,7 +29,10 @@ pub struct RPMSpec { } impl RPMSpec { - /// Creates a new RPMSpec file representation + /// Creates a new RPMSpec file representation. + /// + /// # Panics + /// - cannot read spec to file pub fn new(name: String, chkupdate: T, spec: U) -> Self where T: Into + AsRef, @@ -57,7 +60,9 @@ impl RPMSpec { } /// Sets the version in the spec file pub fn version(&mut self, ver: &str) { - let Some(m) = RE_VERSION.captures(self.f.as_str()) else { return error!("No `Version:` preamble for {}", self.name) }; + let Some(m) = RE_VERSION.captures(self.f.as_str()) else { + return error!("No `Version:` preamble for {}", self.name); + }; let ver = ver.strip_prefix('v').unwrap_or(ver).replace('-', "."); if ver != m[2] { info!("{}: {} —→ {ver}", self.name, &m[2]); @@ -67,20 +72,26 @@ impl RPMSpec { } /// Change the value of a `%define` macro by the name pub fn define(&mut self, name: &str, val: &str) { - let Some(cap) = RE_DEFINE.captures_iter(self.f.as_str()).find(|cap| &cap[2] == name) else { return error!("No `Version:` preamble for {}", self.name) }; + let Some(cap) = RE_DEFINE.captures_iter(self.f.as_str()).find(|cap| &cap[2] == name) else { + return error!("No `Version:` preamble for {}", self.name); + }; self.f = self.f.replace(&cap[0], &format!("%define{}{name}{}{val}", &cap[1], &cap[3])); self.changed = true; } /// Change the value of a `%global` macro by the name pub fn global(&mut self, name: &str, val: &str) { - let Some(cap) = RE_GLOBAL.captures_iter(self.f.as_str()).find(|cap| &cap[2] == name) else { return error!("No `Version:` preamble for {}", self.name) }; + let Some(cap) = RE_GLOBAL.captures_iter(self.f.as_str()).find(|cap| &cap[2] == name) else { + return error!("No `Version:` preamble for {}", self.name); + }; self.f = self.f.replace(&cap[0], &format!("%global{}{name}{}{val}", &cap[1], &cap[3])); self.changed = true; } /// Change the `SourceN:` preamble value by `N` pub fn source(&mut self, i: i64, p: &str) { let si = i.to_string(); - let Some(cap) = RE_SOURCE.captures_iter(self.f.as_str()).find(|cap| cap[1] == si) else { return error!("No `Source{i}:` preamble for {}", self.name)}; + let Some(cap) = RE_SOURCE.captures_iter(self.f.as_str()).find(|cap| cap[1] == si) else { + return error!("No `Source{i}:` preamble for {}", self.name); + }; info!("{}: Source{i}: {p}", self.name); self.f = self.f.replace(&cap[0], &format!("Source{i}:{}{p}\n", &cap[2])); self.changed = true; diff --git a/andax/src/run.rs b/andax/src/run.rs index 1051463..b911115 100644 --- a/andax/src/run.rs +++ b/andax/src/run.rs @@ -287,10 +287,10 @@ fn hint_ear(sl: &str, lns: &str, ear: &EvalAltResult, rhai_fn: &str) -> Option