should_color/lib.rs
1/*!
2Determine whether output should use colors or not.
3
4The resulting color choice is determined by taking into account,
5in order of priority from higher to lower, the following settings:
6
7- [`CLICOLOR_FORCE`](#clicolor_force) environment variable (requires the <span class="stab portability"><code>clicolor_force</code></span> feature),
8- explicit user preference (for instance command line arguments),
9- [`CLICOLOR`](#clicolor) environment variable (requires the <span class="stab portability"><code>clicolor</code></span> feature),
10- [`NO_COLOR`](#no_color) environment variable (requires the <span class="stab portability"><code>no_color</code></span> feature),
11- application default choice.
12
13If the final choice is `ColorChoice::Auto` and the feature <span class="stab portability"><code>stream</code></span> is enabled,
14the choice can be refined using [`ColorChoice::for_stream`] which takes into account the output stream.
15
16The specification of `CLICOLOR`, `CLICOLOR_FORCE`, and `NO_COLOR` is inspired by:
17
18- <https://bixense.com/clicolors/>,
19- <https://no-color.org>,
20
21with the exception that variables which are set to the empty string `""`
22are treated as if they were unset.
23The reason is that it is common to override environment variables by executing programs as
24`VAR= cmd args...` and expect that `VAR` is unset.
25
26# `CLICOLOR_FORCE`
27
28Requires the <span class="stab portability" title="Available on crate feature `clicolor_force` only"><code>clicolor_force</code></span> feature.
29
30The meaning of the environment variable is the following:
31
32- if not set or `CLICOLOR_FORCE == ""` or `CLICOLOR_FORCE == "0"`: ignore;
33- if set and `CLICOLOR_FORCE != ""` and `CLICOLOR_FORCE != "0"`: [`ColorChoice::Always`].
34
35# `CLICOLOR`
36
37Requires the <span class="stab portability" title="Available on crate feature `clicolor` only"><code>clicolor</code></span> feature.
38
39The meaning of the environment variable is the following:
40
41- if not set or `CLICOLOR == ""`: ignore;
42- if set and `CLICOLOR == "0"`: [`ColorChoice::Never`];
43- if set and `CLICOLOR != ""` and `CLICOLOR != "0"`: [`ColorChoice::Auto`].
44
45# `NO_COLOR`
46
47Requires the <span class="stab portability" title="Available on crate feature `no_color` only"><code>no_color</code></span> feature.
48
49The meaning of the environment variable is the following:
50
51- if not set or `NO_COLOR == ""`: ignore;
52- if set and `NO_COLOR != ""`: [`ColorChoice::Never`].
53
54# Compatibility
55
56The goal of this crate is to merge and specify the standards proposed in
57<https://no-color.org> and <https://bixense.com/clicolors/>.
58
59Please note that the proposals in the latter are slightly ambiguous and undesirable
60(see [this issue](https://github.com/jhasse/clicolors/issues/8)),
61hence they are merely taken as an inspiration and not followed too strictly.
62
63Relevant quote from <https://no-color.org>:
64
65> Command-line software which adds ANSI color to its output by default should
66 check for a `NO_COLOR` environment variable that, when present and not an
67 empty string (regardless of its value), prevents the addition of ANSI color.
68
69Relevant quote from <https://bixense.com/clicolors/>:
70
71> The idea is to have the environment variables `CLICOLOR` and `CLICOLOR_FORCE`
72> (which are currently already used for this exact reason on some UNIX systems).
73> When set, the following rules should apply:
74> - `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn’t piped,
75> - `CLICOLOR == 0`: don’t output ANSI color escape codes,
76> - `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what.
77
78# Crate features
79*/
80#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
81//!
82
83#![deny(missing_docs, missing_debug_implementations, warnings)]
84#![cfg_attr(docsrs, feature(doc_auto_cfg))]
85
86/// Name of the `NO_COLOR` environment variable.
87#[cfg(feature = "no_color")]
88pub const NO_COLOR: &str = "NO_COLOR";
89/// Name of the `CLICOLOR` environment variable.
90#[cfg(feature = "clicolor")]
91pub const CLICOLOR: &str = "CLICOLOR";
92/// Name of the `CLICOLOR_FORCE` environment variable.
93#[cfg(feature = "clicolor_force")]
94pub const CLICOLOR_FORCE: &str = "CLICOLOR_FORCE";
95
96/**
97Possible color choices for the output.
98*/
99#[cfg_attr(
100 feature = "clap",
101 doc = r#"
102
103# Clap interoperability
104
105If the <span class="stab portability" title="Available on crate feature `clap` only"><code>clap</code></span> feature is enabled then
106[`ColorChoice`] can be converted to and from [`clap::ColorChoice`](https://docs.rs/clap/latest/clap/enum.ColorChoice.html).
107Moreover it implements [`clap::ValueEnum`](https://docs.rs/clap/latest/clap/trait.ValueEnum.html), hence can be used as
108
109```rust
110#[derive(clap::Parser)]
111struct Cli {
112 /// Coloring of the output
113 #[clap(long, value_name = "WHEN", arg_enum, global = true)]
114 color: Option<should_color::ColorChoice>,
115
116 // Other arguments...
117}
118```
119"#
120)]
121#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
122#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
123pub enum ColorChoice {
124 /// The output will not be colorized.
125 Never,
126 /// The output will be colorized if the output device is a tty,
127 /// i.e. when the output goes directly to a text screen or terminal emulator window.
128 Auto,
129 /// The output will be colorized.
130 Always,
131}
132
133#[cfg(feature = "stream")]
134impl ColorChoice {
135 /**
136 Determine the color setting for a specific stream.
137
138 If the choice is [`ColorChoice::Never`] or [`ColorChoice::Always`],
139 the result will be `false` and `true` respectively.
140
141 If the choice is [`ColorChoice::Auto`], then the answer depends on whether
142 the `stream` is a TTY or not.
143
144 See the examples [`colored.rs`] and [`termcolor.rs`] for a demonstration of how to use this method.
145
146 [`colored.rs`]: https://github.com/FedericoStra/should-color/blob/master/examples/colored.rs#L38-L39
147 [`termcolor.rs`]: https://github.com/FedericoStra/should-color/blob/master/examples/termcolor.rs#L38-L39
148 */
149 pub fn for_stream(&self, stream: atty::Stream) -> bool {
150 match self {
151 ColorChoice::Never => false,
152 ColorChoice::Always => true,
153 ColorChoice::Auto => atty::is(stream),
154 }
155 }
156}
157
158// #[cfg(feature = "clap")]
159// /// Alias for [`clap::ColorChoice`](https://docs.rs/clap/latest/clap/enum.ColorChoice.html).
160// pub type ClapColorChoice = clap::ColorChoice;
161
162#[cfg(feature = "clap")]
163impl From<ColorChoice> for clap::ColorChoice {
164 fn from(color_choice: ColorChoice) -> clap::ColorChoice {
165 match color_choice {
166 ColorChoice::Never => clap::ColorChoice::Never,
167 ColorChoice::Auto => clap::ColorChoice::Auto,
168 ColorChoice::Always => clap::ColorChoice::Always,
169 }
170 }
171}
172
173#[cfg(feature = "clap")]
174impl From<clap::ColorChoice> for ColorChoice {
175 fn from(color_choice: clap::ColorChoice) -> ColorChoice {
176 match color_choice {
177 clap::ColorChoice::Never => ColorChoice::Never,
178 clap::ColorChoice::Auto => ColorChoice::Auto,
179 clap::ColorChoice::Always => ColorChoice::Always,
180 }
181 }
182}
183
184/**
185Compute a [`clap::ColorChoice`](https://docs.rs/clap/latest/clap/enum.ColorChoice.html)
186suitable for the [`clap::App::color`](https://docs.rs/clap/latest/clap/builder/struct.App.html#method.color) setting.
187
188This is a convenience function equivalent to [`resolve`] without an explicit CLI preference
189and a default value of [`clap::ColorChoice::Auto`](https://docs.rs/clap/latest/clap/enum.ColorChoice.html#variant.Auto).
190
191```rust
192#[derive(clap::Parser)]
193#[clap(color = should_color::clap_color())]
194struct Cli {
195 // Arguments...
196}
197```
198*/
199#[cfg(feature = "clap")]
200pub fn clap_color() -> clap::ColorChoice {
201 resolve(None).unwrap_or(ColorChoice::Auto).into()
202}
203
204/**
205Get the setting of the `NO_COLOR` environment variable.
206
207The environment variable is treated as follows:
208
209- if not set or `NO_COLOR == ""`: return `None`;
210- if set and `NO_COLOR != ""`: return `Some(`[`ColorChoice::Never`]`)`.
211*/
212#[cfg(feature = "no_color")]
213pub fn no_color() -> Option<ColorChoice> {
214 match std::env::var_os(NO_COLOR) {
215 Some(s) if !s.is_empty() => Some(ColorChoice::Never),
216 _ => None,
217 }
218}
219
220/**
221Get the setting of the `CLICOLOR` environment variable.
222
223The environment variable is treated as follows:
224
225- if not set or `CLICOLOR == ""`: return `None`;
226- if set and `CLICOLOR == "0"`: return `Some(`[`ColorChoice::Never`]`)`;
227- if set and `CLICOLOR != ""` and `CLICOLOR != "0"`: return `Some(`[`ColorChoice::Auto`]`)`.
228*/
229#[cfg(feature = "clicolor")]
230pub fn clicolor() -> Option<ColorChoice> {
231 match std::env::var_os(CLICOLOR) {
232 Some(s) if s == "0" => Some(ColorChoice::Never),
233 Some(s) if !s.is_empty() => Some(ColorChoice::Auto),
234 _ => None,
235 }
236}
237
238/**
239Get the setting of the `CLICOLOR_FORCE` environment variable.
240
241The environment variable is treated as follows:
242
243- if not set or `CLICOLOR_FORCE == ""` or `CLICOLOR_FORCE == "0"`: return `None`;
244- if set and `CLICOLOR_FORCE != ""` and `CLICOLOR_FORCE != "0"`: return `Some`[`ColorChoice::Always`]`)`.
245*/
246#[cfg(feature = "clicolor_force")]
247pub fn clicolor_force() -> Option<ColorChoice> {
248 match std::env::var_os(CLICOLOR_FORCE) {
249 Some(s) if !s.is_empty() && s != "0" => Some(ColorChoice::Always),
250 _ => None,
251 }
252}
253
254/**
255Resolve the output color choice from the environment variables and an explicit CLI preference.
256
257Notice that the resolution depends on the activation of the features
258<span class="stab portability"><code>clicolor_force</code></span>,
259<span class="stab portability"><code>clicolor</code></span>, and
260<span class="stab portability"><code>no_color</code></span>.
261Please refer to the [crate level documentation](crate) for a detailed description of the
262resolution process.
263
264Commonly this function will be called as `resolve(cli).unwrap_or(default)` to take into account
265a preference expressed through the CLI arguments and the default behavior of the application.
266See the examples [`colored.rs`] and [`termcolor.rs`] for a demonstration of how to use this function.
267
268[`colored.rs`]: https://github.com/FedericoStra/should-color/blob/master/examples/colored.rs#L36
269[`termcolor.rs`]: https://github.com/FedericoStra/should-color/blob/master/examples/termcolor.rs#L36
270
271# Examples
272
273The following examples assume that all the features
274<span class="stab portability" title="Available on crate feature `clicolor` only"><code>clicolor</code></span>,
275<span class="stab portability" title="Available on crate feature `clicolor_force` only"><code>clicolor_force</code></span>, and
276<span class="stab portability" title="Available on crate feature `no_color` only"><code>no_color</code></span>
277are enabled.
278
279- ```
280 # use should_color::{resolve, ColorChoice};
281 std::env::set_var("CLICOLOR_FORCE", "false"); // this wins
282 # #[cfg(all(feature = "clicolor_force"))]
283 assert_eq!(resolve(Some(ColorChoice::Never)), Some(ColorChoice::Always));
284 ```
285
286- ```
287 # use should_color::{resolve, ColorChoice};
288 std::env::remove_var("CLICOLOR_FORCE");
289 std::env::set_var("CLICOLOR", "1"); // this wins
290 # #[cfg(all(feature = "clicolor"))]
291 assert_eq!(resolve(None), Some(ColorChoice::Auto));
292 # #[cfg(not(feature = "clicolor"))]
293 # assert_eq!(resolve(None), None);
294 ```
295
296- ```
297 # use should_color::{resolve, ColorChoice};
298 std::env::remove_var("CLICOLOR_FORCE");
299 std::env::set_var("CLICOLOR", "0"); // this wins
300 # #[cfg(all(feature = "clicolor"))]
301 assert_eq!(resolve(None), Some(ColorChoice::Never));
302 # #[cfg(not(feature = "clicolor"))]
303 # assert_eq!(resolve(None), None);
304 ```
305
306- ```
307 # use should_color::{resolve, ColorChoice};
308 std::env::remove_var("CLICOLOR_FORCE");
309 std::env::remove_var("CLICOLOR");
310 std::env::set_var("NO_COLOR", "1"); // this wins
311 # #[cfg(feature = "no_color")]
312 assert_eq!(resolve(None), Some(ColorChoice::Never));
313 # #[cfg(not(feature = "no_color"))]
314 # assert_eq!(resolve(None), None);
315 ```
316
317- ```
318 # use should_color::{resolve, ColorChoice};
319 std::env::remove_var("CLICOLOR_FORCE");
320 std::env::remove_var("CLICOLOR");
321 std::env::remove_var("NO_COLOR");
322 assert_eq!(resolve(None), None);
323 ```
324*/
325pub fn resolve(cli: Option<ColorChoice>) -> Option<ColorChoice> {
326 #[cfg(feature = "clicolor_force")]
327 let choice = clicolor_force();
328
329 #[cfg(feature = "clicolor_force")]
330 let choice = choice.or(cli);
331 #[cfg(not(feature = "clicolor_force"))]
332 let choice = cli;
333
334 #[cfg(feature = "clicolor")]
335 let choice = choice.or_else(clicolor);
336
337 #[cfg(feature = "no_color")]
338 let choice = choice.or_else(no_color);
339
340 choice
341}
342
343#[cfg(test)]
344mod tests {
345 #[test]
346 #[cfg(feature = "no_color")]
347 fn test_no_color() {
348 use super::*;
349
350 std::env::remove_var(NO_COLOR);
351 assert_eq!(no_color(), None);
352
353 std::env::set_var(NO_COLOR, "");
354 assert_eq!(no_color(), None);
355
356 for s in ["0", "1", "false", "true", "="] {
357 std::env::set_var(NO_COLOR, s);
358 assert_eq!(no_color(), Some(ColorChoice::Never));
359 }
360 }
361
362 #[test]
363 #[cfg(feature = "clicolor")]
364 fn test_clicolor() {
365 use super::*;
366
367 std::env::remove_var(CLICOLOR);
368 assert_eq!(clicolor(), None);
369
370 std::env::set_var(CLICOLOR, "");
371 assert_eq!(clicolor(), None);
372
373 std::env::set_var(CLICOLOR, "0");
374 assert_eq!(clicolor(), Some(ColorChoice::Never));
375
376 for s in ["1", "false", "true", "="] {
377 std::env::set_var(CLICOLOR, s);
378 assert_eq!(clicolor(), Some(ColorChoice::Auto));
379 }
380 }
381
382 #[test]
383 #[cfg(feature = "clicolor_force")]
384 fn test_clicolor_force() {
385 use super::*;
386
387 std::env::remove_var(CLICOLOR_FORCE);
388 assert_eq!(clicolor_force(), None);
389
390 std::env::set_var(CLICOLOR_FORCE, "");
391 assert_eq!(clicolor_force(), None);
392
393 std::env::set_var(CLICOLOR_FORCE, "0");
394 assert_eq!(clicolor_force(), None);
395
396 for s in ["1", "false", "true", "="] {
397 std::env::set_var(CLICOLOR_FORCE, s);
398 assert_eq!(clicolor_force(), Some(ColorChoice::Always));
399 }
400 }
401}