owo_colors/
overrides.rs

1use core::sync::atomic::{AtomicU8, Ordering};
2
3/// Set an override value for whether or not colors are supported using
4/// [`set_override`] while executing the closure provided.
5///
6/// Once the function has executed the value will be reset to the previous set
7/// (or unset) override.
8///
9/// This is especially useful in use-cases where one would like to temporarily
10/// override the supported color set, without impacting previous configurations.
11///
12/// ```
13/// # use owo_colors::{Stream, OwoColorize, set_override, unset_override, with_override};
14/// # use owo_colors::colors::Black;
15/// #
16/// set_override(false);
17/// assert_eq!("example".if_supports_color(Stream::Stdout, |value| value.bg::<Black>()).to_string(), "example");
18///
19/// with_override(true, || {
20///     assert_eq!("example".if_supports_color(Stream::Stdout, |value| value.bg::<Black>()).to_string(), "\x1b[40mexample\x1b[49m");
21/// });
22///
23/// assert_eq!("example".if_supports_color(Stream::Stdout, |value| value.bg::<Black>()).to_string(), "example");
24/// # unset_override() // make sure that other doc tests are not impacted
25/// ```
26#[cfg(feature = "supports-colors")]
27pub fn with_override<T, F: FnOnce() -> T>(enabled: bool, f: F) -> T {
28    let previous = OVERRIDE.inner();
29    OVERRIDE.set_force(enabled);
30
31    // Use a scope guard to ensure that if `f` panics, the override is still
32    // caught.
33    let _guard = ResetOverrideGuard { previous };
34
35    f()
36}
37
38struct ResetOverrideGuard {
39    previous: u8,
40}
41
42impl Drop for ResetOverrideGuard {
43    fn drop(&mut self) {
44        OVERRIDE.set_unchecked(self.previous);
45    }
46}
47
48/// Set an override value for whether or not colors are supported.
49///
50/// If `true` is passed,
51/// [`if_supports_color`](crate::OwoColorize::if_supports_color) will always act
52/// as if colors are supported.
53///
54/// If `false` is passed,
55/// [`if_supports_color`](crate::OwoColorize::if_supports_color) will always act
56/// as if colors are **not** supported.
57///
58/// This behavior can be disabled using [`unset_override`], allowing
59/// `owo-colors` to return to inferring if colors are supported.
60#[cfg(feature = "supports-colors")]
61pub fn set_override(enabled: bool) {
62    OVERRIDE.set_force(enabled);
63}
64
65/// Remove any override value for whether or not colors are supported. This
66/// means [`if_supports_color`](crate::OwoColorize::if_supports_color) will
67/// resume checking if the given terminal output ([`Stream`](crate::Stream))
68/// supports colors.
69///
70/// This override can be set using [`set_override`].
71#[cfg(feature = "supports-colors")]
72pub fn unset_override() {
73    OVERRIDE.unset();
74}
75
76pub(crate) static OVERRIDE: Override = Override::none();
77
78pub(crate) struct Override(AtomicU8);
79
80const FORCE_MASK: u8 = 0b10;
81const FORCE_ENABLE: u8 = 0b11;
82const FORCE_DISABLE: u8 = 0b10;
83const NO_FORCE: u8 = 0b00;
84
85impl Override {
86    const fn none() -> Self {
87        Self(AtomicU8::new(NO_FORCE))
88    }
89
90    fn inner(&self) -> u8 {
91        self.0.load(Ordering::SeqCst)
92    }
93
94    pub(crate) fn is_force_enabled_or_disabled(&self) -> (bool, bool) {
95        let inner = self.inner();
96
97        (inner == FORCE_ENABLE, inner == FORCE_DISABLE)
98    }
99
100    fn set_force(&self, enable: bool) {
101        self.set_unchecked(FORCE_MASK | (enable as u8));
102    }
103
104    fn unset(&self) {
105        self.set_unchecked(0);
106    }
107
108    fn set_unchecked(&self, value: u8) {
109        self.0.store(value, Ordering::SeqCst);
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn with_override_on_panic() {
119        set_override(false);
120
121        std::panic::catch_unwind(|| {
122            with_override(true, || {
123                assert_eq!(OVERRIDE.inner(), FORCE_ENABLE);
124                panic!("test");
125            });
126        })
127        .expect_err("test should panic");
128
129        assert_eq!(
130            OVERRIDE.inner(),
131            FORCE_DISABLE,
132            "override should have been reset"
133        );
134    }
135}