console_utils/
styled.rs

1//! Style Utilities
2//!
3//! This module provides functions for coloring text using ANSI escape code sequences.
4//! It allows setting foreground and background colors, as well as applying bold, italic,
5//! underline, blink, reverse and strikethrough formatting.
6
7use std::fmt;
8
9/// Represents all colors the text/background can be.
10#[derive(Debug, Clone, Copy)]
11pub enum Color {
12    /// Black color.
13    Black,
14    /// Red color.
15    Red,
16    /// Green color.
17    Green,
18    /// Yellow color.
19    Yellow,
20    /// Blue color.
21    Blue,
22    /// Magenta color.
23    Magenta,
24    /// Cyan color.
25    Cyan,
26    /// White color.
27    White,
28    /// Bright Black color.
29    BrightBlack,
30    /// Bright Red color.
31    BrightRed,
32    /// Bright Green color.
33    BrightGreen,
34    /// Bright Yellow color.
35    BrightYellow,
36    /// Bright Blue color.
37    BrightBlue,
38    /// Bright Magenta color.
39    BrightMagenta,
40    /// Bright Cyan color.
41    BrightCyan,
42    /// Bright White color.
43    BrightWhite,
44    /// An ANSI color of your choice.
45    ANSI(u8),
46}
47
48impl Color {
49    /// Converts a color to its ANSI foreground variant.
50    fn fg_code(self) -> u8 {
51        match self {
52            Color::Black => 30,
53            Color::Red => 31,
54            Color::Green => 32,
55            Color::Yellow => 33,
56            Color::Blue => 34,
57            Color::Magenta => 35,
58            Color::Cyan => 36,
59            Color::White => 37,
60            Color::BrightBlack => 90,
61            Color::BrightRed => 91,
62            Color::BrightGreen => 92,
63            Color::BrightYellow => 93,
64            Color::BrightBlue => 94,
65            Color::BrightMagenta => 95,
66            Color::BrightCyan => 96,
67            Color::BrightWhite => 97,
68            Color::ANSI(c) => c,
69        }
70    }
71
72    /// Converts a color to its ANSI background variant.
73    fn bg_code(self) -> u8 {
74        self.fg_code() + 10
75    }
76}
77
78/// Represents a piece of text with optional color and formatting.
79pub struct StyledText<'a> {
80    text: &'a str,
81    fg: Option<Color>,
82    bg: Option<Color>,
83    bold: bool,
84    italic: bool,
85    underline: bool,
86    blink: bool,
87    reverse: bool,
88    strikethrough: bool,
89}
90
91impl<'a> StyledText<'a> {
92    /// Creates a new `StyledText` instance with default settings.
93    ///
94    /// # Arguments
95    /// * `text` - The string slice representing the text.
96    ///
97    /// # Returns
98    /// A `StyledText` instance with no colors or formatting applied.
99    pub fn new(text: &'a str) -> Self {
100        Self {
101            text,
102            fg: None,
103            bg: None,
104            bold: false,
105            italic: false,
106            underline: false,
107            blink: false,
108            reverse: false,
109            strikethrough: false,
110        }
111    }
112
113    /// Sets the foreground color of the text.
114    ///
115    /// # Arguments
116    /// * `color` - A `Color` enum variant representing the desired foreground color.
117    ///
118    /// # Returns
119    /// The modified `StyledText` instance.
120    pub fn fg(mut self, color: Color) -> Self {
121        self.fg = Some(color);
122        self
123    }
124
125    /// Sets the background color of the text.
126    ///
127    /// # Arguments
128    /// * `color` - A `Color` enum variant representing the desired background color.
129    ///
130    /// # Returns
131    /// The modified `StyledText` instance.
132    pub fn bg(mut self, color: Color) -> Self {
133        self.bg = Some(color);
134        self
135    }
136
137    /// Enables bold formatting for the text.
138    ///
139    /// # Returns
140    /// The modified `StyledText` instance with bold formatting applied.
141    pub fn bold(mut self) -> Self {
142        self.bold = true;
143        self
144    }
145
146    /// Enables italic formatting for the text.
147    ///
148    /// # Returns
149    /// The modified `StyledText` instance with italic formatting applied.
150    pub fn italic(mut self) -> Self {
151        self.italic = true;
152        self
153    }
154
155    /// Enables underline formatting for the text.
156    ///
157    /// # Returns
158    /// The modified `StyledText` instance with underline formatting applied.
159    pub fn underline(mut self) -> Self {
160        self.underline = true;
161        self
162    }
163
164    /// Enables blink effect for the text.
165    ///
166    /// # Returns
167    /// The modified `StyledText` instance with blinking enabled.
168    pub fn blink(mut self) -> Self {
169        self.blink = true;
170        self
171    }
172
173    /// Enables reverse video (inverts foreground and background colors).
174    ///
175    /// # Returns
176    /// The modified `StyledText` instance with inverted colors.
177    pub fn reverse(mut self) -> Self {
178        self.reverse = true;
179        self
180    }
181
182    /// Enables strikethrough formatting for the text.
183    ///
184    /// # Returns
185    /// The modified `StyledText` instance with strikethrough applied.
186    pub fn strikethrough(mut self) -> Self {
187        self.strikethrough = true;
188        self
189    }
190
191    /// Converts the colored text into a formatted ANSI escape sequence string.
192    ///
193    /// # Returns
194    /// A `String` containing the ANSI-formatted text.
195    pub fn format_sequence(&'a self) -> String {
196        let mut codes = Vec::new();
197        if let Some(fg) = self.fg {
198            codes.push(fg.fg_code());
199        }
200        if let Some(bg) = self.bg {
201            codes.push(bg.bg_code());
202        }
203        if self.bold {
204            codes.push(1);
205        }
206        if self.italic {
207            codes.push(3);
208        }
209        if self.underline {
210            codes.push(4);
211        }
212        if self.blink {
213            codes.push(5);
214        }
215        if self.reverse {
216            codes.push(7);
217        }
218        if self.strikethrough {
219            codes.push(9);
220        }
221
222        let codes_str = codes
223            .iter()
224            .map(|c| c.to_string())
225            .collect::<Vec<_>>()
226            .join(";");
227
228        if !codes.is_empty() {
229            format!("\x1B[{}m{}\x1B[0m", codes_str, self.text)
230        } else {
231            self.text.to_string()
232        }
233    }
234}
235
236/// Implements the `Display` trait for `StyledText`, allowing it to be printed directly.
237impl fmt::Display for StyledText<'_> {
238    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
239        write!(f, "{}", self.format_sequence())
240    }
241}