use std::{
env,
io::{self, IsTerminal},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorLevel {
None,
Basic, Ansi256, TrueColor, }
fn is_tty() -> bool {
io::stdout().is_terminal()
}
pub fn detect_color_level() -> ColorLevel {
detect_color_level_inner(
is_tty(),
env::var_os("NO_COLOR").is_some(),
env::var("FORCE_COLOR").ok().as_deref(),
env::var("COLORTERM").ok().as_deref(),
env::var("TERM").ok().as_deref(),
)
}
fn detect_color_level_inner(
is_tty: bool,
no_color: bool,
force_color: Option<&str>,
colorterm: Option<&str>,
term: Option<&str>,
) -> ColorLevel {
if no_color {
return ColorLevel::None;
}
match force_color {
Some("0") => return ColorLevel::None,
Some("1") => return ColorLevel::Basic,
Some("2") => return ColorLevel::Ansi256,
Some(_) => return ColorLevel::TrueColor,
None => {}
}
if !is_tty {
return ColorLevel::None;
}
if let Some(ct) = colorterm
&& (ct.contains("truecolor") || ct.contains("24bit"))
{
return ColorLevel::TrueColor;
}
if let Some(term) = term {
if term == "dumb" {
return ColorLevel::None;
}
if term.contains("256color") {
return ColorLevel::Ansi256;
}
}
ColorLevel::Basic
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_none_when_not_tty_without_force_color() {
assert_eq!(
detect_color_level_inner(
false,
false,
None,
Some("truecolor"),
Some("xterm-256color")
),
ColorLevel::None
);
}
#[test]
fn no_color_disables_color_without_force_color() {
assert_eq!(
detect_color_level_inner(true, true, None, Some("truecolor"), Some("xterm-256color")),
ColorLevel::None
);
}
#[test]
fn force_color_overrides_not_tty() {
assert_eq!(
detect_color_level_inner(
false,
false,
Some("3"),
Some("truecolor"),
Some("xterm-256color")
),
ColorLevel::TrueColor
);
}
#[test]
fn no_color_overrides_force_color() {
assert_eq!(
detect_color_level_inner(
true,
true,
Some("3"),
Some("truecolor"),
Some("xterm-256color")
),
ColorLevel::None
);
}
#[test]
fn force_color_zero_disables_color() {
assert_eq!(
detect_color_level_inner(
true,
false,
Some("0"),
Some("truecolor"),
Some("xterm-256color")
),
ColorLevel::None
);
}
#[test]
fn force_color_one_returns_basic() {
assert_eq!(
detect_color_level_inner(true, false, Some("1"), None, None),
ColorLevel::Basic
);
}
#[test]
fn force_color_two_returns_ansi256() {
assert_eq!(
detect_color_level_inner(true, false, Some("2"), None, None),
ColorLevel::Ansi256
);
}
#[test]
fn force_color_three_returns_truecolor() {
assert_eq!(
detect_color_level_inner(true, false, Some("3"), None, None),
ColorLevel::TrueColor
);
}
#[test]
fn unknown_force_color_returns_truecolor() {
assert_eq!(
detect_color_level_inner(true, false, Some("yes"), None, None),
ColorLevel::TrueColor
);
}
#[test]
fn colorterm_truecolor_returns_truecolor() {
assert_eq!(
detect_color_level_inner(true, false, None, Some("truecolor"), None),
ColorLevel::TrueColor
);
}
#[test]
fn colorterm_24bit_returns_truecolor() {
assert_eq!(
detect_color_level_inner(true, false, None, Some("24bit"), None),
ColorLevel::TrueColor
);
}
#[test]
fn term_dumb_returns_none() {
assert_eq!(
detect_color_level_inner(true, false, None, None, Some("dumb")),
ColorLevel::None
);
}
#[test]
fn term_256color_returns_ansi256() {
assert_eq!(
detect_color_level_inner(true, false, None, None, Some("xterm-256color")),
ColorLevel::Ansi256
);
}
#[test]
fn fallback_returns_basic() {
assert_eq!(
detect_color_level_inner(true, false, None, None, Some("xterm")),
ColorLevel::Basic
);
}
#[test]
fn colorterm_unknown_falls_through_to_term() {
assert_eq!(
detect_color_level_inner(true, false, None, Some("ansi"), Some("xterm-256color")),
ColorLevel::Ansi256
);
}
#[test]
fn no_colorterm_no_term_fallback_basic() {
assert_eq!(
detect_color_level_inner(true, false, None, None, None),
ColorLevel::Basic
);
}
#[test]
fn term_unknown_falls_back_basic() {
assert_eq!(
detect_color_level_inner(true, false, None, None, Some("vt100")),
ColorLevel::Basic
);
}
}