use std::error::Error;
use std::fmt;
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
use std::process;
pub use sysexits::ExitCode;
pub trait ExitCodeExt {
fn to_i32(self) -> i32;
}
impl ExitCodeExt for ExitCode {
fn to_i32(self) -> i32 {
i32::from(u8::from(self))
}
}
pub trait ExitCodeProvider: Error {
fn exit_code(&self) -> ExitCode;
}
pub const PARTIAL_SUCCESS_I32: i32 = 3;
pub const PARTIAL_SUCCESS_U8: u8 = 3;
const _: () = assert!(
PARTIAL_SUCCESS_I32 >= 0
&& PARTIAL_SUCCESS_I32 <= u8::MAX as i32
&& PARTIAL_SUCCESS_I32 as u8 == PARTIAL_SUCCESS_U8,
"PARTIAL_SUCCESS_I32 must fit in u8 and agree with PARTIAL_SUCCESS_U8",
);
pub mod codes {
use sysexits::ExitCode;
pub const OK: ExitCode = ExitCode::Ok;
pub const PARTIAL_SUCCESS: i32 = super::PARTIAL_SUCCESS_I32;
pub const PARTIAL_SUCCESS_U8: u8 = super::PARTIAL_SUCCESS_U8;
pub const SOFTWARE: ExitCode = ExitCode::Software;
pub const IO_ERR: ExitCode = ExitCode::IoErr;
pub const NO_INPUT: ExitCode = ExitCode::NoInput;
pub const NO_PERM: ExitCode = ExitCode::NoPerm;
pub const DATA_ERR: ExitCode = ExitCode::DataErr;
pub const CONFIG: ExitCode = ExitCode::Config;
pub const OS_ERR: ExitCode = ExitCode::OsErr;
pub const UNAVAILABLE: ExitCode = ExitCode::Unavailable;
pub const TEMP_FAIL: ExitCode = ExitCode::TempFail;
pub const PROTOCOL: ExitCode = ExitCode::Protocol;
pub const USAGE: ExitCode = ExitCode::Usage;
}
#[derive(Debug)]
pub enum Outcome<E>
where
E: ExitCodeProvider,
{
Success,
PartialSuccess,
Failure(E),
}
impl<E> Outcome<E>
where
E: ExitCodeProvider,
{
pub fn exit_code_i32(&self) -> i32 {
match self {
Outcome::Success => ExitCode::Ok.to_i32(),
Outcome::PartialSuccess => PARTIAL_SUCCESS_I32,
Outcome::Failure(e) => e.exit_code().to_i32(),
}
}
pub fn into_result_u8(self) -> Result<u8, E> {
match self {
Outcome::Success => Ok(0),
Outcome::PartialSuccess => Ok(PARTIAL_SUCCESS_U8),
Outcome::Failure(e) => Err(e),
}
}
pub fn is_success(&self) -> bool {
matches!(self, Outcome::Success)
}
pub fn is_partial_success(&self) -> bool {
matches!(self, Outcome::PartialSuccess)
}
pub fn is_failure(&self) -> bool {
matches!(self, Outcome::Failure(_))
}
}
impl<E> fmt::Display for Outcome<E>
where
E: ExitCodeProvider + fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Outcome::Success => write!(f, "success (exit 0)"),
Outcome::PartialSuccess => {
write!(f, "partial success (exit {PARTIAL_SUCCESS_I32})")
}
Outcome::Failure(e) => {
write!(f, "failure (exit {}): {e}", e.exit_code().to_i32())
}
}
}
}
pub fn run_with_exit_code<E, F>(f: F) -> !
where
E: ExitCodeProvider,
F: FnOnce() -> Result<(), E>,
{
match f() {
Ok(()) => process::exit(ExitCode::Ok.to_i32()),
Err(e) => {
eprintln!("Error: {e}");
process::exit(e.exit_code().to_i32())
}
}
}
pub fn run_with_outcome<E, F>(f: F) -> !
where
E: ExitCodeProvider,
F: FnOnce() -> Outcome<E>,
{
let outcome = f();
if let Outcome::Failure(e) = &outcome {
eprintln!("Error: {e}");
}
process::exit(outcome.exit_code_i32())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ExitOutcome {
Success,
PartialSuccess,
Failure(i32),
}
impl ExitOutcome {
pub fn from_code(code: i32) -> Self {
match code {
0 => Self::Success,
c if c == PARTIAL_SUCCESS_I32 => Self::PartialSuccess,
other => Self::Failure(other),
}
}
pub fn from_status(status: &process::ExitStatus) -> Self {
if let Some(code) = status.code() {
return Self::from_code(code);
}
#[cfg(unix)]
if let Some(signum) = ExitStatusExt::signal(status) {
return Self::Failure(128 + signum);
}
Self::Failure(-1)
}
pub fn is_success_shaped(&self) -> bool {
matches!(self, Self::Success | Self::PartialSuccess)
}
pub fn is_success(&self) -> bool {
matches!(self, Self::Success)
}
pub fn is_partial_success(&self) -> bool {
matches!(self, Self::PartialSuccess)
}
pub fn is_failure(&self) -> bool {
matches!(self, Self::Failure(_))
}
pub fn code(&self) -> i32 {
match self {
Self::Success => 0,
Self::PartialSuccess => PARTIAL_SUCCESS_I32,
Self::Failure(c) => *c,
}
}
pub fn canonicalize(self) -> Self {
match self {
Self::Failure(c) => Self::from_code(c),
other => other,
}
}
}
impl fmt::Display for ExitOutcome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Success => write!(f, "success (exit 0)"),
Self::PartialSuccess => {
write!(f, "partial success (exit {PARTIAL_SUCCESS_I32})")
}
Self::Failure(c) => write!(f, "failure (exit {c})"),
}
}
}