use crate::context::with_context;
use crate::error::Result;
use std::process::Command;
use steel::SteelVal;
use steel::steel_vm::engine::Engine;
use steel::steel_vm::register_fn::RegisterFn;
pub fn register(engine: &mut Engine) -> Result<()> {
engine.register_fn("hx/run", run_command);
engine.register_fn("hx/run-checked", run_checked);
engine.register_fn("hx/run-silent", run_silent);
Ok(())
}
fn run_command(cmd: String, args: Vec<SteelVal>) -> SteelVal {
let args: Vec<String> = args
.into_iter()
.filter_map(|v| match v {
SteelVal::StringV(s) => Some(s.to_string()),
_ => None,
})
.collect();
let env_vars = with_context(|ctx| ctx.env_vars.clone()).unwrap_or_default();
let result = Command::new(&cmd).args(&args).envs(&env_vars).output();
match result {
Ok(output) => {
let exit_code = SteelVal::IntV(output.status.code().unwrap_or(-1) as isize);
let stdout =
SteelVal::StringV(String::from_utf8_lossy(&output.stdout).to_string().into());
let stderr =
SteelVal::StringV(String::from_utf8_lossy(&output.stderr).to_string().into());
SteelVal::ListV(
vec![
SteelVal::ListV(vec![SteelVal::SymbolV("exit-code".into()), exit_code].into()),
SteelVal::ListV(vec![SteelVal::SymbolV("stdout".into()), stdout].into()),
SteelVal::ListV(vec![SteelVal::SymbolV("stderr".into()), stderr].into()),
]
.into(),
)
}
Err(e) => SteelVal::ListV(
vec![
SteelVal::ListV(
vec![SteelVal::SymbolV("exit-code".into()), SteelVal::IntV(-1)].into(),
),
SteelVal::ListV(
vec![
SteelVal::SymbolV("stdout".into()),
SteelVal::StringV("".into()),
]
.into(),
),
SteelVal::ListV(
vec![
SteelVal::SymbolV("stderr".into()),
SteelVal::StringV(e.to_string().into()),
]
.into(),
),
]
.into(),
),
}
}
fn run_checked(cmd: String, args: Vec<SteelVal>) -> std::result::Result<SteelVal, String> {
let args: Vec<String> = args
.into_iter()
.filter_map(|v| match v {
SteelVal::StringV(s) => Some(s.to_string()),
_ => None,
})
.collect();
let env_vars = with_context(|ctx| ctx.env_vars.clone()).unwrap_or_default();
let output = Command::new(&cmd)
.args(&args)
.envs(&env_vars)
.output()
.map_err(|e| format!("Failed to execute {}: {}", cmd, e))?;
if output.status.success() {
Ok(SteelVal::StringV(
String::from_utf8_lossy(&output.stdout).to_string().into(),
))
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!(
"Command '{}' failed with exit code {:?}: {}",
cmd,
output.status.code(),
stderr
))
}
}
fn run_silent(cmd: String, args: Vec<SteelVal>) -> SteelVal {
let args: Vec<String> = args
.into_iter()
.filter_map(|v| match v {
SteelVal::StringV(s) => Some(s.to_string()),
_ => None,
})
.collect();
let env_vars = with_context(|ctx| ctx.env_vars.clone()).unwrap_or_default();
let result = Command::new(&cmd).args(&args).envs(&env_vars).output();
match result {
Ok(output) => SteelVal::IntV(output.status.code().unwrap_or(-1) as isize),
Err(_) => SteelVal::IntV(-1),
}
}