1 | //! A couple of functions to enable and disable coloring. |
2 | |
3 | use std::default::Default; |
4 | use std::env; |
5 | use std::io::{self, IsTerminal}; |
6 | use std::sync::atomic::{AtomicBool, Ordering}; |
7 | |
8 | /// Sets a flag to the console to use a virtual terminal environment. |
9 | /// |
10 | /// This is primarily used for Windows 10 environments which will not correctly colorize |
11 | /// the outputs based on ANSI escape codes. |
12 | /// |
13 | /// The returned `Result` is _always_ `Ok(())`, the return type was kept to ensure backwards |
14 | /// compatibility. |
15 | /// |
16 | /// # Notes |
17 | /// > Only available to `Windows` build targets. |
18 | /// |
19 | /// # Example |
20 | /// ```rust |
21 | /// use colored::*; |
22 | /// control::set_virtual_terminal(false).unwrap(); |
23 | /// println!("{}", "bright cyan".bright_cyan()); // will print '[96mbright cyan[0m' on windows 10 |
24 | /// |
25 | /// control::set_virtual_terminal(true).unwrap(); |
26 | /// println!("{}", "bright cyan".bright_cyan()); // will print correctly |
27 | /// ``` |
28 | #[allow (clippy::result_unit_err)] |
29 | #[cfg (windows)] |
30 | pub fn set_virtual_terminal(use_virtual: bool) -> Result<(), ()> { |
31 | use windows_sys::Win32::System::Console::{ |
32 | GetConsoleMode, GetStdHandle, SetConsoleMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING, |
33 | STD_OUTPUT_HANDLE, |
34 | }; |
35 | |
36 | unsafe { |
37 | let handle = GetStdHandle(STD_OUTPUT_HANDLE); |
38 | let mut original_mode = 0; |
39 | GetConsoleMode(handle, &mut original_mode); |
40 | |
41 | let enabled = original_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING |
42 | == ENABLE_VIRTUAL_TERMINAL_PROCESSING; |
43 | |
44 | match (use_virtual, enabled) { |
45 | // not enabled, should be enabled |
46 | (true, false) => { |
47 | SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING | original_mode) |
48 | } |
49 | // already enabled, should be disabled |
50 | (false, true) => { |
51 | SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING ^ original_mode) |
52 | } |
53 | _ => 0, |
54 | }; |
55 | } |
56 | |
57 | Ok(()) |
58 | } |
59 | |
60 | /// A flag for whether coloring should occur. |
61 | pub struct ShouldColorize { |
62 | clicolor: bool, |
63 | clicolor_force: Option<bool>, |
64 | // XXX we can't use Option<Atomic> because we can't use &mut references to ShouldColorize |
65 | has_manual_override: AtomicBool, |
66 | manual_override: AtomicBool, |
67 | } |
68 | |
69 | /// Use this to force colored to ignore the environment and always/never colorize |
70 | /// See example/control.rs |
71 | pub fn set_override(override_colorize: bool) { |
72 | SHOULD_COLORIZE.set_override(override_colorize); |
73 | } |
74 | |
75 | /// Remove the manual override and let the environment decide if it's ok to colorize |
76 | /// See example/control.rs |
77 | pub fn unset_override() { |
78 | SHOULD_COLORIZE.unset_override(); |
79 | } |
80 | |
81 | lazy_static! { |
82 | /// The persistent [`ShouldColorize`]. |
83 | pub static ref SHOULD_COLORIZE: ShouldColorize = ShouldColorize::from_env(); |
84 | } |
85 | |
86 | impl Default for ShouldColorize { |
87 | fn default() -> ShouldColorize { |
88 | ShouldColorize { |
89 | clicolor: true, |
90 | clicolor_force: None, |
91 | has_manual_override: AtomicBool::new(false), |
92 | manual_override: AtomicBool::new(false), |
93 | } |
94 | } |
95 | } |
96 | |
97 | impl ShouldColorize { |
98 | /// Reads environment variables and checks if output is a tty to determine |
99 | /// whether colorization should be used or not. |
100 | /// `CLICOLOR_FORCE` takes highest priority, followed by `NO_COLOR`, |
101 | /// followed by `CLICOLOR` combined with tty check. |
102 | pub fn from_env() -> Self { |
103 | ShouldColorize { |
104 | clicolor: ShouldColorize::normalize_env(env::var("CLICOLOR" )).unwrap_or(true) |
105 | && io::stdout().is_terminal(), |
106 | clicolor_force: ShouldColorize::resolve_clicolor_force( |
107 | env::var("NO_COLOR" ), |
108 | env::var("CLICOLOR_FORCE" ), |
109 | ), |
110 | ..ShouldColorize::default() |
111 | } |
112 | } |
113 | |
114 | /// Returns if the current coloring is expected. |
115 | pub fn should_colorize(&self) -> bool { |
116 | if self.has_manual_override.load(Ordering::Relaxed) { |
117 | return self.manual_override.load(Ordering::Relaxed); |
118 | } |
119 | |
120 | if let Some(forced_value) = self.clicolor_force { |
121 | return forced_value; |
122 | } |
123 | |
124 | self.clicolor |
125 | } |
126 | |
127 | /// Use this to force colored to ignore the environment and always/never colorize |
128 | pub fn set_override(&self, override_colorize: bool) { |
129 | self.has_manual_override.store(true, Ordering::Relaxed); |
130 | self.manual_override |
131 | .store(override_colorize, Ordering::Relaxed); |
132 | } |
133 | |
134 | /// Remove the manual override and let the environment decide if it's ok to colorize |
135 | pub fn unset_override(&self) { |
136 | self.has_manual_override.store(false, Ordering::Relaxed); |
137 | } |
138 | |
139 | /* private */ |
140 | |
141 | fn normalize_env(env_res: Result<String, env::VarError>) -> Option<bool> { |
142 | match env_res { |
143 | Ok(string) => Some(string != "0" ), |
144 | Err(_) => None, |
145 | } |
146 | } |
147 | |
148 | fn resolve_clicolor_force( |
149 | no_color: Result<String, env::VarError>, |
150 | clicolor_force: Result<String, env::VarError>, |
151 | ) -> Option<bool> { |
152 | if ShouldColorize::normalize_env(clicolor_force) == Some(true) { |
153 | Some(true) |
154 | } else if ShouldColorize::normalize_env(no_color).is_some() { |
155 | Some(false) |
156 | } else { |
157 | None |
158 | } |
159 | } |
160 | } |
161 | |
162 | #[cfg (test)] |
163 | mod specs { |
164 | use super::*; |
165 | use rspec; |
166 | use std::env; |
167 | |
168 | #[test ] |
169 | fn clicolor_behavior() { |
170 | rspec::run(&rspec::describe("ShouldColorize" , (), |ctx| { |
171 | ctx.specify("::normalize_env" , |ctx| { |
172 | ctx.it("should return None if error" , |_| { |
173 | assert_eq!( |
174 | None, |
175 | ShouldColorize::normalize_env(Err(env::VarError::NotPresent)) |
176 | ); |
177 | assert_eq!( |
178 | None, |
179 | ShouldColorize::normalize_env(Err(env::VarError::NotUnicode("" .into()))) |
180 | ); |
181 | }); |
182 | |
183 | ctx.it("should return Some(true) if != 0" , |_| { |
184 | Some(true) == ShouldColorize::normalize_env(Ok(String::from("1" ))) |
185 | }); |
186 | |
187 | ctx.it("should return Some(false) if == 0" , |_| { |
188 | Some(false) == ShouldColorize::normalize_env(Ok(String::from("0" ))) |
189 | }); |
190 | }); |
191 | |
192 | ctx.specify("::resolve_clicolor_force" , |ctx| { |
193 | ctx.it( |
194 | "should return None if NO_COLOR is not set and CLICOLOR_FORCE is not set or set to 0" , |
195 | |_| { |
196 | assert_eq!( |
197 | None, |
198 | ShouldColorize::resolve_clicolor_force( |
199 | Err(env::VarError::NotPresent), |
200 | Err(env::VarError::NotPresent) |
201 | ) |
202 | ); |
203 | assert_eq!( |
204 | None, |
205 | ShouldColorize::resolve_clicolor_force( |
206 | Err(env::VarError::NotPresent), |
207 | Ok(String::from("0" )), |
208 | ) |
209 | ); |
210 | }, |
211 | ); |
212 | |
213 | ctx.it( |
214 | "should return Some(false) if NO_COLOR is set and CLICOLOR_FORCE is not enabled" , |
215 | |_| { |
216 | assert_eq!( |
217 | Some(false), |
218 | ShouldColorize::resolve_clicolor_force( |
219 | Ok(String::from("0" )), |
220 | Err(env::VarError::NotPresent) |
221 | ) |
222 | ); |
223 | assert_eq!( |
224 | Some(false), |
225 | ShouldColorize::resolve_clicolor_force( |
226 | Ok(String::from("1" )), |
227 | Err(env::VarError::NotPresent) |
228 | ) |
229 | ); |
230 | assert_eq!( |
231 | Some(false), |
232 | ShouldColorize::resolve_clicolor_force( |
233 | Ok(String::from("1" )), |
234 | Ok(String::from("0" )), |
235 | ) |
236 | ); |
237 | }, |
238 | ); |
239 | |
240 | ctx.it( |
241 | "should prioritize CLICOLOR_FORCE over NO_COLOR if CLICOLOR_FORCE is set to non-zero value" , |
242 | |_| { |
243 | assert_eq!( |
244 | Some(true), |
245 | ShouldColorize::resolve_clicolor_force( |
246 | Ok(String::from("1" )), |
247 | Ok(String::from("1" )), |
248 | ) |
249 | ); |
250 | assert_eq!( |
251 | Some(false), |
252 | ShouldColorize::resolve_clicolor_force( |
253 | Ok(String::from("1" )), |
254 | Ok(String::from("0" )), |
255 | ) |
256 | ); |
257 | assert_eq!( |
258 | Some(true), |
259 | ShouldColorize::resolve_clicolor_force( |
260 | Err(env::VarError::NotPresent), |
261 | Ok(String::from("1" )), |
262 | ) |
263 | ); |
264 | }, |
265 | ); |
266 | }); |
267 | |
268 | ctx.specify("constructors" , |ctx| { |
269 | ctx.it("should have a default constructor" , |_| { |
270 | ShouldColorize::default(); |
271 | }); |
272 | |
273 | ctx.it("should have an environment constructor" , |_| { |
274 | ShouldColorize::from_env(); |
275 | }); |
276 | }); |
277 | |
278 | ctx.specify("when only changing clicolors" , |ctx| { |
279 | ctx.it("clicolor == false means no colors" , |_| { |
280 | let colorize_control = ShouldColorize { |
281 | clicolor: false, |
282 | ..ShouldColorize::default() |
283 | }; |
284 | !colorize_control.should_colorize() |
285 | }); |
286 | |
287 | ctx.it("clicolor == true means colors !" , |_| { |
288 | let colorize_control = ShouldColorize { |
289 | clicolor: true, |
290 | ..ShouldColorize::default() |
291 | }; |
292 | colorize_control.should_colorize() |
293 | }); |
294 | |
295 | ctx.it("unset clicolors implies true" , |_| { |
296 | ShouldColorize::default().should_colorize() |
297 | }); |
298 | }); |
299 | |
300 | ctx.specify("when using clicolor_force" , |ctx| { |
301 | ctx.it( |
302 | "clicolor_force should force to true no matter clicolor" , |
303 | |_| { |
304 | let colorize_control = ShouldColorize { |
305 | clicolor: false, |
306 | clicolor_force: Some(true), |
307 | ..ShouldColorize::default() |
308 | }; |
309 | |
310 | colorize_control.should_colorize() |
311 | }, |
312 | ); |
313 | |
314 | ctx.it( |
315 | "clicolor_force should force to false no matter clicolor" , |
316 | |_| { |
317 | let colorize_control = ShouldColorize { |
318 | clicolor: true, |
319 | clicolor_force: Some(false), |
320 | ..ShouldColorize::default() |
321 | }; |
322 | |
323 | !colorize_control.should_colorize() |
324 | }, |
325 | ); |
326 | }); |
327 | |
328 | ctx.specify("using a manual override" , |ctx| { |
329 | ctx.it("shoud colorize if manual_override is true, but clicolor is false and clicolor_force also false" , |_| { |
330 | let colorize_control = ShouldColorize { |
331 | clicolor: false, |
332 | clicolor_force: None, |
333 | has_manual_override: AtomicBool::new(true), |
334 | manual_override: AtomicBool::new(true), |
335 | }; |
336 | |
337 | colorize_control.should_colorize(); |
338 | }); |
339 | |
340 | ctx.it("should not colorize if manual_override is false, but clicolor is true or clicolor_force is true" , |_| { |
341 | let colorize_control = ShouldColorize { |
342 | clicolor: true, |
343 | clicolor_force: Some(true), |
344 | has_manual_override: AtomicBool::new(true), |
345 | manual_override: AtomicBool::new(false), |
346 | }; |
347 | |
348 | !colorize_control.should_colorize() |
349 | }); |
350 | }); |
351 | |
352 | ctx.specify("::set_override" , |ctx| { |
353 | ctx.it("should exists" , |_| { |
354 | let colorize_control = ShouldColorize::default(); |
355 | colorize_control.set_override(true); |
356 | }); |
357 | |
358 | ctx.it("set the manual_override property" , |_| { |
359 | let colorize_control = ShouldColorize::default(); |
360 | colorize_control.set_override(true); |
361 | { |
362 | assert!(colorize_control.has_manual_override.load(Ordering::Relaxed)); |
363 | let val = colorize_control.manual_override.load(Ordering::Relaxed); |
364 | assert!(val); |
365 | } |
366 | colorize_control.set_override(false); |
367 | { |
368 | assert!(colorize_control.has_manual_override.load(Ordering::Relaxed)); |
369 | let val = colorize_control.manual_override.load(Ordering::Relaxed); |
370 | assert!(!val); |
371 | } |
372 | }); |
373 | }); |
374 | |
375 | ctx.specify("::unset_override" , |ctx| { |
376 | ctx.it("should exists" , |_| { |
377 | let colorize_control = ShouldColorize::default(); |
378 | colorize_control.unset_override(); |
379 | }); |
380 | |
381 | ctx.it("unset the manual_override property" , |_| { |
382 | let colorize_control = ShouldColorize::default(); |
383 | colorize_control.set_override(true); |
384 | colorize_control.unset_override(); |
385 | assert!(!colorize_control.has_manual_override.load(Ordering::Relaxed)); |
386 | }); |
387 | }); |
388 | })); |
389 | } |
390 | } |
391 | |