1use anyhow::Result;
12use std::sync::OnceLock;
13use termcolor::ColorChoice;
14
15static COR_CACHE: OnceLock<ColorChoice> = OnceLock::new();
17
18pub fn initialize(no_color: bool) -> Result<()> {
23 let choice = determine_color(no_color);
24 let _ = COR_CACHE.set(choice);
25 tracing::debug!("terminal color configuration: {:?}", choice);
26 Ok(())
27}
28
29#[must_use]
34pub fn color_choice() -> ColorChoice {
35 *COR_CACHE.get().unwrap_or(&ColorChoice::Never)
36}
37
38#[must_use]
43pub fn is_interactive() -> bool {
44 use std::io::IsTerminal;
45
46 if std::env::var("TERM").as_deref() == Ok("dumb") {
48 return false;
49 }
50
51 std::io::stdout().is_terminal()
52}
53
54fn determine_color(no_color_cli: bool) -> ColorChoice {
56 if no_color_cli {
58 return ColorChoice::Never;
59 }
60
61 if std::env::var("NO_COLOR").is_ok() {
63 return ColorChoice::Never;
64 }
65
66 if std::env::var("CLICOLOR_FORCE").as_deref() == Ok("1") {
68 return ColorChoice::Always;
69 }
70
71 if is_interactive() {
73 ColorChoice::Auto
74 } else {
75 ColorChoice::Never
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn no_color_cli_returns_never() {
85 let choice = determine_color(true);
86 assert!(matches!(choice, ColorChoice::Never));
87 }
88
89 #[test]
90 fn no_color_env_returns_never() {
91 let anterior = std::env::var("NO_COLOR").ok();
93 let anterior_force = std::env::var("CLICOLOR_FORCE").ok();
94
95 std::env::set_var("NO_COLOR", "1");
96 std::env::remove_var("CLICOLOR_FORCE");
97
98 let choice = determine_color(false);
99 assert!(matches!(choice, ColorChoice::Never));
100
101 match anterior {
103 Some(v) => std::env::set_var("NO_COLOR", v),
104 None => std::env::remove_var("NO_COLOR"),
105 }
106 match anterior_force {
107 Some(v) => std::env::set_var("CLICOLOR_FORCE", v),
108 None => std::env::remove_var("CLICOLOR_FORCE"),
109 }
110 }
111
112 #[test]
113 fn clicolor_force_returns_always() {
114 let anterior = std::env::var("NO_COLOR").ok();
115 let anterior_force = std::env::var("CLICOLOR_FORCE").ok();
116
117 std::env::remove_var("NO_COLOR");
118 std::env::set_var("CLICOLOR_FORCE", "1");
119
120 let choice = determine_color(false);
121 assert!(matches!(choice, ColorChoice::Always));
122
123 match anterior {
125 Some(v) => std::env::set_var("NO_COLOR", v),
126 None => std::env::remove_var("NO_COLOR"),
127 }
128 match anterior_force {
129 Some(v) => std::env::set_var("CLICOLOR_FORCE", v),
130 None => std::env::remove_var("CLICOLOR_FORCE"),
131 }
132 }
133
134 #[test]
135 fn color_choice_returns_never_without_init() {
136 let _ = color_choice();
140 }
141
142 #[test]
143 fn is_interactive_returns_bool() {
144 let _ = is_interactive();
146 }
147}