use std::time::Duration;
use crate::utils::command_timeout::run_command_with_timeout;
#[derive(Debug, Clone)]
pub struct ExecOutput {
pub status: i32,
pub stdout: String,
pub stderr: String,
pub timed_out: bool,
}
impl ExecOutput {
pub fn success(&self) -> bool {
self.status == 0 && !self.timed_out
}
}
pub fn try_exec(cmd: &str, args: &[&str], timeout: Duration) -> Option<ExecOutput> {
match run_command_with_timeout(cmd, args, timeout) {
Ok(out) => Some(ExecOutput {
status: out.status.code().unwrap_or(-1),
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
timed_out: false,
}),
Err(e) if e.kind() == std::io::ErrorKind::TimedOut => Some(ExecOutput {
status: -1,
stdout: String::new(),
stderr: format!("timed out after {timeout:?}"),
timed_out: true,
}),
Err(_) => None,
}
}
#[allow(dead_code)]
pub fn exec_stdout_ok(cmd: &str, args: &[&str], timeout: Duration) -> Option<String> {
let out = try_exec(cmd, args, timeout)?;
if out.success() {
Some(out.stdout)
} else {
None
}
}
pub fn which(cmd: &str) -> Option<String> {
let p = std::path::Path::new(cmd);
if p.is_absolute() || p.components().count() > 1 {
return if p.exists() {
Some(cmd.to_string())
} else {
None
};
}
#[cfg(unix)]
let probe = ("which", cmd);
#[cfg(windows)]
let probe = ("where", cmd);
let out = try_exec(probe.0, &[probe.1], Duration::from_millis(500))?;
if out.success() {
out.stdout.lines().next().map(|l| l.trim().to_string())
} else {
None
}
}