thag_rs 0.2.0

A versatile cross-platform playground and REPL for Rust snippets, expressions and programs. Accepts a script file or dynamic options.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
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;

/// Result type alias for Thag operations that may fail with a `ThagError`.
pub type ThagResult<T> = Result<T, ThagError>;

#[derive(Debug)]
/// Error type for all Thag operations.
///
/// This enum encompasses all possible error conditions that can occur
/// during Thag's execution, including I/O errors, parsing errors,
/// configuration errors, and errors from external dependencies.
pub enum ThagError {
    /// Error from `thag_common` operations
    Common(thag_common::ThagCommonError),
    /// Error from `thag_common::config`
    #[cfg(feature = "config")]
    Config(thag_common::ConfigError),
    /// Error from `thag_styling` operations
    #[cfg(feature = "thag_styling")]
    Styling(thag_styling::StylingError),
    #[cfg(feature = "bitflags")]
    /// Error parsing bitflags values
    BitFlagsParse(BitFlagsParseError), // For bitflags parse error
    /// User cancelled the operation
    Cancelled, // For user electing to cancel
    #[cfg(feature = "clap")]
    /// Error from `clap` command-line parsing
    ClapError(ClapError), // For clap errors
    /// Error during Cargo build or program execution
    Command(&'static str), // For errors during Cargo build or program execution
    /// Boxed dynamic error from third-party libraries
    Dyn(Box<dyn Error + Send + Sync + 'static>), // For boxed dynamic errors from 3rd parties
    /// Simple error from a string message
    FromStr(Cow<'static, str>), // For simple errors from a string
    /// Error converting from UTF-8 bytes
    FromUtf8(FromUtf8Error), // For simple errors from a utf8 array
    /// I/O operation error
    Io(std::io::Error), // For I/O errors
    /// Mutex guard lock error
    LockMutexGuard(&'static str), // For lock errors with MutexGuard
    /// Logic error in program flow
    Logic(&'static str), // For logic errors
    /// Error unwrapping None from Option
    NoneOption(String), // For unwrapping Options
    /// Error converting `OsString` to valid UTF-8
    OsString(std::ffi::OsString), // For unconvertible OsStrings
    /// Generic parsing error
    Parse,
    /// Integer parsing error
    ParseInt(ParseIntError),
    /// Profiling system error
    #[cfg(feature = "profiling")]
    Profiling(String),
    #[cfg(feature = "reedline")]
    /// Reedline terminal interaction error
    Reedline(ReedlineError), // For reedline errors
    #[cfg(feature = "serde_merge")]
    /// Serde merge operation error
    SerdeMerge(SerdeMergeError), // For serde_merge errors
    /// Strum enum parsing error
    StrumParse(StrumParseError), // For strum parse enum
    #[cfg(feature = "syn")]
    /// Syn syntax parsing error
    Syn(SynError), // For syn errors
    #[cfg(feature = "color_detect")]
    /// Terminal background detection error
    Termbg(termbg::Error), // For termbg errors
    /// Theme-related error
    Theme(ThemeError), // For thag_rs::styling theme errors
    /// TOML deserialization error
    TomlDe(TomlDeError), // For TOML deserialization errors
    /// TOML serialization error
    TomlSer(TomlSerError), // For TOML serialization errors
    #[cfg(feature = "cargo_toml")]
    /// Cargo.toml parsing error
    Toml(CargoTomlError), // For cargo_toml errors
    /// Unsupported terminal type error
    UnsupportedTerm(String), // For terminal interrogation
    /// Configuration validation error
    Validation(String), // For config.toml and similar validation
    /// Environment variable error
    VarError(std::env::VarError), // For std::env::var errors
}

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 {
        // Enable backtraces in all build types - this is a development tool after all
        use std::backtrace::Backtrace;

        // Get more context about the IO error
        eprintln!("IO Error: {err}");
        eprintln!("Kind: {:?}", err.kind());
        eprintln!("Raw OS Error: {:?}", err.raw_os_error());

        // Always collect and print a backtrace for IO errors
        // This ensures we get diagnostics in both debug and release builds
        let backtrace = Backtrace::force_capture(); // Always capture, even in release mode
        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 {
            // Use display formatting instead of debug formatting where possible
            #[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) => {
                // Extract the actual error message without all the nested structure
                let msg = e.to_string();
                write!(f, "toml::de::Error: {}", disentangle(msg.as_str()))?;
                Ok(())
            }

            Self::TomlSer(e) => {
                // Extract the actual error message without all the nested structure
                let msg = e.to_string();
                write!(f, "toml::ser::Error: {}", disentangle(msg.as_str()))?;
                Ok(())
            }
            // Self::Toml(e) => write!(f, "{e}"),
            #[cfg(feature = "cargo_toml")]
            Self::Toml(e) => {
                // Extract the actual error message without all the nested structure
                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)> {
        // Force all match arms to return the same type by providing an explicit type annotation
        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,
            // Use as_ref() to convert from Box<dyn Error + Send + Sync> to &dyn Error
            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)]
/// Error type for theme-related operations.
///
/// This enum encompasses all possible error conditions that can occur
/// during theme loading, validation, and application, including color
/// support mismatches, invalid configurations, and terminal compatibility issues.
pub enum ThemeError {
    /// Failed to detect terminal background color
    BackgroundDetectionFailed,
    /// Color support mismatch between theme requirements and terminal capabilities
    ColorSupportMismatch {
        /// The color support level required by the theme
        required: ColorSupport,
        /// The color support level available in the terminal
        available: ColorSupport,
    },
    /// Attempted to use a dark theme with a light terminal background
    DarkThemeLightTerm,
    /// Terminal does not support sufficient colors for the requested operation
    InsufficientColorSupport,
    /// Invalid ANSI escape code format
    InvalidAnsiCode(String),
    /// Invalid color support specification
    InvalidColorSupport(String),
    /// Invalid color value format or specification
    InvalidColorValue(String),
    /// Invalid style attribute specification
    InvalidStyle(String),
    /// Invalid terminal background luminance value
    InvalidTermBgLuma(String),
    /// Attempted to use a light theme with a dark terminal background
    LightThemeDarkTerm,
    /// No valid background color found for the specified theme
    NoValidBackground(String),
    /// Terminal background luminance mismatch with theme requirements
    TermBgLumaMismatch {
        /// The background luminance required by the theme
        theme: TermBgLuma,
        /// The actual background luminance of the terminal
        terminal: TermBgLuma,
    },
    /// Unknown or unrecognized theme name
    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 {}

// From trait implementations for new error types
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)
    }
}