Skip to main content

colorchoice/
lib.rs

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