use crate::error::{Error, Result};
use crate::types::{ExecResult, IoOptions, ScriptOptions};
use std::io::Write;
use std::path::PathBuf;
use std::process::Stdio;
use std::time::Instant;
use tempfile::NamedTempFile;
use tokio::process::{Child, Command};
use tokio::time::timeout;
pub async fn run(script: &str, options: ScriptOptions) -> Result<ExecResult> {
let mut temp_file = NamedTempFile::new().map_err(|e| Error::TempFile(e.to_string()))?;
writeln!(temp_file, "{}", script).map_err(|e| Error::TempFile(e.to_string()))?;
let path = temp_file.path().to_path_buf();
run_file(&path, options).await
}
pub async fn run_file(path: &PathBuf, options: ScriptOptions) -> Result<ExecResult> {
let child = spawn_file(path, options.clone()).await?;
let start_time = Instant::now();
if let Some(duration) = options.timeout {
let child_id = child.id();
match timeout(duration, child.wait_with_output()).await {
Ok(Ok(output)) => {
let duration = start_time.elapsed();
let stdout = String::from_utf8(output.stdout)
.map_err(|e| Error::StdoutCapture(e.to_string()))?;
let stderr = String::from_utf8(output.stderr)
.map_err(|e| Error::StderrCapture(e.to_string()))?;
Ok(ExecResult {
exit_code: output.status.code().unwrap_or(-1),
stdout,
stderr,
duration,
timed_out: false,
})
}
Ok(Err(e)) => Err(Error::Io(e)),
Err(_) => {
if let Some(id) = child_id {
#[cfg(unix)]
{
use std::process::Command as StdCommand;
let _ = StdCommand::new("kill").arg(format!("{}", id)).output();
}
}
Ok(ExecResult {
exit_code: -1,
stdout: String::new(),
stderr: String::new(),
duration: start_time.elapsed(),
timed_out: true,
})
}
}
} else {
let output = child.wait_with_output().await?;
let duration = start_time.elapsed();
let stdout = String::from_utf8(output.stdout)
.map_err(|e| Error::StdoutCapture(e.to_string()))?;
let stderr = String::from_utf8(output.stderr)
.map_err(|e| Error::StderrCapture(e.to_string()))?;
Ok(ExecResult {
exit_code: output.status.code().unwrap_or(-1),
stdout,
stderr,
duration,
timed_out: false,
})
}
}
pub async fn spawn(script: &str, options: ScriptOptions) -> Result<Child> {
let mut temp_file = NamedTempFile::new().map_err(|e| Error::TempFile(e.to_string()))?;
writeln!(temp_file, "{}", script).map_err(|e| Error::TempFile(e.to_string()))?;
let path = temp_file.path().to_path_buf();
spawn_file(&path, options).await
}
pub async fn spawn_file(path: &PathBuf, options: ScriptOptions) -> Result<Child> {
let openscript_path = options
.openscript_path
.unwrap_or_else(|| PathBuf::from("openscript"));
let mut cmd = Command::new(&openscript_path);
if let Some(path_str) = openscript_path.to_str() {
if path_str.ends_with("sh") || path_str.ends_with("bash") || path_str.ends_with("zsh") {
cmd.arg("-c");
let script_content =
std::fs::read_to_string(path).map_err(|_| Error::InvalidPath)?;
cmd.arg(script_content);
cmd.arg("sh"); cmd.args(&options.args); } else {
cmd.arg(path);
cmd.args(&options.args);
}
} else {
cmd.arg(path);
cmd.args(&options.args);
}
if let Some(cwd) = options.working_directory {
cmd.current_dir(cwd);
}
if options.clear_env {
cmd.env_clear();
}
cmd.envs(options.env_vars);
cmd.stdin(convert_io(options.stdin));
cmd.stdout(convert_io(options.stdout));
cmd.stderr(convert_io(options.stderr));
let child = cmd.spawn().map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
Error::OpenScriptNotFound
} else {
Error::CommandFailed(e.to_string())
}
})?;
Ok(child)
}
fn convert_io(io_options: IoOptions) -> Stdio {
match io_options {
IoOptions::Inherit => Stdio::inherit(),
IoOptions::Pipe => Stdio::piped(),
IoOptions::Null => Stdio::null(),
}
}