#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Code(i32);
impl Code {
pub const SUCCESS: Code = Code(0);
pub const FAILURE: Code = Code(1);
pub const UNKNOWN: Code = Code(2);
pub const USAGE_ERR: Code = Code(64);
pub const DATA_ERR: Code = Code(65);
pub const NO_INPUT: Code = Code(66);
pub const NO_USER: Code = Code(67);
pub const NO_HOST: Code = Code(68);
pub const SERVICE_UNAVAILABLE: Code = Code(69);
pub const SOFTWARE_ERR: Code = Code(70);
pub const OS_ERR: Code = Code(71);
pub const OS_FILE_ERR: Code = Code(72);
pub const CANT_CREAT: Code = Code(73);
pub const IO_ERR: Code = Code(74);
pub const TEMP_FAIL: Code = Code(75);
pub const PROTOCOL_ERR: Code = Code(76);
pub const NO_PERM: Code = Code(77);
pub const CONFIG_ERR: Code = Code(78);
pub const NOT_EXECUTABLE: Code = Code(126);
pub const NOT_FOUND: Code = Code(127);
pub const INVALID_EXIT: Code = Code(128);
const SIGBASE: i32 = 128;
pub const SIGHUP: Code = Code(Code::SIGBASE + 1);
pub const SIGINT: Code = Code(Code::SIGBASE + 2);
pub const SIGQUIT: Code = Code(Code::SIGBASE + 3);
pub const SIGILL: Code = Code(Code::SIGBASE + 4);
pub const SIGTRAP: Code = Code(Code::SIGBASE + 5);
pub const SIGABRT: Code = Code(Code::SIGBASE + 6);
pub const SIGFPE: Code = Code(Code::SIGBASE + 8);
pub const SIGKILL: Code = Code(Code::SIGBASE + 9);
pub const SIGSEGV: Code = Code(Code::SIGBASE + 11);
pub const SIGPIPE: Code = Code(Code::SIGBASE + 13);
pub const SIGALRM: Code = Code(Code::SIGBASE + 14);
pub const SIGTERM: Code = Code(Code::SIGBASE + 15);
pub const fn new(code: i32) -> Self {
Self(code)
}
pub fn from_status(status: std::process::ExitStatus) -> Self {
Self::from(status)
}
#[cfg(feature = "portable")]
pub const fn coerce(self) -> Option<Self> {
if self.is_portable() {
Some(self)
} else {
None
}
}
#[cfg(not(feature = "portable"))]
const fn coerce(self) -> Option<Self> {
if self.is_portable() {
Some(self)
} else {
None
}
}
#[cfg(feature = "portable")]
pub const fn is_portable(self) -> bool {
0 <= self.0 && self.0 <= 255
}
#[cfg(not(feature = "portable"))]
const fn is_portable(self) -> bool {
true
}
pub fn process_exit(self) -> ! {
std::process::exit(self.coerce().unwrap_or_default().raw())
}
pub fn ok(self) -> Result<(), crate::Exit> {
if self.0 == Self::SUCCESS.0 {
Ok(())
} else {
Err(crate::Exit::new(self))
}
}
pub fn into_exit(self) -> crate::Exit {
assert_ne!(self, Self::SUCCESS);
crate::Exit::new(self)
}
pub fn with_message<D: std::fmt::Display + 'static>(self, msg: D) -> crate::Exit {
self.into_exit().with_message(msg)
}
pub const fn is_ok(self) -> bool {
self.0 == Self::SUCCESS.0
}
pub const fn is_err(self) -> bool {
!self.is_ok()
}
#[allow(clippy::needless_bool)]
#[allow(clippy::if_same_then_else)]
pub const fn is_reserved(self) -> bool {
let code = self.0;
if Self::SUCCESS.0 <= code && code <= Self::UNKNOWN.0 {
true
} else if Self::USAGE_ERR.0 <= code && code <= Self::CONFIG_ERR.0 {
true
} else if Self::NOT_EXECUTABLE.0 <= code && code <= Self::INVALID_EXIT.0 {
true
} else if Self::SIGHUP.0 <= code && code <= Self::SIGTERM.0 {
true
} else {
false
}
}
pub const fn raw(self) -> i32 {
self.0
}
}
impl Default for Code {
fn default() -> Self {
Self::UNKNOWN
}
}
impl From<i32> for Code {
fn from(n: i32) -> Self {
Self(n)
}
}
impl From<std::process::ExitStatus> for Code {
fn from(status: std::process::ExitStatus) -> Self {
let n = platform_exit_code(status).unwrap_or(Code::UNKNOWN.0);
From::from(n)
}
}
impl From<std::io::ErrorKind> for Code {
fn from(kind: std::io::ErrorKind) -> Self {
use std::io::ErrorKind::*;
match kind {
NotFound => Code::OS_FILE_ERR,
PermissionDenied => Code::NO_PERM,
ConnectionRefused | ConnectionReset | ConnectionAborted | NotConnected => {
Code::PROTOCOL_ERR
}
AddrInUse | AddrNotAvailable => Code::SERVICE_UNAVAILABLE,
BrokenPipe => Code::SIGPIPE,
AlreadyExists => Code::CANT_CREAT,
InvalidInput | InvalidData | UnexpectedEof => Code::DATA_ERR,
TimedOut => Code::SIGALRM,
WriteZero => Code::NO_INPUT,
Interrupted => Code::SIGINT,
Other => Code::FAILURE,
_ => Code::IO_ERR,
}
}
}
#[cfg(target_family = "unix")]
fn platform_exit_code(status: std::process::ExitStatus) -> Option<i32> {
use std::os::unix::process::ExitStatusExt;
status.code().or_else(|| status.signal())
}
#[cfg(not(target_family = "unix"))]
fn platform_exit_code(status: std::process::ExitStatus) -> Option<i32> {
status.code()
}
impl std::fmt::Display for Code {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let reason = match *self {
Code::SUCCESS => "success",
Code::FAILURE => "failure",
Code::UNKNOWN => "unknown",
Code::USAGE_ERR => "usage",
Code::DATA_ERR => "data",
Code::NO_INPUT => "no input",
Code::NO_USER => "no user",
Code::NO_HOST => "no host",
Code::SERVICE_UNAVAILABLE => "unavailable",
Code::SOFTWARE_ERR => "software",
Code::OS_ERR => "os err",
Code::OS_FILE_ERR => "os file",
Code::CANT_CREAT => "cannot create",
Code::IO_ERR => "i/o error",
Code::TEMP_FAIL => "temporary failure",
Code::PROTOCOL_ERR => "protocol",
Code::NO_PERM => "permission denied",
Code::CONFIG_ERR => "config",
Code::NOT_EXECUTABLE => "not executable",
Code::NOT_FOUND => "not found",
Code::SIGHUP => "hangup signal",
Code::SIGINT => "terminal interrupt signal",
Code::SIGQUIT => "quit signal",
Code::SIGKILL => "kill signal",
Code::SIGTRAP => "trap signal",
Code::SIGABRT => "abort signal",
Code::SIGFPE => "floating point exception",
Code::SIGSEGV => "segmentation fault",
Code::SIGPIPE => "broken pipe signal",
Code::SIGALRM => "alarm",
Code::SIGTERM => "terminate signal",
_ => "unknown",
};
write!(f, "{} ({})", reason, self.0)
}
}