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}