term_color_support/colors/
mod.rs

1//! Module for managing color support detection and information.
2//!
3//! This module provides functionality to detect the color support level of the terminal,
4//! determine color support for standard output and standard error streams, and create
5//! `ColorInfo` structs representing the color support information.
6//!
7//! # Note
8//!
9//! This module relies on the `atty` crate for detecting whether standard output and
10//! standard error streams are connected to a terminal.
11//!
12
13/// The module provides functionality to detect and manage color support information for terminal output
14/// streams.
15///
16/// Arguments:
17///
18/// * `options`: The `options` parameter in the `determine_stream_color_level` function is of type
19/// `OutputStreamOptions`. It contains information about the stream, such as whether it is a TTY
20/// (terminal) and any sniffed flags related to color support. The function uses this information to
21/// determine the color support
22///
23/// Returns:
24///
25/// The module provides functionality to detect the color support level of the terminal, determine color
26/// support for standard output and standard error streams, and create `ColorInfo` structs representing
27/// the color support information. It also includes unit tests for the module's functions.
28use atty::{ Stream, is };
29
30use crate::environment::Environment;
31use crate::options::{
32    OutputStreamOptions,
33    has_flag,
34    extract_force_color_level_from_env,
35    extract_color_level_from_flags,
36};
37
38/// Enumeration representing the level of color support.
39#[derive(Debug, PartialEq)]
40pub enum ColorSupportLevel {
41    /// No color support.
42    NoColor,
43    /// Basic color support.
44    Basic,
45    /// Support for 256 colors.
46    Colors256,
47    /// True color support.
48    TrueColor,
49}
50
51impl ColorSupportLevel {
52    /// Converts a u32 value to a ColorSupportLevel.
53    pub fn from_u32(level: u32) -> Option<ColorSupportLevel> {
54        match level {
55            0 => Some(ColorSupportLevel::NoColor),
56            1 => Some(ColorSupportLevel::Basic),
57            2 => Some(ColorSupportLevel::Colors256),
58            3 => Some(ColorSupportLevel::TrueColor),
59            _ => None,
60        }
61    }
62}
63
64/// Struct representing color support information.
65#[derive(Debug, PartialEq)]
66pub struct ColorInfo {
67    /// The color support level.
68    pub level: ColorSupportLevel,
69    /// Indicates if basic color support is available.
70    pub has_basic: bool,
71    /// Indicates if 256-color support is available.
72    pub has_256: bool,
73    /// Indicates if true color support (16 million colors) is available.
74    pub has_16m: bool,
75}
76
77impl ColorInfo {
78    /// Creates a new ColorInfo instance based on the provided color support level.
79    pub fn new(level: ColorSupportLevel) -> Self {
80        let (has_basic, has_256, has_16m) = match level {
81            ColorSupportLevel::NoColor => (false, false, false),
82            ColorSupportLevel::Basic => (true, false, false),
83            ColorSupportLevel::Colors256 => (true, true, false),
84            ColorSupportLevel::TrueColor => (true, true, true),
85        };
86
87        ColorInfo {
88            level,
89            has_basic,
90            has_256,
91            has_16m,
92        }
93    }
94}
95
96/// Struct representing color support for standard output and standard error streams.
97#[derive(Debug)]
98pub struct ColorSupport {
99    /// Color support information for standard output stream.
100    pub stdout: ColorInfo,
101    /// Color support information for standard error stream.
102    pub stderr: ColorInfo,
103}
104
105impl ColorSupport {
106    /// Detects and returns color support information for standard output stream.
107    pub fn stdout() -> ColorInfo {
108        let stdout_color_support_level: Option<ColorSupportLevel> = determine_stream_color_level(
109            OutputStreamOptions::new(Some(is(Stream::Stdout)), None)
110        );
111        ColorInfo::new(stdout_color_support_level.unwrap_or(ColorSupportLevel::NoColor))
112    }
113
114    /// Detects and returns color support information for standard error stream.
115    pub fn stderr() -> ColorInfo {
116        let stderr_color_support_level: Option<ColorSupportLevel> = determine_stream_color_level(
117            OutputStreamOptions::new(Some(is(Stream::Stderr)), None)
118        );
119        ColorInfo::new(stderr_color_support_level.unwrap_or(ColorSupportLevel::NoColor))
120    }
121}
122
123/// Determines the color support level for a stream based on the provided options.
124pub fn determine_stream_color_level(options: OutputStreamOptions) -> Option<ColorSupportLevel> {
125    let args = std::env::args().collect::<Vec<String>>();
126
127    let force_color_level_from_env = extract_force_color_level_from_env();
128
129    let mut color_level_from_flag: Option<ColorSupportLevel> = Some(ColorSupportLevel::NoColor);
130
131    if force_color_level_from_env.is_none() {
132        color_level_from_flag = extract_color_level_from_flags(&args);
133    }
134
135    let force_color = if options.sniff_flags == true {
136        color_level_from_flag
137    } else {
138        force_color_level_from_env
139    };
140
141    if force_color.is_some() {
142        return force_color;
143    }
144
145    if options.sniff_flags {
146        if
147            has_flag("color=16m", &args) ||
148            has_flag("color=full", &args) ||
149            has_flag("color=truecolor", &args)
150        {
151            return Some(ColorSupportLevel::TrueColor);
152        }
153        if has_flag("color=256", &args) {
154            return Some(ColorSupportLevel::Colors256);
155        }
156    }
157
158    if !options.is_tty && force_color.is_none() {
159        return Some(ColorSupportLevel::NoColor);
160    }
161
162    let environment = Environment::default();
163    Some(environment.determine_color_level())
164}
165
166/// Unit Tests
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn test_color_support_level_from_u32() {
174        assert_eq!(ColorSupportLevel::from_u32(0), Some(ColorSupportLevel::NoColor));
175        assert_eq!(ColorSupportLevel::from_u32(1), Some(ColorSupportLevel::Basic));
176        assert_eq!(ColorSupportLevel::from_u32(2), Some(ColorSupportLevel::Colors256));
177        assert_eq!(ColorSupportLevel::from_u32(3), Some(ColorSupportLevel::TrueColor));
178        assert_eq!(ColorSupportLevel::from_u32(4), None);
179    }
180
181    #[test]
182    fn test_color_info_new() {
183        let color_info = ColorInfo::new(ColorSupportLevel::Basic);
184        assert_eq!(color_info.level, ColorSupportLevel::Basic);
185        assert_eq!(color_info.has_basic, true);
186        assert_eq!(color_info.has_256, false);
187        assert_eq!(color_info.has_16m, false);
188    }
189
190    #[test]
191    fn test_color_support_stderr() {
192        // As we don't have control over the actual terminal, we'll just test if the function runs without error
193        let _ = ColorSupport::stderr();
194    }
195
196    #[test]
197    fn test_determine_stream_color_level() {
198        // As we don't have control over the actual terminal, we'll just test if the function runs without error
199        let _ = determine_stream_color_level(OutputStreamOptions::new(Some(false), None));
200    }
201
202    /// Tests the detection of color support for standard output stream.
203    #[test]
204    fn test_color_support_stdout() {
205        // As we don't have control over the actual terminal, we'll just test if the function runs without error
206        let _ = ColorSupport::stdout();
207    }
208
209    /// Tests if ColorSupportLevel enum variants are comparable for equality.
210    #[test]
211    fn test_color_support_level_equality() {
212        assert_eq!(ColorSupportLevel::NoColor, ColorSupportLevel::NoColor);
213        assert_eq!(ColorSupportLevel::Basic, ColorSupportLevel::Basic);
214        assert_eq!(ColorSupportLevel::Colors256, ColorSupportLevel::Colors256);
215        assert_eq!(ColorSupportLevel::TrueColor, ColorSupportLevel::TrueColor);
216    }
217
218    /// Tests the equality of ColorInfo instances.
219    #[test]
220    fn test_color_info_equality() {
221        let color_info1 = ColorInfo::new(ColorSupportLevel::Basic);
222        let color_info2 = ColorInfo::new(ColorSupportLevel::Basic);
223        assert_eq!(color_info1, color_info2);
224    }
225
226    /// Tests if ColorInfo instances with different color support levels are not equal.
227    #[test]
228    fn test_color_info_inequality() {
229        let color_info1 = ColorInfo::new(ColorSupportLevel::Basic);
230        let color_info2 = ColorInfo::new(ColorSupportLevel::TrueColor);
231        assert_ne!(color_info1, color_info2);
232    }
233}