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}