use log::debug;
use std::process::ExitStatus;
use thiserror::Error;
pub struct Shell {}
impl Shell {
pub fn new() -> Self {
Self {}
}
fn indent(spaces: usize, string: &str) -> String {
let indent = " ".repeat(spaces);
string
.lines()
.map(|line| format!("{}{}", indent, line))
.collect::<Vec<_>>()
.join("\r")
}
pub fn run_command(
&self,
cmd: String,
args: Vec<String>,
) -> Result<ShellOutput, ShellError> {
debug!("running command: {} {}", cmd, args.join(" "));
let output = std::process::Command::new(&cmd)
.args(&args)
.output()
.map_err(|err| ShellError::ShellStartFailure {
command: cmd.clone(),
args: args.join(" "),
error: err.to_string(),
})?;
let output = ShellOutput {
status: output.status,
stdout: String::from_utf8(output.stdout).unwrap(),
stderr: String::from_utf8(output.stderr).unwrap(),
};
debug!("command status: {}", output.status);
debug!(
"command output:\n stdout:\n{}\n\n stderr:\n{}",
if output.stdout.is_empty() {
Shell::indent(4, "<NO STDOUT OUTPUT>").into()
} else {
format!("\n{}", Shell::indent(4, &output.stdout))
},
if output.stderr.is_empty() {
Shell::indent(4, "<NO STDERR OUTPUT>").into()
} else {
format!("\n{}", Shell::indent(4, &output.stderr))
}
);
if output.status.success() {
Ok(output)
} else {
Err(ShellError::HostProcessExecutionFailure {
command: cmd,
args: args.join(" "),
status: output.status,
stdout: output.stdout,
stderr: output.stderr,
})
}
}
}
pub struct ShellOutput {
pub status: std::process::ExitStatus,
pub stdout: String,
pub stderr: String,
}
#[derive(Error, Debug, Clone)]
pub enum ShellError {
#[error("failed to execute command (ran: '{command} {args}', got status: {status}, stdout: '{stdout}', stderr: '{stderr}')")]
HostProcessExecutionFailure {
command: String,
args: String,
status: ExitStatus,
stdout: String,
stderr: String,
},
#[error("failed to start shell: {error} (ran: '{command} {args}')")]
ShellStartFailure {
command: String,
args: String,
error: String,
},
}
#[macro_export]
macro_rules! args {
($($arg:expr),*) => {
vec![$($arg.to_string()),*]
};
}
#[macro_export]
macro_rules! exec_on {
($shell:expr, $cmd:expr) => {
$shell.run_command($cmd.to_string(), vec![])
};
($shell:expr, $cmd:expr, $($arg:expr),*) => {
$shell.run_command($cmd.to_string(), $crate::args![$($arg),*])
};
}