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}