use std::error::Error;
use std::fmt::Display;
use std::fs::OpenOptions;
use std::io::{self, prelude::*};
use std::process::{Command, ExitStatus, Stdio};
use crate::log_file::LogFile;
#[derive(Debug)]
pub(crate) struct NonZeroExitStatus(ExitStatus);
impl Display for NonZeroExitStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Error for NonZeroExitStatus {}
#[derive(Debug)]
#[non_exhaustive]
pub(crate) struct ExecuteError {
kind: ExecuteErrorKind,
}
impl Display for ExecuteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.kind {
ExecuteErrorKind::Write(_) => write!(f, "unable to write to stream"),
ExecuteErrorKind::Exec(_, command) => {
write!(f, "unable to execute command: {}", command.join(" "))
}
ExecuteErrorKind::Child(_, command) => write!(
f,
"child process exited with non-zero code: {}",
command.join(" ")
),
}
}
}
impl Error for ExecuteError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.kind {
ExecuteErrorKind::Write(err) => Some(err),
ExecuteErrorKind::Exec(err, _) => Some(err),
ExecuteErrorKind::Child(err, _) => Some(err),
}
}
}
#[derive(Debug)]
pub(crate) enum ExecuteErrorKind {
#[non_exhaustive]
Write(io::Error),
#[non_exhaustive]
Exec(io::Error, Vec<String>),
#[non_exhaustive]
Child(NonZeroExitStatus, Vec<String>),
}
pub(crate) fn execute(command: &[&str], log_file: &LogFile) -> Result<(), ExecuteError> {
(|| -> Result<(), ExecuteErrorKind> {
let mut runner = Command::new(command[0]);
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(log_file)
.unwrap_or_else(|_| panic!("should be able to append to log file {:?}", log_file));
writeln!(file, "$ {:?}", command.join(" ")).map_err(ExecuteErrorKind::Write)?;
runner.stdout(Stdio::null());
runner.stderr(file);
for argument in command.iter().skip(1) {
runner.arg(argument);
}
let status = runner.status().map_err(|err| {
ExecuteErrorKind::Exec(
err,
command
.iter()
.map(ToOwned::to_owned)
.map(ToOwned::to_owned)
.collect(),
)
})?;
if !status.success() {
return Err(ExecuteErrorKind::Child(
NonZeroExitStatus(status),
command
.iter()
.map(ToOwned::to_owned)
.map(ToOwned::to_owned)
.collect(),
));
}
Ok(())
})()
.map_err(|kind| ExecuteError { kind })
}