Coverage Report

Created: 2025-06-16 06:50

/rust/registry/src/index.crates.io-6f17d22bba15001f/yansi-1.0.1/src/condition.rs
Line
Count
Source (jump to first uncovered line)
1
use core::fmt;
2
use core::sync::atomic::AtomicU8;
3
use core::sync::atomic::{AtomicPtr, Ordering};
4
5
/// A function that decides whether styling should be applied.
6
///
7
/// A styling `Condition` can be specified globally via
8
/// [`yansi::whenever()`](crate::whenever()) or locally to a specific style via
9
/// the [`whenever()`](crate::Style::whenever()) builder method. Any time a
10
/// [`Painted`](crate::Painted) value is formatted, both the local and global
11
/// conditions are checked, and only when both evaluate to `true` is styling
12
/// actually applied.
13
///
14
/// A `Condition` is nothing more than a function that returns a `bool`. The
15
/// function is called each and every time a `Painted` is formatted, and so it
16
/// is expected to be fast. All of the built-in conditions (except for their
17
/// "live" variants) cache their first evaluation as a result: the
18
/// [`Condition::cached()`] constructor can do the same for your conditions.
19
///
20
/// # Built-In Conditions
21
///
22
/// `yansi` comes with built-in conditions for common scenarios that can be
23
/// enabled via crate features:
24
///
25
/// | feature(s)                   | condition                       | implication            |
26
/// |------------------------------|---------------------------------|------------------------|
27
/// | `detect-tty`                 | [TTY Detectors]                 | `std`, [`is-terminal`] |
28
/// | `detect-env`                 | [Environment Variable Checkers] | `std`                  |
29
/// | [`detect-tty`, `detect-env`] | All Above, [Combo Detectors]    | `std`, [`is-terminal`] |
30
///
31
/// [`is-terminal`]: https://docs.rs/is-terminal
32
///
33
/// For example, to enable the TTY detectors, enable the `detect-tty` feature:
34
///
35
/// ```toml
36
/// yansi = { version = "...", features = ["detect-tty"] }
37
/// ```
38
///
39
/// To enable the TTY detectors, env-var checkers, and combo detectors, enable
40
/// `detect-tty` _and_ `detect-env`:
41
///
42
/// ```toml
43
/// yansi = { version = "...", features = ["detect-tty", "detect-env"] }
44
/// ```
45
///
46
/// ```rust
47
/// # #[cfg(all(feature = "detect-tty", feature = "detect-env"))] {
48
/// use yansi::Condition;
49
///
50
/// yansi::whenever(Condition::TTY_AND_COLOR);
51
/// # }
52
/// ```
53
///
54
/// [TTY detectors]: Condition#impl-Condition-1
55
/// [Environment Variable Checkers]: Condition#impl-Condition-2
56
/// [Combo Detectors]: Condition#impl-Condition-3
57
///
58
/// # Custom Conditions
59
///
60
/// Custom, arbitrary conditions can be created with [`Condition::from()`] or
61
/// [`Condition::cached()`].
62
///
63
/// ```rust
64
/// # #[cfg(all(feature = "detect-tty", feature = "detect-env"))] {
65
/// use yansi::{Condition, Style, Color::*};
66
///
67
/// // Combine two conditions (`stderr` is a TTY, `CLICOLOR` is set) into one.
68
/// static STDERR_COLOR: Condition = Condition::from(||
69
///     Condition::stderr_is_tty() && Condition::clicolor()
70
/// );
71
///
72
/// static DEBUG: Style = Yellow.bold().on_primary().invert().whenever(STDERR_COLOR);
73
/// # }
74
/// ```
75
#[repr(transparent)]
76
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77
pub struct Condition(
78
    /// The function that gets called to check the condition.
79
    pub fn() -> bool
80
);
81
82
#[repr(transparent)]
83
pub struct AtomicCondition(AtomicPtr<()>);
84
85
#[allow(unused)]
86
#[repr(transparent)]
87
pub struct CachedBool(AtomicU8);
88
89
impl Condition {
90
    /// A condition that evaluates to `true` if the OS supports coloring.
91
    ///
92
    /// Uses [`Condition::os_support()`]. On Windows, this condition tries to
93
    /// enable coloring support on the first call and caches the result for
94
    /// subsequent calls. Outside of Windows, this always evaluates to `true`.
95
    pub const DEFAULT: Condition = Condition(Condition::os_support);
96
97
    /// A condition that always evaluates to `true`.
98
    pub const ALWAYS: Condition = Condition(Condition::always);
99
100
    /// A condition that always evaluated to `false`.
101
    pub const NEVER: Condition = Condition(Condition::never);
102
103
    /// Creates a dynamically checked condition from a function `f`.
104
    ///
105
    /// The function `f` is called anytime the condition is checked, including
106
    /// every time a style with the condition is used.
107
    ///
108
    /// # Example
109
    ///
110
    /// ```rust,no_run
111
    /// use yansi::Condition;
112
    ///
113
    /// fn some_function() -> bool {
114
    ///     /* checking arbitrary conditions */
115
    ///     todo!()
116
    /// }
117
    ///
118
    /// // Create a custom static condition from a function.
119
    /// static MY_CONDITION: Condition = Condition::from(some_function);
120
    ///
121
    /// // Create a condition on the stack from a function.
122
    /// let my_condition = Condition::from(some_function);
123
    ///
124
    /// // Create a static condition from a closure that becomes a `fn`.
125
    /// static MY_CONDITION_2: Condition = Condition::from(|| false);
126
    ///
127
    /// // Create a condition on the stack from a closure that becomes a `fn`.
128
    /// let my_condition = Condition::from(|| some_function());
129
    /// ```
130
0
    pub const fn from(f: fn() -> bool) -> Self {
131
0
        Condition(f)
132
0
    }
133
134
    /// Creates a condition that is [`ALWAYS`](Self::ALWAYS) when `value` is
135
    /// `true` and [`NEVER`](Self::NEVER) otherwise.
136
    ///
137
    /// # Example
138
    ///
139
    /// ```rust,no_run
140
    /// use yansi::Condition;
141
    ///
142
    /// fn some_function() -> bool {
143
    ///     /* checking arbitrary conditions */
144
    ///     todo!()
145
    /// }
146
    ///
147
    /// // Cache the result of `some_function()` so it doesn't get called each
148
    /// // time the condition needs to be checked.
149
    /// let my_condition = Condition::cached(some_function());
150
    /// ```
151
0
    pub const fn cached(value: bool) -> Self {
152
0
        match value {
153
0
            true => Condition::ALWAYS,
154
0
            false => Condition::NEVER,
155
        }
156
0
    }
157
158
    /// The backing function for [`Condition::ALWAYS`]. Returns `true` always.
159
0
    pub const fn always() -> bool { true }
160
161
    /// The backing function for [`Condition::NEVER`]. Returns `false` always.
162
0
    pub const fn never() -> bool { false }
163
164
    /// The backing function for [`Condition::DEFAULT`].
165
    ///
166
    /// Returns `true` if the current OS supports ANSI escape sequences for
167
    /// coloring. Outside of Windows, this always returns `true`. On Windows,
168
    /// the first call to this function attempts to enable support and returns
169
    /// whether it was successful every time thereafter.
170
0
    pub fn os_support() -> bool {
171
0
        crate::windows::cache_enable()
172
0
    }
173
}
174
175
impl Default for Condition {
176
0
    fn default() -> Self {
177
0
        Condition::DEFAULT
178
0
    }
179
}
180
181
impl core::ops::Deref for Condition {
182
    type Target = fn() -> bool;
183
184
0
    fn deref(&self) -> &Self::Target {
185
0
        &self.0
186
0
    }
187
}
188
189
impl AtomicCondition {
190
    pub const DEFAULT: AtomicCondition = AtomicCondition::from(Condition::DEFAULT);
191
192
0
    pub const fn from(value: Condition) -> Self {
193
0
        AtomicCondition(AtomicPtr::new(value.0 as *mut ()))
194
0
    }
195
196
0
    pub fn store(&self, cond: Condition) {
197
0
        self.0.store(cond.0 as *mut (), Ordering::Release)
198
0
    }
199
200
0
    pub fn read(&self) -> bool {
201
0
        let condition = unsafe {
202
0
            Condition(core::mem::transmute(self.0.load(Ordering::Acquire)))
203
0
        };
204
0
205
0
        condition()
206
0
    }
207
}
208
209
#[allow(unused)]
210
impl CachedBool {
211
    const TRUE: u8 = 1;
212
    const UNINIT: u8 = 2;
213
    const INITING: u8 = 3;
214
215
0
    pub const fn new() -> Self {
216
0
        CachedBool(AtomicU8::new(Self::UNINIT))
217
0
    }
218
219
0
    pub fn get_or_init(&self, f: impl FnOnce() -> bool) -> bool {
220
        use core::sync::atomic::Ordering::*;
221
222
0
        match self.0.compare_exchange(Self::UNINIT, Self::INITING, AcqRel, Relaxed) {
223
            Ok(_) => {
224
0
                let new_value = f();
225
0
                self.0.store(new_value as u8 /* false = 0, true = 1 */, Release);
226
0
                new_value
227
            }
228
            Err(Self::INITING) => {
229
                let mut value;
230
0
                while { value = self.0.load(Acquire); value } == Self::INITING {
231
0
                    #[cfg(feature = "std")]
232
0
                    std::thread::yield_now();
233
0
                }
234
235
0
                value == Self::TRUE
236
            },
237
0
            Err(value) => value == Self::TRUE,
238
        }
239
0
    }
240
}
241
242
impl fmt::Debug for Condition {
243
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244
0
        if *self == Condition::DEFAULT {
245
0
            f.write_str("Condition::DEFAULT")
246
0
        } else if *self == Condition::ALWAYS {
247
0
            f.write_str("Condition::ALWAYS")
248
0
        } else if *self == Condition::NEVER {
249
0
            f.write_str("Condition::NEVER")
250
        } else {
251
0
            f.debug_tuple("Condition").field(&self.0).finish()
252
        }
253
0
    }
