use std::{
ffi::OsStr,
fmt,
process::{Command, ExitStatus, Output},
str,
};
use crate::error::{Context as _, Error, Result};
macro_rules! cmd {
($program:expr $(, $arg:expr)* $(,)?) => {{
let mut _cmd = std::process::Command::new($program);
$(
_cmd.arg($arg);
)*
$crate::process::ProcessBuilder::from_std(_cmd)
}};
}
#[must_use]
pub(crate) struct ProcessBuilder {
cmd: Command,
}
impl ProcessBuilder {
pub(crate) fn from_std(cmd: Command) -> Self {
Self { cmd }
}
pub(crate) fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
self.cmd.arg(arg.as_ref());
self
}
pub(crate) fn args(&mut self, args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> &mut Self {
self.cmd.args(args);
self
}
pub(crate) fn run_with_output(&mut self) -> Result<Output> {
let output = self.cmd.output().with_context(|| {
process_error(format!("could not execute process {self}"), None, None)
})?;
if output.status.success() {
Ok(output)
} else {
Err(process_error(
format!("process didn't exit successfully: {self}"),
Some(output.status),
Some(&output),
))
}
}
pub(crate) fn read(&mut self) -> Result<String> {
let mut output = String::from_utf8(self.run_with_output()?.stdout)
.with_context(|| format!("failed to parse output from {self}"))?;
while output.ends_with('\n') || output.ends_with('\r') {
output.pop();
}
Ok(output)
}
}
impl fmt::Display for ProcessBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !f.alternate() {
f.write_str("`")?;
}
f.write_str(&self.cmd.get_program().to_string_lossy())?;
for arg in self.cmd.get_args() {
write!(f, " {}", arg.to_string_lossy())?;
}
if !f.alternate() {
f.write_str("`")?;
}
Ok(())
}
}
fn process_error(mut msg: String, status: Option<ExitStatus>, output: Option<&Output>) -> Error {
match status {
Some(s) => {
msg.push_str(" (");
msg.push_str(&s.to_string());
msg.push(')');
}
None => msg.push_str(" (never executed)"),
}
if let Some(out) = output {
match str::from_utf8(&out.stdout) {
Ok(s) if !s.trim_start().is_empty() => {
msg.push_str("\n--- stdout\n");
msg.push_str(s);
}
Ok(_) | Err(_) => {}
}
match str::from_utf8(&out.stderr) {
Ok(s) if !s.trim_start().is_empty() => {
msg.push_str("\n--- stderr\n");
msg.push_str(s);
}
Ok(_) | Err(_) => {}
}
}
Error::new(msg)
}