1#![forbid(unsafe_code)]
4use anyhow::Result;
14use std::sync::OnceLock;
15use termcolor::ColorChoice;
16
17static COLOR_CACHE: OnceLock<ColorChoice> = OnceLock::new();
22
23pub fn initialize(no_color: bool) -> Result<()> {
28 let choice = determine_color(no_color);
29 let _ = COLOR_CACHE.set(choice);
30 tracing::debug!("terminal color configuration: {:?}", choice);
31 Ok(())
32}
33
34#[must_use]
39pub fn color_choice() -> ColorChoice {
40 *COLOR_CACHE.get().unwrap_or(&ColorChoice::Never)
41}
42
43#[must_use]
48pub fn is_interactive() -> bool {
49 use std::io::IsTerminal;
50
51 if std::env::var("TERM").as_deref() == Ok("dumb") {
53 return false;
54 }
55
56 std::io::stdout().is_terminal()
57}
58
59fn determine_color(no_color_cli: bool) -> ColorChoice {
61 if no_color_cli {
63 return ColorChoice::Never;
64 }
65
66 if std::env::var("NO_COLOR").is_ok() {
68 return ColorChoice::Never;
69 }
70
71 if std::env::var("CLICOLOR_FORCE").as_deref() == Ok("1") {
73 return ColorChoice::Always;
74 }
75
76 if is_interactive() {
78 ColorChoice::Auto
79 } else {
80 ColorChoice::Never
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn no_color_cli_returns_never() {
90 let choice = determine_color(true);
91 assert!(matches!(choice, ColorChoice::Never));
92 }
93
94 #[test]
95 #[serial_test::serial]
96 fn no_color_env_returns_never() {
97 let previous = std::env::var("NO_COLOR").ok();
99 let previous_force = std::env::var("CLICOLOR_FORCE").ok();
100
101 crate::test_util::env::set_var("NO_COLOR", "1");
102 crate::test_util::env::remove_var("CLICOLOR_FORCE");
103
104 let choice = determine_color(false);
105 assert!(matches!(choice, ColorChoice::Never));
106
107 match previous {
109 Some(v) => crate::test_util::env::set_var("NO_COLOR", v),
110 None => crate::test_util::env::remove_var("NO_COLOR"),
111 }
112 match previous_force {
113 Some(v) => crate::test_util::env::set_var("CLICOLOR_FORCE", v),
114 None => crate::test_util::env::remove_var("CLICOLOR_FORCE"),
115 }
116 }
117
118 #[test]
119 #[serial_test::serial]
120 fn clicolor_force_returns_always() {
121 let previous = std::env::var("NO_COLOR").ok();
122 let previous_force = std::env::var("CLICOLOR_FORCE").ok();
123
124 crate::test_util::env::remove_var("NO_COLOR");
125 crate::test_util::env::set_var("CLICOLOR_FORCE", "1");
126
127 let choice = determine_color(false);
128 assert!(matches!(choice, ColorChoice::Always));
129
130 match previous {
132 Some(v) => crate::test_util::env::set_var("NO_COLOR", v),
133 None => crate::test_util::env::remove_var("NO_COLOR"),
134 }
135 match previous_force {
136 Some(v) => crate::test_util::env::set_var("CLICOLOR_FORCE", v),
137 None => crate::test_util::env::remove_var("CLICOLOR_FORCE"),
138 }
139 }
140
141 #[test]
142 fn color_choice_returns_never_without_init() {
143 let _ = color_choice();
147 }
148
149 #[test]
150 fn is_interactive_returns_bool() {
151 let _ = is_interactive();
153 }
154}