1 | //! Global override of color control |
2 | |
3 | #![cfg_attr (not(test), no_std)] |
4 | #![cfg_attr (docsrs, feature(doc_auto_cfg))] |
5 | #![warn (missing_docs)] |
6 | #![warn (clippy::print_stderr)] |
7 | #![warn (clippy::print_stdout)] |
8 | |
9 | use core::sync::atomic::{AtomicUsize, Ordering}; |
10 | |
11 | /// Selection for overriding color output |
12 | #[allow (clippy::exhaustive_enums)] |
13 | #[derive (Copy, Clone, Debug, PartialEq, Eq)] |
14 | pub enum ColorChoice { |
15 | /// Use colors if the output device appears to support them |
16 | Auto, |
17 | /// Like `Always`, except it never tries to use anything other than emitting ANSI |
18 | /// color codes. |
19 | AlwaysAnsi, |
20 | /// Try very hard to emit colors. |
21 | /// |
22 | /// This includes emitting ANSI colors on Windows if the console API is unavailable. |
23 | Always, |
24 | /// Never emit colors. |
25 | Never, |
26 | } |
27 | |
28 | impl ColorChoice { |
29 | /// Get the current [`ColorChoice`] state |
30 | pub fn global() -> Self { |
31 | USER.get() |
32 | } |
33 | |
34 | /// Override the detected [`ColorChoice`] |
35 | pub fn write_global(self) { |
36 | USER.set(self); |
37 | } |
38 | } |
39 | |
40 | impl Default for ColorChoice { |
41 | fn default() -> Self { |
42 | Self::Auto |
43 | } |
44 | } |
45 | |
46 | static USER: AtomicChoice = AtomicChoice::new(); |
47 | |
48 | #[derive (Debug)] |
49 | pub(crate) struct AtomicChoice(AtomicUsize); |
50 | |
51 | impl AtomicChoice { |
52 | pub(crate) const fn new() -> Self { |
53 | Self(AtomicUsize::new(Self::from_choice(ColorChoice::Auto))) |
54 | } |
55 | |
56 | pub(crate) fn get(&self) -> ColorChoice { |
57 | let choice = self.0.load(Ordering::SeqCst); |
58 | Self::to_choice(choice).expect("Only `ColorChoice` values can be `set`" ) |
59 | } |
60 | |
61 | pub(crate) fn set(&self, choice: ColorChoice) { |
62 | let choice = Self::from_choice(choice); |
63 | self.0.store(choice, Ordering::SeqCst); |
64 | } |
65 | |
66 | const fn from_choice(choice: ColorChoice) -> usize { |
67 | match choice { |
68 | ColorChoice::Auto => 0, |
69 | ColorChoice::AlwaysAnsi => 1, |
70 | ColorChoice::Always => 2, |
71 | ColorChoice::Never => 3, |
72 | } |
73 | } |
74 | |
75 | const fn to_choice(choice: usize) -> Option<ColorChoice> { |
76 | match choice { |
77 | 0 => Some(ColorChoice::Auto), |
78 | 1 => Some(ColorChoice::AlwaysAnsi), |
79 | 2 => Some(ColorChoice::Always), |
80 | 3 => Some(ColorChoice::Never), |
81 | _ => None, |
82 | } |
83 | } |
84 | } |
85 | |
86 | impl Default for AtomicChoice { |
87 | fn default() -> Self { |
88 | Self::new() |
89 | } |
90 | } |
91 | |
92 | #[cfg (test)] |
93 | mod test { |
94 | use super::*; |
95 | |
96 | #[test ] |
97 | fn choice_serialization() { |
98 | let expected = vec![ |
99 | ColorChoice::Auto, |
100 | ColorChoice::AlwaysAnsi, |
101 | ColorChoice::Always, |
102 | ColorChoice::Never, |
103 | ]; |
104 | let values: Vec<_> = expected |
105 | .iter() |
106 | .cloned() |
107 | .map(AtomicChoice::from_choice) |
108 | .collect(); |
109 | let actual: Vec<_> = values |
110 | .iter() |
111 | .cloned() |
112 | .filter_map(AtomicChoice::to_choice) |
113 | .collect(); |
114 | assert_eq!(expected, actual); |
115 | } |
116 | } |
117 | |