color_nope/lib.rs
1/*!
2Support for standard options to disable colors in the terminal.
3
4An implementation of the [NO_COLOR](https://no-color.org/) standard, following
5the [Command Line Interface Guidelines](https://clig.dev/#output).
6
7## Usage
8
9See [`ColorNope`] for usage examples.
10*/
11
12#![deny(missing_docs)]
13
14#[cfg(doctest)]
15use doc_comment::doctest;
16#[cfg(doctest)]
17doctest!("../README.md");
18
19use std::ffi::OsString;
20
21/// Decides whether color should be enabled, based on the environment and the
22/// target stream.
23///
24/// Assumes color is enabled by default, unless indicated otherwise.
25///
26/// # Examples
27///
28/// Can be created using the `from_env()` convenience function:
29///
30/// ```rust
31/// use color_nope::{ColorNope, Stream};
32///
33/// assert_eq!(
34/// ColorNope::from_env().enable_color_for(Stream::Stdout),
35/// false
36/// );
37/// ```
38///
39/// Or by passing in your own values:
40///
41/// ```rust
42/// use color_nope::{ColorNope, Stream, Force};
43///
44/// assert_eq!(
45/// ColorNope::new(
46/// std::env::var_os("TERM"),
47/// std::env::var_os("NO_COLOR"),
48/// if std::env::args_os().any(|a| a == "--no-color") {
49/// Some(Force::Off)
50/// } else {
51/// None
52/// },
53/// )
54/// .enable_color_for(Stream::Stdout),
55/// false
56/// );
57/// ```
58#[derive(Clone, Debug)]
59pub struct ColorNope {
60 term_env: Option<OsString>,
61 no_color_env: Option<OsString>,
62 force_color: Option<Force>,
63}
64
65impl ColorNope {
66 /// Create a new instance without touching the environment.
67 ///
68 /// [`ColorNope`] considers the `TERM` and `NO_COLOR` environmental
69 /// variables (`term_env` and `no_color_env` respectively).
70 ///
71 /// These values can be overridden by using `force_color`.
72 ///
73 /// # Example
74 ///
75 /// ```rust
76 /// # use color_nope::ColorNope;
77 /// ColorNope::new(
78 /// std::env::var_os("TERM"),
79 /// std::env::var_os("NO_COLOR"),
80 /// None
81 /// );
82 /// ```
83 pub fn new(
84 term_env: Option<OsString>,
85 no_color_env: Option<OsString>,
86 force_color: Option<Force>,
87 ) -> ColorNope {
88 ColorNope {
89 term_env,
90 no_color_env,
91 force_color,
92 }
93 }
94
95 /// Uses the `TERM` and `NO_COLOR` environmental variables.
96 pub fn from_env() -> ColorNope {
97 ColorNope {
98 term_env: std::env::var_os("TERM"),
99 no_color_env: std::env::var_os("NO_COLOR"),
100 force_color: None,
101 }
102 }
103
104 /// Should color be enabled for the target stream?
105 pub fn enable_color_for(&self, stream: Stream) -> bool {
106 match self.force_color {
107 Some(force) => force.enable_color(),
108 None => {
109 atty::is(stream.into())
110 && term_allows_color(self.term_env.as_ref())
111 && no_color_allows_color(self.no_color_env.as_ref())
112 }
113 }
114 }
115}
116
117/// Output streams.
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub enum Stream {
120 #[allow(missing_docs)]
121 Stdout,
122 #[allow(missing_docs)]
123 Stderr,
124}
125impl From<Stream> for atty::Stream {
126 fn from(s: Stream) -> Self {
127 match s {
128 Stream::Stdout => atty::Stream::Stdout,
129 Stream::Stderr => atty::Stream::Stderr,
130 }
131 }
132}
133
134/// Override other settings to force colors on or off.
135#[derive(Clone, Copy, Debug, Eq, PartialEq)]
136pub enum Force {
137 #[allow(missing_docs)]
138 On,
139 #[allow(missing_docs)]
140 Off,
141}
142impl Force {
143 fn enable_color(&self) -> bool {
144 use Force::*;
145 match self {
146 On => true,
147 Off => false,
148 }
149 }
150}
151
152fn no_color_allows_color(no_color: Option<&OsString>) -> bool {
153 no_color.map_or(true, |s| s.is_empty())
154}
155
156// These next functions are shamelessly stolen from [termcolor](https://github.com/BurntSushi/termcolor).
157
158#[cfg(not(windows))]
159fn term_allows_color(term: Option<&OsString>) -> bool {
160 match term {
161 // If TERM isn't set, then we are in a weird environment that
162 // probably doesn't support colors.
163 None => false,
164 Some(v) => v != "dumb",
165 }
166}
167
168#[cfg(windows)]
169fn term_allows_color(term: Option<&OsString>) -> bool {
170 // On Windows, if TERM isn't set, then we shouldn't automatically
171 // assume that colors aren't allowed. This is unlike Unix environments
172 // where TERM is more rigorously set.
173 if let Some(v) = term {
174 v != "dumb"
175 } else {
176 true
177 }
178}