Skip to main content

flag_rs/
color.rs

1//! Simple ANSI color support with zero dependencies
2//!
3//! This module provides basic color support for terminal output without
4//! requiring any external dependencies. It automatically detects whether
5//! colors should be used based on terminal capabilities and environment.
6//!
7//! # Features
8//!
9//! - Automatic TTY detection
10//! - Respects `NO_COLOR` environment variable
11//! - Zero dependencies - uses only Rust standard library
12//! - Common ANSI colors and styles
13//!
14//! # Examples
15//!
16//! ```
17//! use flag_rs::color;
18//!
19//! // Using convenience functions
20//! println!("{}", color::red("Error: Something went wrong"));
21//! println!("{}", color::green("Success!"));
22//! println!("{}", color::bold("Important message"));
23//!
24//! // Using Style directly for more control
25//! use flag_rs::color::Style;
26//! println!("{}", Style::YELLOW.paint("Warning: Check this out"));
27//! ```
28
29use std::env;
30use std::io::{self, IsTerminal};
31
32/// ANSI style configuration
33///
34/// Represents a text style with ANSI escape codes for coloring terminal output.
35pub struct Style {
36    prefix: &'static str,
37    suffix: &'static str,
38}
39
40impl Style {
41    /// ANSI reset code to clear all styles
42    pub const RESET: &'static str = "\x1b[0m";
43
44    /// Red color style
45    pub const RED: Self = Self {
46        prefix: "\x1b[31m",
47        suffix: Self::RESET,
48    };
49
50    /// Green color style
51    pub const GREEN: Self = Self {
52        prefix: "\x1b[32m",
53        suffix: Self::RESET,
54    };
55
56    /// Yellow color style
57    pub const YELLOW: Self = Self {
58        prefix: "\x1b[33m",
59        suffix: Self::RESET,
60    };
61
62    /// Blue color style
63    pub const BLUE: Self = Self {
64        prefix: "\x1b[34m",
65        suffix: Self::RESET,
66    };
67
68    /// Magenta color style
69    pub const MAGENTA: Self = Self {
70        prefix: "\x1b[35m",
71        suffix: Self::RESET,
72    };
73
74    /// Cyan color style
75    pub const CYAN: Self = Self {
76        prefix: "\x1b[36m",
77        suffix: Self::RESET,
78    };
79
80    /// Bold text style
81    pub const BOLD: Self = Self {
82        prefix: "\x1b[1m",
83        suffix: Self::RESET,
84    };
85
86    /// Dim/faint text style
87    pub const DIM: Self = Self {
88        prefix: "\x1b[2m",
89        suffix: Self::RESET,
90    };
91
92    /// Applies this style to the given text
93    ///
94    /// The style is only applied if colors are enabled (stdout is a TTY
95    /// and `NO_COLOR` is not set).
96    ///
97    /// # Arguments
98    ///
99    /// * `text` - The text to style
100    ///
101    /// # Returns
102    ///
103    /// The styled text if colors are enabled, otherwise the original text
104    #[must_use]
105    pub fn paint(&self, text: &str) -> String {
106        if should_colorize() {
107            format!("{}{}{}", self.prefix, text, self.suffix)
108        } else {
109            text.to_string()
110        }
111    }
112}
113
114/// Determines whether output should be colorized
115///
116/// Returns `true` if:
117/// - stdout is a terminal (TTY)
118/// - `NO_COLOR` environment variable is not set
119/// - `CLICOLOR_FORCE` environment variable is set (overrides TTY check)
120///
121/// This follows the `NO_COLOR` standard: <https://no-color.org/>
122#[must_use]
123pub fn should_colorize() -> bool {
124    // Respect `NO_COLOR` environment variable
125    if env::var("NO_COLOR").is_ok() {
126        return false;
127    }
128
129    // Force color if CLICOLOR_FORCE is set
130    if env::var("CLICOLOR_FORCE").is_ok() {
131        return true;
132    }
133
134    // Check if stdout is a terminal
135    io::stdout().is_terminal()
136}
137
138/// Colors text red (typically for errors)
139#[must_use]
140pub fn red(text: &str) -> String {
141    Style::RED.paint(text)
142}
143
144/// Colors text green (typically for success)
145#[must_use]
146pub fn green(text: &str) -> String {
147    Style::GREEN.paint(text)
148}
149
150/// Colors text yellow (typically for warnings)
151#[must_use]
152pub fn yellow(text: &str) -> String {
153    Style::YELLOW.paint(text)
154}
155
156/// Colors text blue (typically for information)
157#[must_use]
158pub fn blue(text: &str) -> String {
159    Style::BLUE.paint(text)
160}
161
162/// Colors text cyan (typically for highlights)
163#[must_use]
164pub fn cyan(text: &str) -> String {
165    Style::CYAN.paint(text)
166}
167
168/// Makes text bold (typically for emphasis)
169#[must_use]
170pub fn bold(text: &str) -> String {
171    Style::BOLD.paint(text)
172}
173
174/// Makes text dim (typically for less important information)
175#[must_use]
176pub fn dim(text: &str) -> String {
177    Style::DIM.paint(text)
178}