/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 | | } |