Skip to main content

afetch_colored/
control.rs

1//! A couple of functions to enable and disable coloring.
2
3use std::default::Default;
4use std::env;
5use std::io::{self, IsTerminal};
6use 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 'bright cyan' 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)]
30pub 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.
61pub 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
71pub 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
77pub fn unset_override() {
78    SHOULD_COLORIZE.unset_override()
79}
80
81lazy_static! {
82/// The persistent [`ShouldColorize`].
83    pub static ref SHOULD_COLORIZE: ShouldColorize = ShouldColorize::from_env();
84}
85
86impl 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
97impl 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)]
163mod specs {
164    use super::*;
165    use rspec;
166    use rspec::context::*;
167    use std::env;
168
169    #[test]
170    fn clicolor_behavior() {
171        use std::io;
172
173        let stdout = &mut io::stdout();
174        let mut formatter = rspec::formatter::Simple::new(stdout);
175        let mut runner = describe("ShouldColorize", |ctx| {
176            ctx.describe("::normalize_env", |ctx| {
177                ctx.it("should return None if error", || {
178                    assert_eq!(
179                        None,
180                        ShouldColorize::normalize_env(Err(env::VarError::NotPresent))
181                    );
182                    assert_eq!(
183                        None,
184                        ShouldColorize::normalize_env(Err(env::VarError::NotUnicode("".into())))
185                    )
186                });
187
188                ctx.it("should return Some(true) if != 0", || {
189                    Some(true) == ShouldColorize::normalize_env(Ok(String::from("1")))
190                });
191
192                ctx.it("should return Some(false) if == 0", || {
193                    Some(false) == ShouldColorize::normalize_env(Ok(String::from("0")))
194                });
195            });
196
197            ctx.describe("::resolve_clicolor_force", |ctx| {
198                ctx.it(
199                    "should return None if NO_COLOR is not set and CLICOLOR_FORCE is not set or set to 0",
200                    || {
201                        assert_eq!(
202                            None,
203                            ShouldColorize::resolve_clicolor_force(
204                                Err(env::VarError::NotPresent),
205                                Err(env::VarError::NotPresent)
206                            )
207                        );
208                        assert_eq!(
209                            None,
210                            ShouldColorize::resolve_clicolor_force(
211                                Err(env::VarError::NotPresent),
212                                Ok(String::from("0")),
213                            )
214                        );
215                    },
216                );
217
218                ctx.it(
219                    "should return Some(false) if NO_COLOR is set and CLICOLOR_FORCE is not enabled",
220                    || {
221                        assert_eq!(
222                            Some(false),
223                            ShouldColorize::resolve_clicolor_force(
224                                Ok(String::from("0")),
225                                Err(env::VarError::NotPresent)
226                            )
227                        );
228                        assert_eq!(
229                            Some(false),
230                            ShouldColorize::resolve_clicolor_force(
231                                Ok(String::from("1")),
232                                Err(env::VarError::NotPresent)
233                            )
234                        );
235                        assert_eq!(
236                            Some(false),
237                            ShouldColorize::resolve_clicolor_force(
238                                Ok(String::from("1")),
239                                Ok(String::from("0")),
240                            )
241                        );
242                    },
243                );
244
245                ctx.it(
246                    "should prioritize CLICOLOR_FORCE over NO_COLOR if CLICOLOR_FORCE is set to non-zero value",
247                    || {
248                        assert_eq!(
249                            Some(true),
250                            ShouldColorize::resolve_clicolor_force(
251                                Ok(String::from("1")),
252                                Ok(String::from("1")),
253                            )
254                        );
255                        assert_eq!(
256                            Some(false),
257                            ShouldColorize::resolve_clicolor_force(
258                                Ok(String::from("1")),
259                                Ok(String::from("0")),
260                            )
261                        );
262                        assert_eq!(
263                            Some(true),
264                            ShouldColorize::resolve_clicolor_force(
265                                Err(env::VarError::NotPresent),
266                                Ok(String::from("1")),
267                            )
268                        );
269                    },
270                );
271            });
272
273            ctx.describe("constructors", |ctx| {
274                ctx.it("should have a default constructor", || {
275                    ShouldColorize::default();
276                });
277
278                ctx.it("should have an environment constructor", || {
279                    ShouldColorize::from_env();
280                });
281            });
282
283            ctx.describe("when only changing clicolors", |ctx| {
284                ctx.it("clicolor == false means no colors", || {
285                    let colorize_control = ShouldColorize {
286                        clicolor: false,
287                        ..ShouldColorize::default()
288                    };
289                    false == colorize_control.should_colorize()
290                });
291
292                ctx.it("clicolor == true means colors !", || {
293                    let colorize_control = ShouldColorize {
294                        clicolor: true,
295                        ..ShouldColorize::default()
296                    };
297                    true == colorize_control.should_colorize()
298                });
299
300                ctx.it("unset clicolors implies true", || {
301                    true == ShouldColorize::default().should_colorize()
302                });
303            });
304
305            ctx.describe("when using clicolor_force", |ctx| {
306                ctx.it(
307                    "clicolor_force should force to true no matter clicolor",
308                    || {
309                        let colorize_control = ShouldColorize {
310                            clicolor: false,
311                            clicolor_force: Some(true),
312                            ..ShouldColorize::default()
313                        };
314
315                        true == colorize_control.should_colorize()
316                    },
317                );
318
319                ctx.it(
320                    "clicolor_force should force to false no matter clicolor",
321                    || {
322                        let colorize_control = ShouldColorize {
323                            clicolor: true,
324                            clicolor_force: Some(false),
325                            ..ShouldColorize::default()
326                        };
327
328                        false == colorize_control.should_colorize()
329                    },
330                );
331            });
332
333            ctx.describe("using a manual override", |ctx| {
334                ctx.it("shoud colorize if manual_override is true, but clicolor is false and clicolor_force also false", || {
335                    let colorize_control = ShouldColorize {
336                        clicolor: false,
337                        clicolor_force: None,
338                        has_manual_override: AtomicBool::new(true),
339                        manual_override: AtomicBool::new(true),
340                        .. ShouldColorize::default()
341                    };
342
343                    true == colorize_control.should_colorize()
344                });
345
346                ctx.it("should not colorize if manual_override is false, but clicolor is true or clicolor_force is true", || {
347                    let colorize_control = ShouldColorize {
348                        clicolor: true,
349                        clicolor_force: Some(true),
350                        has_manual_override: AtomicBool::new(true),
351                        manual_override: AtomicBool::new(false),
352                        .. ShouldColorize::default()
353                    };
354
355                    false == colorize_control.should_colorize()
356                })
357            });
358
359            ctx.describe("::set_override", |ctx| {
360                ctx.it("should exists", || {
361                    let colorize_control = ShouldColorize::default();
362                    colorize_control.set_override(true);
363                });
364
365                ctx.it("set the manual_override property", || {
366                    let colorize_control = ShouldColorize::default();
367                    colorize_control.set_override(true);
368                    {
369                        assert_eq!(
370                            true,
371                            colorize_control.has_manual_override.load(Ordering::Relaxed)
372                        );
373                        let val = colorize_control.manual_override.load(Ordering::Relaxed);
374                        assert_eq!(true, val);
375                    }
376                    colorize_control.set_override(false);
377                    {
378                        assert_eq!(
379                            true,
380                            colorize_control.has_manual_override.load(Ordering::Relaxed)
381                        );
382                        let val = colorize_control.manual_override.load(Ordering::Relaxed);
383                        assert_eq!(false, val);
384                    }
385                });
386            });
387
388            ctx.describe("::unset_override", |ctx| {
389                ctx.it("should exists", || {
390                    let colorize_control = ShouldColorize::default();
391                    colorize_control.unset_override();
392                });
393
394                ctx.it("unset the manual_override property", || {
395                    let colorize_control = ShouldColorize::default();
396                    colorize_control.set_override(true);
397                    colorize_control.unset_override();
398                    assert_eq!(
399                        false,
400                        colorize_control.has_manual_override.load(Ordering::Relaxed)
401                    );
402                });
403            });
404        });
405        runner.add_event_handler(&mut formatter);
406        runner.run().unwrap();
407    }
408}