use anyhow::{Context, Result};
use std::path::Path;
use std::process::{Command, ExitStatus, Stdio};
pub fn run_inherit(program: &str, args: &[&str]) -> Result<ExitStatus> {
Command::new(program)
.args(args)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.with_context(|| format!("spawning `{program} {}`", args.join(" ")))
}
pub fn run_capture(program: &str, args: &[&str]) -> Result<(ExitStatus, String)> {
let output = Command::new(program)
.args(args)
.stdin(Stdio::null())
.output()
.with_context(|| format!("spawning `{program} {}`", args.join(" ")))?;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
Ok((output.status, stdout))
}
pub fn run_capture_split(program: &str, args: &[&str]) -> Result<(ExitStatus, String, String)> {
let output = Command::new(program)
.args(args)
.stdin(Stdio::null())
.output()
.with_context(|| format!("spawning `{program} {}`", args.join(" ")))?;
Ok((
output.status,
String::from_utf8_lossy(&output.stdout).into_owned(),
String::from_utf8_lossy(&output.stderr).into_owned(),
))
}
pub fn run_capture_combined(program: &str, args: &[&str]) -> Result<(ExitStatus, String)> {
let output = Command::new(program)
.args(args)
.stdin(Stdio::null())
.output()
.with_context(|| format!("spawning `{program} {}`", args.join(" ")))?;
let mut combined = String::from_utf8_lossy(&output.stdout).into_owned();
combined.push_str(&String::from_utf8_lossy(&output.stderr));
Ok((output.status, combined))
}
pub fn run_with_stdin_file(program: &str, args: &[&str], stdin_path: &Path) -> Result<ExitStatus> {
let mut child = Command::new(program)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.with_context(|| format!("spawning `{program} {}`", args.join(" ")))?;
let mut file = std::fs::File::open(stdin_path)
.with_context(|| format!("opening {}", stdin_path.display()))?;
let mut stdin = child.stdin.take().expect("child stdin was piped");
std::io::copy(&mut file, &mut stdin).context("streaming file to child stdin")?;
drop(stdin);
child.wait().context("waiting for child process")
}
pub fn require_success(what: &str, status: ExitStatus) -> Result<()> {
if !status.success() {
anyhow::bail!("{what} failed ({status})");
}
Ok(())
}
pub fn run_stdin_to_file(
program: &str,
args: &[&str],
stdin_path: &Path,
stdout_path: &Path,
) -> Result<ExitStatus> {
let out = std::fs::File::create(stdout_path)
.with_context(|| format!("creating {}", stdout_path.display()))?;
let input = std::fs::File::open(stdin_path)
.with_context(|| format!("opening {}", stdin_path.display()))?;
Command::new(program)
.args(args)
.stdin(Stdio::from(input))
.stdout(Stdio::from(out))
.stderr(Stdio::inherit())
.status()
.with_context(|| format!("spawning `{program} {}`", args.join(" ")))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stdout_to_file_keeps_binary_bytes() {
let dir = std::env::temp_dir().join(format!("moor-proc-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let (input, output) = (dir.join("in.bin"), dir.join("out.bin"));
let bytes: Vec<u8> = (0..=255u8).cycle().take(4096).collect();
std::fs::write(&input, &bytes).unwrap();
let status = run_stdin_to_file("cat", &[], &input, &output).unwrap();
assert!(status.success());
assert_eq!(std::fs::read(&output).unwrap(), bytes);
let _ = std::fs::remove_dir_all(&dir);
}
}