use std::fmt;
pub trait CommandRunner {
fn run(&self, program: &str, args: &[&str]) -> Result<String, CommandError>;
}
#[derive(Default)]
pub struct SystemCommandRunner;
impl CommandRunner for SystemCommandRunner {
fn run(&self, program: &str, args: &[&str]) -> Result<String, CommandError> {
let output = std::process::Command::new(program).args(args).output()?;
if !output.status.success() {
return Err(CommandError::Failed {
program: program.to_string(),
args: args.iter().map(ToString::to_string).collect(),
status: output.status,
});
}
Ok(String::from_utf8(output.stdout)?)
}
}
#[derive(Debug)]
pub enum CommandError {
Io(std::io::Error),
Utf8(std::string::FromUtf8Error),
Failed {
program: String,
args: Vec<String>,
status: std::process::ExitStatus,
},
Parse {
program: String,
field: String,
},
}
impl fmt::Display for CommandError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(err) => write!(f, "I/O error: {err}"),
Self::Utf8(err) => write!(f, "UTF-8 decoding error: {err}"),
Self::Failed {
program,
args,
status,
} => {
write!(f, "{program:?} {args:?} exited with status {status}")
}
Self::Parse { program, field } => {
write!(
f,
"{program} output did not contain expected field \"{field}\""
)
}
}
}
}
impl std::error::Error for CommandError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(err) => Some(err),
Self::Utf8(err) => Some(err),
Self::Failed { .. } | Self::Parse { .. } => None,
}
}
}
impl From<std::io::Error> for CommandError {
fn from(value: std::io::Error) -> Self {
Self::Io(value)
}
}
impl From<std::string::FromUtf8Error> for CommandError {
fn from(value: std::string::FromUtf8Error) -> Self {
Self::Utf8(value)
}
}