midenc_session/
color.rs

1use alloc::string::ToString;
2use core::str::FromStr;
3
4/// ColorChoice represents the color preferences of an end user.
5///
6/// The `Default` implementation for this type will select `Auto`, which tries
7/// to do the right thing based on the current environment.
8///
9/// The `FromStr` implementation for this type converts a lowercase kebab-case
10/// string of the variant name to the corresponding variant. Any other string
11/// results in an error.
12#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
13#[cfg_attr(feature = "std", derive(clap::ValueEnum))]
14pub enum ColorChoice {
15    /// Try very hard to emit colors. This includes emitting ANSI colors
16    /// on Windows if the console API is unavailable.
17    Always,
18    /// AlwaysAnsi is like Always, except it never tries to use anything other
19    /// than emitting ANSI color codes.
20    AlwaysAnsi,
21    /// Try to use colors, but don't force the issue. If the console isn't
22    /// available on Windows, or if TERM=dumb, or if `NO_COLOR` is defined, for
23    /// example, then don't use colors.
24    #[default]
25    Auto,
26    /// Never emit colors.
27    Never,
28}
29
30#[cfg(feature = "std")]
31impl From<ColorChoice> for termcolor::ColorChoice {
32    fn from(choice: ColorChoice) -> Self {
33        match choice {
34            ColorChoice::Always => Self::Always,
35            ColorChoice::AlwaysAnsi => Self::AlwaysAnsi,
36            ColorChoice::Auto => Self::Auto,
37            ColorChoice::Never => Self::Never,
38        }
39    }
40}
41
42#[derive(Debug, thiserror::Error)]
43#[error("invalid color choice: {0}")]
44pub struct ColorChoiceParseError(alloc::borrow::Cow<'static, str>);
45
46impl FromStr for ColorChoice {
47    type Err = ColorChoiceParseError;
48
49    fn from_str(s: &str) -> Result<Self, Self::Err> {
50        match s.to_lowercase().as_str() {
51            "always" => Ok(ColorChoice::Always),
52            "always-ansi" => Ok(ColorChoice::AlwaysAnsi),
53            "never" => Ok(ColorChoice::Never),
54            "auto" => Ok(ColorChoice::Auto),
55            unknown => Err(ColorChoiceParseError(unknown.to_string().into())),
56        }
57    }
58}
59
60impl ColorChoice {
61    /// Returns true if we should attempt to write colored output.
62    pub fn should_attempt_color(&self) -> bool {
63        match *self {
64            ColorChoice::Always => true,
65            ColorChoice::AlwaysAnsi => true,
66            ColorChoice::Never => false,
67            #[cfg(feature = "std")]
68            ColorChoice::Auto => self.env_allows_color(),
69            #[cfg(not(feature = "std"))]
70            ColorChoice::Auto => false,
71        }
72    }
73
74    #[cfg(all(feature = "std", not(windows)))]
75    pub fn env_allows_color(&self) -> bool {
76        match std::env::var_os("TERM") {
77            // If TERM isn't set, then we are in a weird environment that
78            // probably doesn't support colors.
79            None => return false,
80            Some(k) => {
81                if k == "dumb" {
82                    return false;
83                }
84            }
85        }
86        // If TERM != dumb, then the only way we don't allow colors at this
87        // point is if NO_COLOR is set.
88        if std::env::var_os("NO_COLOR").is_some() {
89            return false;
90        }
91        true
92    }
93
94    #[cfg(all(feature = "std", windows))]
95    pub fn env_allows_color(&self) -> bool {
96        // On Windows, if TERM isn't set, then we shouldn't automatically
97        // assume that colors aren't allowed. This is unlike Unix environments
98        // where TERM is more rigorously set.
99        if let Some(k) = std::env::var_os("TERM") {
100            if k == "dumb" {
101                return false;
102            }
103        }
104        // If TERM != dumb, then the only way we don't allow colors at this
105        // point is if NO_COLOR is set.
106        if std::env::var_os("NO_COLOR").is_some() {
107            return false;
108        }
109        true
110    }
111
112    /// Returns true if this choice should forcefully use ANSI color codes.
113    ///
114    /// It's possible that ANSI is still the correct choice even if this
115    /// returns false.
116    #[cfg(all(feature = "std", windows))]
117    pub fn should_ansi(&self) -> bool {
118        match *self {
119            ColorChoice::Always => false,
120            ColorChoice::AlwaysAnsi => true,
121            ColorChoice::Never => false,
122            ColorChoice::Auto => {
123                match std::env::var("TERM") {
124                    Err(_) => false,
125                    // cygwin doesn't seem to support ANSI escape sequences
126                    // and instead has its own variety. However, the Windows
127                    // console API may be available.
128                    Ok(k) => k != "dumb" && k != "cygwin",
129                }
130            }
131        }
132    }
133
134    /// Returns true if this choice should forcefully use ANSI color codes.
135    ///
136    /// It's possible that ANSI is still the correct choice even if this
137    /// returns false.
138    #[cfg(not(feature = "std"))]
139    pub fn should_ansi(&self) -> bool {
140        match *self {
141            ColorChoice::Always => false,
142            ColorChoice::AlwaysAnsi => true,
143            ColorChoice::Never => false,
144            ColorChoice::Auto => false,
145        }
146    }
147}