use crate::exit_status;
use crate::NamedOutput;
use crate::OutputWithName;
use std::fmt::Display;
use std::process::{ExitStatus, Output};
#[cfg(doc)]
use std::process::Command;
#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
pub enum CmdError {
SystemError(String, std::io::Error),
NonZeroExitNotStreamed(NamedOutput),
NonZeroExitAlreadyStreamed(NamedOutput),
}
impl Display for CmdError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CmdError::SystemError(name, error) => {
write!(f, "Could not run command `{name}`. {error}")
}
CmdError::NonZeroExitNotStreamed(named_output) => {
let stdout = display_out_or_empty(&named_output.output().stdout);
let stderr = display_out_or_empty(&named_output.output().stderr);
writeln!(f, "Command failed `{name}`", name = named_output.name())?;
writeln!(
f,
"exit status: {status}",
status = exit_status::bashify(&named_output.output().status)
)?;
if let Some(signal_line) = exit_status::signal_line(&named_output.output().status) {
writeln!(f, "{signal_line}")?;
}
writeln!(f, "stdout: {stdout}",)?;
write!(f, "stderr: {stderr}",)
}
CmdError::NonZeroExitAlreadyStreamed(named_output) => {
writeln!(f, "Command failed `{name}`", name = named_output.name())?;
writeln!(
f,
"exit status: {status}",
status = exit_status::bashify(&named_output.output().status)
)?;
if let Some(signal_line) = exit_status::signal_line(&named_output.output().status) {
writeln!(f, "{signal_line}")?;
}
writeln!(f, "stdout: <see above>")?;
write!(f, "stderr: <see above>")
}
}
}
}
impl std::error::Error for CmdError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
CmdError::SystemError(_, io_err) => Some(io_err),
CmdError::NonZeroExitNotStreamed(_) | CmdError::NonZeroExitAlreadyStreamed(_) => None,
}
}
}
impl CmdError {
#[must_use]
pub fn name(&self) -> std::borrow::Cow<'_, str> {
match self {
CmdError::SystemError(name, _) => name.into(),
CmdError::NonZeroExitNotStreamed(out) | CmdError::NonZeroExitAlreadyStreamed(out) => {
out.name().into()
}
}
}
pub fn output(&self) -> Option<&NamedOutput> {
match self {
CmdError::SystemError(_, _) => None,
CmdError::NonZeroExitNotStreamed(named_output) => Some(named_output),
CmdError::NonZeroExitAlreadyStreamed(named_output) => Some(named_output),
}
}
pub fn status(&self) -> ExitStatus {
match self {
CmdError::SystemError(_, error) => exit_status::status_from_error(error),
CmdError::NonZeroExitNotStreamed(named_output) => named_output.status().to_owned(),
CmdError::NonZeroExitAlreadyStreamed(named_output) => named_output.status().to_owned(),
}
}
}
impl From<CmdError> for NamedOutput {
fn from(value: CmdError) -> Self {
match value {
CmdError::SystemError(name, error) => Output {
status: exit_status::status_from_error(&error),
stdout: Vec::new(),
stderr: error.to_string().into_bytes(),
}
.named(name),
CmdError::NonZeroExitNotStreamed(named)
| CmdError::NonZeroExitAlreadyStreamed(named) => named,
}
}
}
fn display_out_or_empty(contents: &[u8]) -> String {
let contents = String::from_utf8_lossy(contents);
if contents.trim().is_empty() {
"<empty>".to_string()
} else {
contents.to_string()
}
}