use std::fmt::{self, Display, Formatter};
use std::io::Write;
use clap::CommandFactory;
use itertools::{Itertools, Position};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use crate::prelude::ClingFinished;
use crate::Run;
pub trait CliErrorHandler {
type Output;
fn unwrap_or_exit(self) -> Self::Output;
fn then_exit(self) -> !;
}
pub enum CliError {
InvalidHandler(String),
Failed,
FailedWithMessage(String),
FailedWithMessageAndCode(String, u8),
ClapError(clap::Error),
InputString,
Other(anyhow::Error),
OtherWithCode(anyhow::Error, u8),
}
impl std::error::Error for CliError {}
impl From<anyhow::Error> for CliError {
fn from(value: anyhow::Error) -> Self {
CliError::Other(value)
}
}
impl From<std::io::Error> for CliError {
fn from(value: std::io::Error) -> Self {
CliError::Other(value.into())
}
}
impl From<clap::Error> for CliError {
fn from(value: clap::Error) -> Self {
CliError::ClapError(value)
}
}
impl<T, E> CliErrorHandler for Result<T, E>
where
E: Into<CliError>,
{
type Output = T;
fn unwrap_or_exit(self) -> T {
match self {
| Ok(x) => x,
| Err(e) => {
let e = e.into();
e.print().unwrap();
e.exit()
}
}
}
fn then_exit(self) -> ! {
match self {
| Ok(_) => std::process::exit(0),
| Err(e) => {
let e = e.into();
e.print().unwrap();
e.exit()
}
}
}
}
impl Display for CliError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
| CliError::ClapError(e) => {
write!(f, "{}", e)
}
| CliError::Failed => {
write!(f, "Failed!")
}
| CliError::FailedWithMessage(e) => {
write!(f, "Failed: {}", e)
}
| CliError::FailedWithMessageAndCode(e, _) => {
write!(f, "Error: {}", e)
}
| CliError::Other(e) => {
write!(f, "Error: {:#}", e)
}
| CliError::OtherWithCode(e, _) => {
write!(f, "Error: {:#}", e)
}
| CliError::InputString => {
write!(f, "Input string cannot be parsed as UNIX shell command")
}
#[allow(unused_variables)]
| CliError::InvalidHandler(msg) => {
#[cfg(not(debug_assertions))]
let r = write!(
f,
"\n\n** Cling Handler Design Error **\n\n{}",
"Detailed error message available only in debug builds.",
);
#[cfg(debug_assertions)]
let r = write!(
f,
"\n\n** Cling Handler Design Error **\n\n{}",
msg
);
r
}
}
}
}
impl std::fmt::Debug for CliError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}
impl CliError {
pub fn print(&self) -> std::io::Result<()> {
let mut stderr = StandardStream::stderr(ColorChoice::Auto);
match self {
| CliError::ClapError(e) => {
e.print()
}
| CliError::Failed => {
print_formatted_error(&mut stderr, "Aborted!", "")
}
| CliError::FailedWithMessage(e) => {
print_formatted_error(&mut stderr, "", e)
}
| CliError::FailedWithMessageAndCode(e, _) => {
print_formatted_error(&mut stderr, "", e)
}
| CliError::Other(e) => {
print_anyhow_error(&mut stderr, "Error: ", e)
}
| CliError::OtherWithCode(e, _) => {
print_anyhow_error(&mut stderr, "Error: ", e)
}
| e @ CliError::InputString => {
print_formatted_error(&mut stderr, "", &e.to_string())
}
#[allow(unused_variables)]
| CliError::InvalidHandler(msg) => {
#[cfg(not(debug_assertions))]
let r = print_formatted_error(
&mut stderr,
"\n\n** Cling Handler Design Error **\n\n",
"Detailed error message available only in debug builds.",
);
#[cfg(debug_assertions)]
let r = print_formatted_error(
&mut stderr,
"\n\n** Cling Handler Design Error **\n\n",
msg,
);
r
}
}
}
pub fn exit_code(&self) -> u8 {
match self {
| CliError::FailedWithMessageAndCode(_, code) => *code,
| CliError::OtherWithCode(_, code) => *code,
| CliError::ClapError(e) => {
let code = e.exit_code();
code.try_into().unwrap_or(255)
}
| _ => 1,
}
}
pub fn exit(self) -> ! {
std::process::exit(self.exit_code() as i32)
}
pub fn into_finished<T: Run + clap::Parser>(self) -> ClingFinished<T> {
Into::into(self)
}
}
static_assertions::assert_impl_all!(CliError: Send, Sync);
fn print_formatted_error(
f: &mut StandardStream,
heading: &str,
msg: &str,
) -> std::io::Result<()> {
f.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
write!(f, "{}", heading)?;
f.reset()?;
writeln!(f, "{}", msg)?;
Ok(())
}
fn print_anyhow_error(
f: &mut StandardStream,
heading: &str,
err: &anyhow::Error,
) -> std::io::Result<()> {
f.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
write!(f, "{}", heading)?;
f.reset()?;
writeln!(f, "{}", err)?;
err.chain()
.skip(1)
.with_position()
.for_each(|(position, cause)| {
if position == Position::First {
let _ =
f.set_color(ColorSpec::new().set_fg(Some(Color::Magenta)));
let _ = writeln!(f, "");
let _ = writeln!(f, "Caused by:");
let _ = f.reset();
}
let symbol = if position == Position::Last {
"└─"
} else {
"├─"
};
let _ =
f.set_color(ColorSpec::new().set_italic(true).set_dimmed(true));
let _ = write!(f, " {} ", symbol);
let _ = f.reset();
let _ = writeln!(f, "{}", cause);
});
Ok(())
}
pub(crate) fn format_clap_error<I: CommandFactory>(
err: clap::Error,
) -> clap::Error {
let mut cmd = I::command();
err.format(&mut cmd)
}