Skip to main content

zlayer_tui/
logger.rs

1//! Color detection and ANSI formatting for plain-text loggers.
2//!
3//! Provides [`detect_color_support`] to check environment variables for
4//! color capability, and [`colorize`] to conditionally wrap text in ANSI
5//! escape codes.
6//!
7//! For the ANSI color constants themselves, use [`crate::palette::ansi`]
8//! directly (e.g. `crate::palette::ansi::GREEN`).
9
10use crate::palette::ansi;
11
12/// Detect if the terminal supports ANSI colors.
13///
14/// Checks the following environment variables in order:
15/// - `NO_COLOR` -- if set, colors are disabled (see <https://no-color.org>)
16/// - `FORCE_COLOR` -- if set, colors are forced on
17/// - `CI` -- CI environments typically support colors
18/// - `TERM` -- colors are disabled only when set to `"dumb"`
19///
20/// Returns `false` when none of the above variables are set.
21#[must_use]
22pub fn detect_color_support() -> bool {
23    if std::env::var("NO_COLOR").is_ok() {
24        return false;
25    }
26    if std::env::var("FORCE_COLOR").is_ok() {
27        return true;
28    }
29    if std::env::var("CI").is_ok() {
30        return true;
31    }
32    if let Ok(term) = std::env::var("TERM") {
33        return term != "dumb";
34    }
35    false
36}
37
38/// Wrap `text` with an ANSI color code if `enabled` is true.
39///
40/// When `enabled` is `true`, returns `format!("{color}{text}{RESET}")`.
41/// When `false`, returns a plain copy of the text with no escape codes.
42///
43/// # Example
44///
45/// ```
46/// use zlayer_tui::logger::colorize;
47/// use zlayer_tui::palette::ansi;
48///
49/// let green = colorize("ok", ansi::GREEN, true);
50/// assert!(green.contains("\x1b[32m"));
51///
52/// let plain = colorize("ok", ansi::GREEN, false);
53/// assert_eq!(plain, "ok");
54/// ```
55#[must_use]
56pub fn colorize(text: &str, color: &str, enabled: bool) -> String {
57    if enabled {
58        format!("{}{}{}", color, text, ansi::RESET)
59    } else {
60        text.to_string()
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use crate::palette::ansi;
68
69    #[test]
70    fn colorize_enabled_wraps_with_ansi() {
71        let result = colorize("hello", ansi::GREEN, true);
72        assert_eq!(result, format!("{}hello{}", ansi::GREEN, ansi::RESET));
73    }
74
75    #[test]
76    fn colorize_disabled_returns_plain_text() {
77        let result = colorize("hello", ansi::RED, false);
78        assert_eq!(result, "hello");
79    }
80
81    #[test]
82    fn colorize_empty_text() {
83        let result = colorize("", ansi::CYAN, true);
84        assert_eq!(result, format!("{}{}", ansi::CYAN, ansi::RESET));
85    }
86
87    #[test]
88    fn detect_color_support_returns_bool() {
89        // Primarily a smoke test -- we cannot easily control the
90        // environment in unit tests, but this verifies the function
91        // runs without panicking and returns a boolean.
92        let _supports_color: bool = detect_color_support();
93    }
94}