anstyle_query/lib.rs
1//! Low level terminal capability lookups
2
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
4#![warn(missing_docs)]
5#![warn(clippy::print_stderr)]
6#![warn(clippy::print_stdout)]
7
8pub mod windows;
9
10/// Check [CLICOLOR] status
11///
12/// - When `true`, ANSI colors are supported and should be used when the program isn't piped,
13/// similar to [`term_supports_color`]
14/// - When `false`, don’t output ANSI color escape codes, similar to [`no_color`]
15///
16/// See also:
17/// - [terminfo](https://crates.io/crates/terminfo) or [term](https://crates.io/crates/term) for
18/// checking termcaps
19/// - [termbg](https://crates.io/crates/termbg) for detecting background color
20///
21/// [CLICOLOR]: https://bixense.com/clicolors/
22#[inline]
23pub fn clicolor() -> Option<bool> {
24 let value = std::env::var_os("CLICOLOR")?;
25 Some(value != "0")
26}
27
28/// Check [CLICOLOR_FORCE] status
29///
30/// ANSI colors should be enabled no matter what.
31///
32/// [CLICOLOR_FORCE]: https://bixense.com/clicolors/
33#[inline]
34pub fn clicolor_force() -> bool {
35 non_empty(std::env::var_os("CLICOLOR_FORCE").as_deref())
36}
37
38/// Check [NO_COLOR] status
39///
40/// When `true`, should prevent the addition of ANSI color.
41///
42/// User-level configuration files and per-instance command-line arguments should override
43/// [NO_COLOR]. A user should be able to export `$NO_COLOR` in their shell configuration file as a
44/// default, but configure a specific program in its configuration file to specifically enable
45/// color.
46///
47/// [NO_COLOR]: https://no-color.org/
48#[inline]
49pub fn no_color() -> bool {
50 non_empty(std::env::var_os("NO_COLOR").as_deref())
51}
52
53/// Check `TERM` for color support
54#[inline]
55#[cfg(not(windows))]
56pub fn term_supports_color() -> bool {
57 match std::env::var_os("TERM") {
58 // If TERM isn't set, then we are in a weird environment that
59 // probably doesn't support colors.
60 None => return false,
61 Some(k) => {
62 if k == "dumb" {
63 return false;
64 }
65 }
66 }
67 true
68}
69
70/// Check `TERM` for color support
71#[inline]
72#[cfg(windows)]
73pub fn term_supports_color() -> bool {
74 // On Windows, if TERM isn't set, then we shouldn't automatically
75 // assume that colors aren't allowed. This is unlike Unix environments
76 // where TERM is more rigorously set.
77 if let Some(k) = std::env::var_os("TERM") {
78 if k == "dumb" {
79 return false;
80 }
81 }
82 true
83}
84
85/// Check `TERM` for ANSI color support
86#[inline]
87#[cfg(not(windows))]
88pub fn term_supports_ansi_color() -> bool {
89 term_supports_color()
90}
91
92/// Check `TERM` for ANSI color support
93#[inline]
94#[cfg(windows)]
95pub fn term_supports_ansi_color() -> bool {
96 match std::env::var_os("TERM") {
97 // If TERM isn't set, then we are in a weird environment that
98 // probably doesn't support ansi.
99 None => return false,
100 Some(k) => {
101 // cygwin doesn't seem to support ANSI escape sequences
102 // and instead has its own variety. However, the Windows
103 // console API may be available.
104 if k == "dumb" || k == "cygwin" {
105 return false;
106 }
107 }
108 }
109 true
110}
111
112/// Check [COLORTERM] for truecolor support
113///
114/// [COLORTERM]: https://github.com/termstandard/colors
115#[inline]
116pub fn truecolor() -> bool {
117 let value = std::env::var_os("COLORTERM");
118 let value = value.as_deref().unwrap_or_default();
119 value == "truecolor" || value == "24bit"
120}
121
122/// Report whether this is running in CI
123///
124/// CI is a common environment where, despite being piped, ansi color codes are supported
125///
126/// This is not as exhaustive as you'd find in a crate like `is_ci` but it should work in enough
127/// cases.
128#[inline]
129pub fn is_ci() -> bool {
130 // Assuming its CI based on presence because who would be setting `CI=false`?
131 //
132 // This makes it easier to all of the potential values when considering our known values:
133 // - Gitlab and Github set it to `true`
134 // - Woodpecker sets it to `woodpecker`
135 std::env::var_os("CI").is_some()
136}
137
138fn non_empty(var: Option<&std::ffi::OsStr>) -> bool {
139 !var.unwrap_or_default().is_empty()
140}
141
142#[cfg(test)]
143mod test {
144 use super::*;
145
146 #[test]
147 fn non_empty_not_present() {
148 assert!(!non_empty(None));
149 }
150
151 #[test]
152 fn non_empty_empty() {
153 assert!(!non_empty(Some(std::ffi::OsStr::new(""))));
154 }
155
156 #[test]
157 fn non_empty_texty() {
158 assert!(non_empty(Some(std::ffi::OsStr::new("hello"))));
159 }
160}