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}