use std::borrow::Cow;
use std::num::ParseIntError;
use std::string::FromUtf8Error;
use std::sync::{MutexGuard, PoisonError as LockError};
use std::{error::Error, io};
use strum::ParseError as StrumParseError;
use thag_common::disentangle;
use thag_common::{ColorSupport, TermBgLuma};
use toml::de::Error as TomlDeError;
use toml::ser::Error as TomlSerError;
#[cfg(feature = "bitflags")]
use bitflags::parser::ParseError as BitFlagsParseError;
#[cfg(feature = "cargo_toml")]
use cargo_toml::Error as CargoTomlError;
#[cfg(feature = "clap")]
use clap::error::Error as ClapError;
#[cfg(feature = "reedline")]
use reedline::ReedlineError;
#[cfg(feature = "serde_merge")]
use serde_merge::error::Error as SerdeMergeError;
#[cfg(feature = "syn")]
use syn::Error as SynError;
pub type ThagResult<T> = Result<T, ThagError>;
#[derive(Debug)]
pub enum ThagError {
Common(thag_common::ThagCommonError),
#[cfg(feature = "config")]
Config(thag_common::ConfigError),
#[cfg(feature = "thag_styling")]
Styling(thag_styling::StylingError),
#[cfg(feature = "bitflags")]
BitFlagsParse(BitFlagsParseError), Cancelled, #[cfg(feature = "clap")]
ClapError(ClapError), Command(&'static str), Dyn(Box<dyn Error + Send + Sync + 'static>), FromStr(Cow<'static, str>), FromUtf8(FromUtf8Error), Io(std::io::Error), LockMutexGuard(&'static str), Logic(&'static str), NoneOption(String), OsString(std::ffi::OsString), Parse,
ParseInt(ParseIntError),
#[cfg(feature = "profiling")]
Profiling(String),
#[cfg(feature = "reedline")]
Reedline(ReedlineError), #[cfg(feature = "serde_merge")]
SerdeMerge(SerdeMergeError), StrumParse(StrumParseError), #[cfg(feature = "syn")]
Syn(SynError), #[cfg(feature = "color_detect")]
Termbg(termbg::Error), Theme(ThemeError), TomlDe(TomlDeError), TomlSer(TomlSerError), #[cfg(feature = "cargo_toml")]
Toml(CargoTomlError), UnsupportedTerm(String), Validation(String), VarError(std::env::VarError), }
impl From<FromUtf8Error> for ThagError {
fn from(err: FromUtf8Error) -> Self {
Self::FromUtf8(err)
}
}
impl From<io::Error> for ThagError {
fn from(err: io::Error) -> Self {
use std::backtrace::Backtrace;
eprintln!("IO Error: {err}");
eprintln!("Kind: {:?}", err.kind());
eprintln!("Raw OS Error: {:?}", err.raw_os_error());
let backtrace = Backtrace::force_capture(); eprintln!("Location:\n{backtrace}");
Self::Io(err)
}
}
#[cfg(feature = "clap")]
impl From<ClapError> for ThagError {
fn from(err: ClapError) -> Self {
Self::ClapError(err)
}
}
impl From<StrumParseError> for ThagError {
fn from(err: StrumParseError) -> Self {
Self::StrumParse(err)
}
}
impl From<ThemeError> for ThagError {
fn from(err: ThemeError) -> Self {
Self::Theme(err)
}
}
impl From<TomlDeError> for ThagError {
fn from(err: TomlDeError) -> Self {
Self::TomlDe(err)
}
}
impl From<TomlSerError> for ThagError {
fn from(err: TomlSerError) -> Self {
Self::TomlSer(err)
}
}
#[cfg(feature = "cargo_toml")]
impl From<CargoTomlError> for ThagError {
fn from(err: CargoTomlError) -> Self {
Self::Toml(err)
}
}
impl From<String> for ThagError {
fn from(s: String) -> Self {
Self::FromStr(Cow::Owned(s))
}
}
impl From<&'static str> for ThagError {
fn from(s: &'static str) -> Self {
Self::FromStr(Cow::Borrowed(s))
}
}
impl From<ParseIntError> for ThagError {
fn from(err: ParseIntError) -> Self {
Self::ParseInt(err)
}
}
#[cfg(feature = "profiling")]
impl From<thag_profiler::ProfileError> for ThagError {
fn from(err: thag_profiler::ProfileError) -> Self {
Self::Profiling(err.to_string())
}
}
#[cfg(feature = "reedline")]
impl From<ReedlineError> for ThagError {
fn from(err: ReedlineError) -> Self {
Self::Reedline(err)
}
}
#[cfg(feature = "serde_merge")]
impl From<SerdeMergeError> for ThagError {
fn from(err: SerdeMergeError) -> Self {
Self::SerdeMerge(err)
}
}
#[cfg(feature = "syn")]
impl From<SynError> for ThagError {
fn from(err: SynError) -> Self {
Self::Syn(err)
}
}
impl<'a, T> From<LockError<MutexGuard<'a, T>>> for ThagError {
fn from(_err: LockError<MutexGuard<'a, T>>) -> Self {
Self::LockMutexGuard("Lock poisoned")
}
}
#[cfg(feature = "bitflags")]
impl From<BitFlagsParseError> for ThagError {
fn from(err: BitFlagsParseError) -> Self {
Self::BitFlagsParse(err)
}
}
impl From<Box<dyn Error + Send + Sync + 'static>> for ThagError {
fn from(err: Box<dyn Error + Send + Sync + 'static>) -> Self {
Self::Dyn(err)
}
}
#[cfg(feature = "color_detect")]
impl From<termbg::Error> for ThagError {
fn from(err: termbg::Error) -> Self {
Self::Termbg(err)
}
}
impl From<std::env::VarError> for ThagError {
fn from(err: std::env::VarError) -> Self {
Self::VarError(err)
}
}
impl std::fmt::Display for ThagError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(feature = "bitflags")]
Self::BitFlagsParse(e) => write!(f, "{e}"),
Self::Cancelled => write!(f, "Cancelled"),
#[cfg(feature = "clap")]
Self::ClapError(e) => write!(f, "{e}"),
Self::Command(s) | Self::Logic(s) => {
for line in s.lines() {
writeln!(f, "{line}")?;
}
Ok(())
}
Self::NoneOption(s) => {
for line in s.lines() {
writeln!(f, "{line}")?;
}
Ok(())
}
Self::Dyn(e) => write!(f, "{e}"),
Self::FromStr(s) => {
for line in s.lines() {
writeln!(f, "{line}")?;
}
Ok(())
}
Self::FromUtf8(e) => write!(f, "{e}"),
Self::Io(e) => write!(f, "{e}"),
Self::LockMutexGuard(e) => write!(f, "{e}"),
Self::OsString(o) => writeln!(f, "<invalid UTF-8: {o:?}>"),
Self::Parse => write!(f, "Error parsing source data"),
Self::ParseInt(e) => write!(f, "{e}"),
#[cfg(feature = "profiling")]
Self::Profiling(e) => write!(f, "{e}"),
#[cfg(feature = "reedline")]
Self::Reedline(e) => write!(f, "{e}"),
#[cfg(feature = "serde_merge")]
Self::SerdeMerge(e) => write!(f, "{e}"),
Self::StrumParse(e) => write!(f, "{e}"),
#[cfg(feature = "syn")]
Self::Syn(e) => write!(f, "{e}"),
#[cfg(feature = "color_detect")]
Self::Termbg(e) => write!(f, "{e}"),
Self::Theme(e) => write!(f, "{e}"),
Self::TomlDe(e) => {
let msg = e.to_string();
write!(f, "toml::de::Error: {}", disentangle(msg.as_str()))?;
Ok(())
}
Self::TomlSer(e) => {
let msg = e.to_string();
write!(f, "toml::ser::Error: {}", disentangle(msg.as_str()))?;
Ok(())
}
#[cfg(feature = "cargo_toml")]
Self::Toml(e) => {
let msg = e.to_string();
write!(f, "cargo_toml error: {}", disentangle(msg.as_str()))?;
Ok(())
}
Self::UnsupportedTerm(e) => write!(f, "Unsupported terminal type {e}"),
Self::Validation(e) => write!(f, "{e}"),
Self::VarError(e) => write!(f, "{e}"),
Self::Common(e) => write!(f, "Common error: {e}"),
#[cfg(feature = "config")]
Self::Config(e) => write!(f, "Config error: {e}"),
#[cfg(feature = "thag_styling")]
Self::Styling(e) => write!(f, "Styling error: {e}"),
}
}
}
impl Error for ThagError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
let result: Option<&(dyn Error + 'static)> = match self {
#[cfg(feature = "bitflags")]
Self::BitFlagsParse(e) => Some(e),
Self::Cancelled => None,
#[cfg(feature = "clap")]
Self::ClapError(e) => Some(e),
Self::Command(_) => None,
Self::Dyn(e) => Some(e.as_ref()),
Self::FromStr(_) => None,
Self::FromUtf8(e) => Some(e),
Self::Io(e) => Some(e),
Self::LockMutexGuard(_) => None,
Self::Logic(_) => None,
Self::NoneOption(_) => None,
Self::OsString(_) => None,
Self::Parse => None,
Self::ParseInt(e) => Some(e),
#[cfg(feature = "profiling")]
Self::Profiling(_) => None,
#[cfg(feature = "reedline")]
Self::Reedline(e) => Some(e),
#[cfg(feature = "serde_merge")]
Self::SerdeMerge(e) => Some(e),
Self::StrumParse(e) => Some(e),
#[cfg(feature = "syn")]
Self::Syn(e) => Some(e),
#[cfg(feature = "color_detect")]
Self::Termbg(e) => Some(e),
Self::Theme(e) => Some(e),
Self::TomlDe(e) => Some(e),
Self::TomlSer(e) => Some(e),
#[cfg(feature = "cargo_toml")]
Self::Toml(e) => Some(e),
Self::UnsupportedTerm(_) => None,
Self::Validation(_) => None,
Self::VarError(e) => Some(e),
Self::Common(e) => Some(e),
#[cfg(feature = "config")]
Self::Config(e) => Some(e),
#[cfg(feature = "thag_styling")]
Self::Styling(e) => Some(e),
};
result
}
}
#[derive(Debug)]
pub enum ThemeError {
BackgroundDetectionFailed,
ColorSupportMismatch {
required: ColorSupport,
available: ColorSupport,
},
DarkThemeLightTerm,
InsufficientColorSupport,
InvalidAnsiCode(String),
InvalidColorSupport(String),
InvalidColorValue(String),
InvalidStyle(String),
InvalidTermBgLuma(String),
LightThemeDarkTerm,
NoValidBackground(String),
TermBgLumaMismatch {
theme: TermBgLuma,
terminal: TermBgLuma,
},
UnknownTheme(String),
}
impl std::fmt::Display for ThemeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BackgroundDetectionFailed => {
write!(f, "Background RGB not detected or configured for terminal")
}
Self::ColorSupportMismatch { required, available } => {
write!(f, "Theme requires {required:?} colors but terminal only supports {available:?}")
}
Self::DarkThemeLightTerm => write!(
f,
"Only light themes may be selected for a light terminal background."
),
Self::InsufficientColorSupport => write!(
f,
"Configured or detected level of terminal colour support is insufficient for this theme."
),
Self::InvalidAnsiCode(e) => write!(f, "{e}"),
Self::InvalidColorSupport(msg) => write!(f, "Invalid color support: {msg}"),
Self::InvalidColorValue(msg) => write!(f, "Invalid color value: {msg}"),
Self::InvalidStyle(style) => write!(f, "Invalid style attribute: {style}"),
Self::InvalidTermBgLuma(name) => write!(f, "Unknown value: must be `light` or `dark`: {name}"),
Self::LightThemeDarkTerm => write!(
f,
"Only dark themes may be selected for a dark terminal background."
),
Self::NoValidBackground(theme) => write!(f, "No valid background found for theme {theme}"),
Self::TermBgLumaMismatch { theme, terminal } => {
write!(f, "Theme requires {theme:?} background but terminal is {terminal:?}")
}
Self::UnknownTheme(name) => write!(f, "Unknown theme: {name}"),
}
}
}
impl std::error::Error for ThemeError {}
impl From<thag_common::ThagCommonError> for ThagError {
fn from(err: thag_common::ThagCommonError) -> Self {
Self::Common(err)
}
}
#[cfg(feature = "config")]
impl From<thag_common::ConfigError> for ThagError {
fn from(err: thag_common::ConfigError) -> Self {
Self::Config(err)
}
}
#[cfg(feature = "thag_styling")]
impl From<thag_styling::StylingError> for ThagError {
fn from(err: thag_styling::StylingError) -> Self {
Self::Styling(err)
}
}