254
}
255
256
macro_rules! conditions {
257
    ($feat:meta $($f:expr, $CACHED:ident: $cached:ident, $LIVE:ident: $live:ident),* $(,)?) => (
258
        #[cfg($feat)]
259
        #[cfg_attr(feature = "_nightly", doc(cfg($feat)))]
260
        /// Feature dependent conditions.
261
        ///
262
        /// Available when compiled with
263
        #[doc = concat!('`', stringify!($feat), "`.")]
264
        impl Condition {
265
            $(
266
                /// Evaluates to `true` if
267
                #[doc = concat!('`', stringify!($f), "`.")]
268
                ///
269
                /// The result of the first check is cached for subsequent
270
                /// checks. Internally uses
271
                #[doc = concat!("[`", stringify!($cached), "`](Condition::", stringify!($cached), ").")]
272
                pub const $CACHED: Condition = Condition(Condition::$cached);
273
            )*
274
275
            $(
276
                /// Evaluates to `true` if
277
                #[doc = concat!('`', stringify!($f), "`.")]
278
                ///
279
                /// A call is dispatched each time the condition is checked.
280
                /// This is expensive, so prefer to use
281
                #[doc = concat!("[`", stringify!($CACHED), "`](Condition::", stringify!($CACHED), ")")]
282
                /// instead.
283
                ///
284
                /// Internally uses
285
                #[doc = concat!("[`", stringify!($live), "`](Condition::", stringify!($live), ").")]
286
                pub const $LIVE: Condition = Condition(Condition::$live);
287
            )*
288
289
            $(
290
                /// Returns `true` if
291
                #[doc = concat!('`', stringify!($f), "`.")]
292
                ///
293
                /// The result of the first check is cached for subsequent
294
                /// checks. This is the backing function for
295
                #[doc = concat!("[`", stringify!($CACHED), "`](Condition::", stringify!($CACHED), ").")]
296
                pub fn $cached() -> bool {
297
                    static IS_TTY: CachedBool = CachedBool::new();
298
                    IS_TTY.get_or_init(Condition::$live)
299
                }
300
            )*
301
302
            $(
303
                /// Returns `true` if
304
                #[doc = concat!('`', stringify!($f), "`.")]
305
                ///
306
                /// This is the backing function for
307
                #[doc = concat!("[`", stringify!($LIVE), "`](Condition::", stringify!($LIVE), ").")]
308
                pub fn $live() -> bool {
309
                    $f
310
                }
311
            )*
312
        }
313
    )
314
}
315
316
#[cfg(feature = "detect-tty")]
317
use is_terminal::is_terminal as is_tty;
318
319
conditions! { feature = "detect-tty"
320
    is_tty(&std::io::stdout()),
321
        STDOUT_IS_TTY: stdout_is_tty,
322
        STDOUT_IS_TTY_LIVE: stdout_is_tty_live,
323
324
    is_tty(&std::io::stderr()),
325
        STDERR_IS_TTY: stderr_is_tty,
326
        STDERR_IS_TTY_LIVE: stderr_is_tty_live,
327
328
    is_tty(&std::io::stdin()),
329
        STDIN_IS_TTY: stdin_is_tty,
330
        STDIN_IS_TTY_LIVE: stdin_is_tty_live,
331
332
    is_tty(&std::io::stdout()) && is_tty(&std::io::stderr()),
333
        STDOUTERR_ARE_TTY: stdouterr_are_tty,
334
        STDOUTERR_ARE_TTY_LIVE: stdouterr_are_tty_live,
335
}
336
337
#[cfg(feature = "detect-env")]
338
pub fn env_set_or(name: &str, default: bool) -> bool {
339
    std::env::var_os(name).map_or(default, |v| v != "0")
340
}
341
342
conditions! { feature = "detect-env"
343
    env_set_or("CLICOLOR_FORCE", false) || env_set_or("CLICOLOR", true),
344
        CLICOLOR: clicolor,
345
        CLICOLOR_LIVE: clicolor_live,
346
347
    !env_set_or("NO_COLOR", false),
348
        YES_COLOR: no_color,
349
        YES_COLOR_LIVE: no_color_live,
350
}
351
352
conditions! { all(feature = "detect-env", feature = "detect-tty")
353
    Condition::stdouterr_are_tty() && Condition::clicolor() && Condition::no_color(),
354
        TTY_AND_COLOR: tty_and_color,
355
        TTY_AND_COLOR_LIVE: tty_and_color_live,
356
}