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 #[must_use]
94 pub fn detect() -> Self {
95 Self::detect_with_env(&RealTerminalEnvironment)
96 }
97}
98
99impl Default for TerminalMode {
100 fn default() -> Self {
101 Self::detect()
102 }
103}
104
105struct RealTerminalEnvironment;
107
108impl ColorEnvironment for RealTerminalEnvironment {
109 fn get_var(&self, name: &str) -> Option<String> {
110 std::env::var(name).ok()
111 }
112
113 fn is_terminal(&self) -> bool {
114 std::io::stdout().is_terminal()
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use std::collections::HashMap;
122
123 struct MockTerminalEnv {
124 vars: HashMap<String, String>,
125 is_tty: bool,
126 }
127
128 impl MockTerminalEnv {
129 fn new() -> Self {
130 Self {
131 vars: HashMap::new(),
132 is_tty: true,
133 }
134 }
135
136 fn with_var(mut self, name: &str, value: &str) -> Self {
137 self.vars.insert(name.to_string(), value.to_string());
138 self
139 }
140
141 fn not_tty(mut self) -> Self {
142 self.is_tty = false;
143 self
144 }
145 }
146
147 impl ColorEnvironment for MockTerminalEnv {
148 fn get_var(&self, name: &str) -> Option<String> {
149 self.vars.get(name).cloned()
150 }
151
152 fn is_terminal(&self) -> bool {
153 self.is_tty
154 }
155 }
156
157 #[test]
158 fn test_terminal_mode_no_color() {
159 let env = MockTerminalEnv::new().with_var("NO_COLOR", "1");
160 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::None);
161 }
162
163 #[test]
164 fn test_terminal_mode_clicolor_force_tty() {
165 let env = MockTerminalEnv::new().with_var("CLICOLOR_FORCE", "1");
166 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Full);
167 }
168
169 #[test]
170 fn test_terminal_mode_clicolor_force_not_tty() {
171 let env = MockTerminalEnv::new()
172 .with_var("CLICOLOR_FORCE", "1")
173 .not_tty();
174 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Basic);
175 }
176
177 #[test]
178 fn test_terminal_mode_clicolor_zero() {
179 let env = MockTerminalEnv::new().with_var("CLICOLOR", "0");
180 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::None);
181 }
182
183 #[test]
184 fn test_terminal_mode_term_dumb() {
185 let env = MockTerminalEnv::new().with_var("TERM", "dumb");
186 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Basic);
187 }
188
189 #[test]
190 fn test_terminal_mode_term_xterm() {
191 let env = MockTerminalEnv::new().with_var("TERM", "xterm-256color");
192 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Full);
193 }
194
195 #[test]
196 fn test_terminal_mode_not_tty() {
197 let env = MockTerminalEnv::new().not_tty();
198 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::None);
199 }
200
201 #[test]
202 fn test_terminal_mode_unknown_term() {
203 let env = MockTerminalEnv::new().with_var("TERM", "unknown-terminal");
204 assert_eq!(TerminalMode::detect_with_env(&env), TerminalMode::Basic);
205 }
206
207 #[test]
208 fn test_terminal_mode_partial_eq() {
209 assert_eq!(TerminalMode::Full, TerminalMode::Full);
210 assert_eq!(TerminalMode::Basic, TerminalMode::Basic);
211 assert_eq!(TerminalMode::None, TerminalMode::None);
212 assert_ne!(TerminalMode::Full, TerminalMode::Basic);
213 }
214}