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}