use std::path::Path;
pub use tokio::process::Command;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Unable to run subcommand {command}")]
IO {
command: String,
source: std::io::Error,
},
#[error("Command {command:?} failed with STDERR: {stderr}")]
CommandError {
command: String,
status: std::process::ExitStatus,
stderr: String,
stdout: String,
},
}
impl Error {
pub fn status(&self) -> Option<i32> {
if let Error::CommandError {
command: _,
status,
stderr: _,
stdout: _,
} = self
{
status.code()
} else {
None
}
}
}
pub trait CommandBuilder {
fn build(&self, current_dir: &Path) -> Command;
}
pub async fn execute<T>(builder: &T, current_dir: &Path) -> Result<(String, String), Error>
where
T: CommandBuilder,
{
let mut cmd = builder.build(current_dir);
cmd.stdin(std::process::Stdio::null());
let child_process_output = cmd.output().await.map_err(|e| Error::IO {
command: format!("{:?}", cmd),
source: e,
})?;
let succeeded = child_process_output.status.success();
let out = String::from_utf8_lossy(&child_process_output.stdout);
let err = String::from_utf8_lossy(&child_process_output.stderr);
tracing::debug!(
"Command:{:?} \n Exit code:{:?} \n Stdout: {}, Stderr: {} \n",
cmd,
child_process_output.status.code(),
out,
err,
);
if !succeeded {
tracing::debug!(
"Failed to run {:?}, Status was: {:?}, Stdout was:\n{:?},Stderr was:\n{:?}",
cmd,
child_process_output.status.code(),
out,
err
);
Err(Error::CommandError {
status: child_process_output.status,
stdout: out.to_string(),
stderr: err.to_string(),
command: format!("{:?}", cmd),
})
} else {
Ok((out.to_string(), err.to_string()))
}
}