diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/supports-color/src | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/supports-color/src')
-rw-r--r-- | vendor/supports-color/src/lib.rs | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/vendor/supports-color/src/lib.rs b/vendor/supports-color/src/lib.rs new file mode 100644 index 0000000..f877d9b --- /dev/null +++ b/vendor/supports-color/src/lib.rs @@ -0,0 +1,263 @@ +//! Detects whether a terminal supports color, and gives details about that +//! support. It takes into account the `NO_COLOR` environment variable. +//! +//! This crate is a Rust port of [@sindresorhus](https://github.com/sindresorhus)' +//! [NPM package by the same name](https://npm.im/supports-color). +//! +//! ## Example +//! +//! ```rust +//! use supports_color::Stream; +//! +//! if let Some(support) = supports_color::on(Stream::Stdout) { +//! if support.has_16m { +//! println!("16 million (RGB) colors are supported"); +//! } else if support.has_256 { +//! println!("256-bit colors are supported."); +//! } else if support.has_basic { +//! println!("Only basic ANSI colors are supported."); +//! } +//! } else { +//! println!("No color support."); +//! } +//! ``` +#![allow(clippy::bool_to_int_with_if)] + +use std::cell::UnsafeCell; +use std::env; +use std::sync::Once; + +/// possible stream sources +#[derive(Clone, Copy, Debug)] +pub enum Stream { + Stdout, + Stderr, +} + +fn env_force_color() -> usize { + if let Ok(force) = env::var("FORCE_COLOR") { + match force.as_ref() { + "true" | "" => 1, + "false" => 0, + f => std::cmp::min(f.parse().unwrap_or(1), 3), + } + } else if let Ok(cli_clr_force) = env::var("CLICOLOR_FORCE") { + if cli_clr_force != "0" { + 1 + } else { + 0 + } + } else { + 0 + } +} + +fn env_no_color() -> bool { + match as_str(&env::var("NO_COLOR")) { + Ok("0") | Err(_) => false, + Ok(_) => true, + } +} + +// same as Option::as_deref +fn as_str<E>(option: &Result<String, E>) -> Result<&str, &E> { + match option { + Ok(inner) => Ok(inner), + Err(e) => Err(e), + } +} + +fn translate_level(level: usize) -> Option<ColorLevel> { + if level == 0 { + None + } else { + Some(ColorLevel { + level, + has_basic: true, + has_256: level >= 2, + has_16m: level >= 3, + }) + } +} + +fn is_a_tty(stream: Stream) -> bool { + use is_terminal::*; + match stream { + Stream::Stdout => std::io::stdout().is_terminal(), + Stream::Stderr => std::io::stderr().is_terminal(), + } +} + +fn supports_color(stream: Stream) -> usize { + let force_color = env_force_color(); + if force_color > 0 { + force_color + } else if env_no_color() + || as_str(&env::var("TERM")) == Ok("dumb") + || !(is_a_tty(stream) || env::var("IGNORE_IS_TERMINAL").map_or(false, |v| v != "0")) + { + 0 + } else if env::var("COLORTERM").map(|colorterm| check_colorterm_16m(&colorterm)) == Ok(true) + || env::var("TERM").map(|term| check_term_16m(&term)) == Ok(true) + || as_str(&env::var("TERM_PROGRAM")) == Ok("iTerm.app") + { + 3 + } else if as_str(&env::var("TERM_PROGRAM")) == Ok("Apple_Terminal") + || env::var("TERM").map(|term| check_256_color(&term)) == Ok(true) + { + 2 + } else if env::var("COLORTERM").is_ok() + || env::var("TERM").map(|term| check_ansi_color(&term)) == Ok(true) + || env::consts::OS == "windows" + || env::var("CLICOLOR").map_or(false, |v| v != "0") + || is_ci::uncached() + { + 1 + } else { + 0 + } +} + +fn check_ansi_color(term: &str) -> bool { + term.starts_with("screen") + || term.starts_with("xterm") + || term.starts_with("vt100") + || term.starts_with("vt220") + || term.starts_with("rxvt") + || term.contains("color") + || term.contains("ansi") + || term.contains("cygwin") + || term.contains("linux") +} + +fn check_colorterm_16m(colorterm: &str) -> bool { + colorterm == "truecolor" || colorterm == "24bit" +} + +fn check_term_16m(term: &str) -> bool { + term.ends_with("direct") || term.ends_with("truecolor") +} + +fn check_256_color(term: &str) -> bool { + term.ends_with("256") || term.ends_with("256color") +} + +/** +Returns a [ColorLevel] if a [Stream] supports terminal colors. +*/ +pub fn on(stream: Stream) -> Option<ColorLevel> { + translate_level(supports_color(stream)) +} + +struct CacheCell(UnsafeCell<Option<ColorLevel>>); + +unsafe impl Sync for CacheCell {} + +static INIT: [Once; 2] = [Once::new(), Once::new()]; +static ON_CACHE: [CacheCell; 2] = [ + CacheCell(UnsafeCell::new(None)), + CacheCell(UnsafeCell::new(None)), +]; + +macro_rules! assert_stream_in_bounds { + ($($variant:ident)*) => { + $( + const _: () = [(); 2][Stream::$variant as usize]; + )* + }; +} + +// Compile-time assertion that the below indexing will never panic +assert_stream_in_bounds!(Stdout Stderr); + +/** +Returns a [ColorLevel] if a [Stream] supports terminal colors, caching the result to +be returned from then on. + +If you expect your environment to change between calls, use [`on`] +*/ +pub fn on_cached(stream: Stream) -> Option<ColorLevel> { + let stream_index = stream as usize; + INIT[stream_index].call_once(|| unsafe { + *ON_CACHE[stream_index].0.get() = translate_level(supports_color(stream)); + }); + + unsafe { *ON_CACHE[stream_index].0.get() } +} + +/** +Color level support details. + +This type is returned from [on]. See documentation for its fields for more details. +*/ +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub struct ColorLevel { + level: usize, + /// Basic ANSI colors are supported. + pub has_basic: bool, + /// 256-bit colors are supported. + pub has_256: bool, + /// 16 million (RGB) colors are supported. + pub has_16m: bool, +} + +#[cfg(test)] +mod tests { + use std::sync::Mutex; + + use super::*; + + // needed to prevent race conditions when mutating the environment + static TEST_LOCK: Mutex<()> = Mutex::new(()); + + fn set_up() { + // clears process env variable + env::vars().for_each(|(k, _v)| env::remove_var(k)); + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_empty_env() { + let _test_guard = TEST_LOCK.lock().unwrap(); + set_up(); + + assert_eq!(on(Stream::Stdout), None); + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_clicolor_ansi() { + let _test_guard = TEST_LOCK.lock().unwrap(); + set_up(); + + env::set_var("IGNORE_IS_TERMINAL", "1"); + env::set_var("CLICOLOR", "1"); + let expected = Some(ColorLevel { + level: 1, + has_basic: true, + has_256: false, + has_16m: false, + }); + assert_eq!(on(Stream::Stdout), expected); + + env::set_var("CLICOLOR", "0"); + assert_eq!(on(Stream::Stdout), None); + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_clicolor_force_ansi() { + let _test_guard = TEST_LOCK.lock().unwrap(); + set_up(); + + env::set_var("CLICOLOR", "0"); + env::set_var("CLICOLOR_FORCE", "1"); + let expected = Some(ColorLevel { + level: 1, + has_basic: true, + has_256: false, + has_16m: false, + }); + assert_eq!(on(Stream::Stdout), expected); + } +} |