ralph_workflow/json_parser/
terminal.rs1use crate::logger::ColorEnvironment;
7use std::io::IsTerminal;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum TerminalMode {
12 Full,
14 Basic,
16 None,
18}
19
20impl TerminalMode {
21 pub fn detect_with_env(env: &dyn ColorEnvironment) -> Self {
23 if env.get_var("NO_COLOR").is_some() {
25 return Self::None;
26 }
27
28 if let Some(val) = env.get_var("CLICOLOR_FORCE") {
30 if val != "0" {
31 return if env.is_terminal() {
32 Self::Full
33 } else {
34 Self::Basic
35 };
36 }
37 }
38
39 if let Some(val) = env.get_var("CLICOLOR") {
41 if val == "0" {
42 return Self::None;
43 }
44 }
45
46 if !env.is_terminal() {
48 return Self::None;
49 }
50
51 match env.get_var("TERM") {
53 Some(term) => {
54 let term_lower = term.to_lowercase();
55
56 if term_lower == "dumb" {
57 return Self::Basic;
58 }
59
60 let capable_terminals = [
61 "xterm",
62 "xterm-",
63 "vt100",
64 "vt102",
65 "vt220",
66 "vt320",
67 "screen",
68 "tmux",
69 "ansi",
70 "rxvt",
71 "konsole",
72 "gnome-terminal",
73 "iterm",
74 "alacritty",
75 "kitty",
76 "wezterm",
77 "foot",
78 ];
79
80 for capable in &capable_terminals {
81 if term_lower.starts_with(capable) {
82 return Self::Full;
83 }
84 }
85
86 Self::Basic
87 }
88 None => Self::Basic,
89 }
90 }
91
92 pub fn detect() -> Self {
94 Self::detect_with_env(&RealTerminalEnvironment)
95 }
96}
97
98impl Default for TerminalMode {
99 fn default() -> Self {
100 Self::detect()
101 }
102}
103
104struct RealTerminalEnvironment;
106
107impl ColorEnvironment for RealTerminalEnvironment {
108 fn get_var(&self, name: &str) -> Option<String> {
109 std::env::var(name).ok()
110 }
111
112 fn is_terminal(&self) -> bool {
113 std::io::stdout().is_terminal()
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use std::collections::HashMap;
121
122 struct MockTerminalEnv {
123 vars: HashMap<String, String>,
124 is_tty: bool,
125 }
126
127 impl MockTerminalEnv {
128 fn new() -> Self {
129 Self {
130 vars: HashMap::new(),
131 is_tty: true,
132 }
133 }
134
135 fn with_var(mut self, name: &str, value: &str) -> Self {
136 self.vars.insert(name.to_string(), value.to_string());
137 self
138 }
139
140 fn not_tty(mut self) -> Self {
141 self.is_tty = false;
142 self
143 }
144 }
145
146 impl ColorEnvironment for MockTerminalEnv {
147 fn get_var(&self, name: &str) -> Option<String> {
148 self.vars.get(name).cloned()
149 }
150
151 fn is_terminal(&self) -> bool {
152 self.is_tty
153 }
154 }
155
156 #[test]
157 fn test_terminal_mode_no_color() {
158 let env = MockTerminalEnv::new().with_var("NO_COLOR", "1");
159 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::None);
160 }
161
162 #[test]
163 fn test_terminal_mode_clicolor_force_tty() {
164 let env = MockTerminalEnv::new().with_var("CLICOLOR_FORCE", "1");
165 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Full);
166 }
167
168 #[test]
169 fn test_terminal_mode_clicolor_force_not_tty() {
170 let env = MockTerminalEnv::new()
171 .with_var("CLICOLOR_FORCE", "1")
172 .not_tty();
173 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Basic);
174 }
175
176 #[test]
177 fn test_terminal_mode_clicolor_zero() {
178 let env = MockTerminalEnv::new().with_var("CLICOLOR", "0");
179 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::None);
180 }
181
182 #[test]
183 fn test_terminal_mode_term_dumb() {
184 let env = MockTerminalEnv::new().with_var("TERM", "dumb");
185 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Basic);
186 }
187
188 #[test]
189 fn test_terminal_mode_term_xterm() {
190 let env = MockTerminalEnv::new().with_var("TERM", "xterm-256color");
191 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Full);
192 }
193
194 #[test]
195 fn test_terminal_mode_not_tty() {
196 let env = MockTerminalEnv::new().not_tty();
197 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::None);
198 }
199
200 #[test]
201 fn test_terminal_mode_unknown_term() {
202 let env = MockTerminalEnv::new().with_var("TERM", "unknown-terminal");
203 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Basic);
204 }
205
206 #[test]
207 fn test_terminal_mode_partial_eq() {
208 assert_eq!(TerminalMode::Full, TerminalMode::Full);
209 assert_eq!(TerminalMode::Basic, TerminalMode::Basic);
210 assert_eq!(TerminalMode::None, TerminalMode::None);
211 assert_ne!(TerminalMode::Full, TerminalMode::Basic);
212 }
213}