1use std::fmt;
9
10pub mod codes {
12 pub const RESET: &str = "\x1b[0m";
14
15 pub const BOLD: &str = "\x1b[1m";
17 pub const DIMMED: &str = "\x1b[2m";
18
19 pub const RED: &str = "\x1b[31m";
21 pub const GREEN: &str = "\x1b[32m";
22 pub const YELLOW: &str = "\x1b[33m";
23 pub const BLUE: &str = "\x1b[34m";
24 pub const MAGENTA: &str = "\x1b[35m";
25 pub const CYAN: &str = "\x1b[36m";
26 pub const WHITE: &str = "\x1b[37m";
27
28 pub const BRIGHT_RED: &str = "\x1b[91m";
30 pub const BRIGHT_GREEN: &str = "\x1b[92m";
31 pub const BRIGHT_YELLOW: &str = "\x1b[93m";
32 pub const BRIGHT_BLUE: &str = "\x1b[94m";
33 pub const BRIGHT_MAGENTA: &str = "\x1b[95m";
34 pub const BRIGHT_CYAN: &str = "\x1b[96m";
35 pub const BRIGHT_WHITE: &str = "\x1b[97m";
36
37 pub const ON_RED: &str = "\x1b[41m";
39}
40
41#[derive(Clone)]
43pub struct StyledString {
44 content: String,
45 styles: Vec<&'static str>,
46}
47
48impl StyledString {
49 fn new(content: impl Into<String>) -> Self {
50 Self { content: content.into(), styles: Vec::new() }
51 }
52
53 fn with_style(mut self, style: &'static str) -> Self {
54 self.styles.push(style);
55 self
56 }
57
58 pub fn red(self) -> Self {
60 self.with_style(codes::RED)
61 }
62 pub fn green(self) -> Self {
63 self.with_style(codes::GREEN)
64 }
65 pub fn yellow(self) -> Self {
66 self.with_style(codes::YELLOW)
67 }
68 pub fn blue(self) -> Self {
69 self.with_style(codes::BLUE)
70 }
71 pub fn magenta(self) -> Self {
72 self.with_style(codes::MAGENTA)
73 }
74 pub fn cyan(self) -> Self {
75 self.with_style(codes::CYAN)
76 }
77 pub fn white(self) -> Self {
78 self.with_style(codes::WHITE)
79 }
80
81 pub fn bright_red(self) -> Self {
83 self.with_style(codes::BRIGHT_RED)
84 }
85 pub fn bright_green(self) -> Self {
86 self.with_style(codes::BRIGHT_GREEN)
87 }
88 pub fn bright_yellow(self) -> Self {
89 self.with_style(codes::BRIGHT_YELLOW)
90 }
91 pub fn bright_blue(self) -> Self {
92 self.with_style(codes::BRIGHT_BLUE)
93 }
94 pub fn bright_magenta(self) -> Self {
95 self.with_style(codes::BRIGHT_MAGENTA)
96 }
97 pub fn bright_cyan(self) -> Self {
98 self.with_style(codes::BRIGHT_CYAN)
99 }
100 pub fn bright_white(self) -> Self {
101 self.with_style(codes::BRIGHT_WHITE)
102 }
103
104 pub fn on_red(self) -> Self {
106 self.with_style(codes::ON_RED)
107 }
108
109 pub fn bold(self) -> Self {
111 self.with_style(codes::BOLD)
112 }
113 pub fn dimmed(self) -> Self {
114 self.with_style(codes::DIMMED)
115 }
116}
117
118impl fmt::Display for StyledString {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 for style in &self.styles {
122 write!(f, "{}", style)?;
123 }
124 write!(f, "{}", self.content)?;
126 write!(f, "{}", codes::RESET)
128 }
129}
130
131pub trait Colorize {
134 fn to_styled(&self) -> StyledString;
135
136 fn red(&self) -> StyledString {
138 self.to_styled().red()
139 }
140 fn green(&self) -> StyledString {
141 self.to_styled().green()
142 }
143 fn yellow(&self) -> StyledString {
144 self.to_styled().yellow()
145 }
146 fn blue(&self) -> StyledString {
147 self.to_styled().blue()
148 }
149 fn magenta(&self) -> StyledString {
150 self.to_styled().magenta()
151 }
152 fn cyan(&self) -> StyledString {
153 self.to_styled().cyan()
154 }
155 fn white(&self) -> StyledString {
156 self.to_styled().white()
157 }
158
159 fn bright_red(&self) -> StyledString {
161 self.to_styled().bright_red()
162 }
163 fn bright_green(&self) -> StyledString {
164 self.to_styled().bright_green()
165 }
166 fn bright_yellow(&self) -> StyledString {
167 self.to_styled().bright_yellow()
168 }
169 fn bright_blue(&self) -> StyledString {
170 self.to_styled().bright_blue()
171 }
172 fn bright_magenta(&self) -> StyledString {
173 self.to_styled().bright_magenta()
174 }
175 fn bright_cyan(&self) -> StyledString {
176 self.to_styled().bright_cyan()
177 }
178 fn bright_white(&self) -> StyledString {
179 self.to_styled().bright_white()
180 }
181
182 fn on_red(&self) -> StyledString {
184 self.to_styled().on_red()
185 }
186
187 fn bold(&self) -> StyledString {
189 self.to_styled().bold()
190 }
191 fn dimmed(&self) -> StyledString {
192 self.to_styled().dimmed()
193 }
194}
195
196impl Colorize for str {
197 fn to_styled(&self) -> StyledString {
198 StyledString::new(self)
199 }
200}
201
202impl Colorize for String {
203 fn to_styled(&self) -> StyledString {
204 StyledString::new(self.as_str())
205 }
206}
207
208impl Colorize for StyledString {
209 fn to_styled(&self) -> StyledString {
210 self.clone()
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_basic_color() {
220 let s = "hello".red();
221 assert!(s.to_string().contains("\x1b[31m"));
222 assert!(s.to_string().contains("hello"));
223 assert!(s.to_string().contains("\x1b[0m"));
224 }
225
226 #[test]
227 fn test_chained_styles() {
228 let s = "hello".bright_cyan().bold();
229 let output = s.to_string();
230 assert!(output.contains("\x1b[96m")); assert!(output.contains("\x1b[1m")); assert!(output.contains("hello"));
233 assert!(output.contains("\x1b[0m")); }
235
236 #[test]
237 fn test_string_owned() {
238 let s = String::from("world").green().dimmed();
239 assert!(s.to_string().contains("\x1b[32m"));
240 assert!(s.to_string().contains("\x1b[2m"));
241 }
242
243 #[test]
244 fn test_borrowed_string() {
245 let owned = String::from("borrowed");
246 let s = owned.yellow(); assert!(s.to_string().contains("\x1b[33m"));
248 assert_eq!(owned, "borrowed");
250 }
251
252 #[test]
253 fn test_all_standard_colors() {
254 assert!("x".red().to_string().contains(codes::RED));
255 assert!("x".green().to_string().contains(codes::GREEN));
256 assert!("x".yellow().to_string().contains(codes::YELLOW));
257 assert!("x".blue().to_string().contains(codes::BLUE));
258 assert!("x".magenta().to_string().contains(codes::MAGENTA));
259 assert!("x".cyan().to_string().contains(codes::CYAN));
260 assert!("x".white().to_string().contains(codes::WHITE));
261 }
262
263 #[test]
264 fn test_all_bright_colors() {
265 assert!("x".bright_red().to_string().contains(codes::BRIGHT_RED));
266 assert!("x".bright_green().to_string().contains(codes::BRIGHT_GREEN));
267 assert!("x".bright_yellow().to_string().contains(codes::BRIGHT_YELLOW));
268 assert!("x".bright_blue().to_string().contains(codes::BRIGHT_BLUE));
269 assert!("x".bright_magenta().to_string().contains(codes::BRIGHT_MAGENTA));
270 assert!("x".bright_cyan().to_string().contains(codes::BRIGHT_CYAN));
271 assert!("x".bright_white().to_string().contains(codes::BRIGHT_WHITE));
272 }
273
274 #[test]
275 fn test_background_color() {
276 let s = "test".on_red();
277 assert!(s.to_string().contains(codes::ON_RED));
278 }
279
280 #[test]
281 fn test_bold_style() {
282 let s = "bold text".bold();
283 assert!(s.to_string().contains(codes::BOLD));
284 }
285
286 #[test]
287 fn test_dimmed_style() {
288 let s = "dimmed text".dimmed();
289 assert!(s.to_string().contains(codes::DIMMED));
290 }
291
292 #[test]
293 fn test_styled_string_clone() {
294 let s1 = "text".red().bold();
295 let s2 = s1.clone();
296 assert_eq!(s1.to_string(), s2.to_string());
297 }
298
299 #[test]
300 fn test_colorize_for_styled_string() {
301 let s = "text".red();
302 let s2 = s.to_styled().bold();
303 assert!(s2.to_string().contains(codes::RED));
304 assert!(s2.to_string().contains(codes::BOLD));
305 }
306
307 #[test]
308 fn test_multiple_styles_order() {
309 let s = "text".red().bold().dimmed();
310 let output = s.to_string();
311 assert!(output.contains(codes::RED));
313 assert!(output.contains(codes::BOLD));
314 assert!(output.contains(codes::DIMMED));
315 assert!(output.contains("text"));
317 assert!(output.ends_with(codes::RESET));
319 }
320
321 #[test]
322 fn test_empty_string() {
323 let s = "".red();
324 let output = s.to_string();
325 assert!(output.contains(codes::RED));
326 assert!(output.contains(codes::RESET));
327 }
328
329 #[test]
330 fn test_styled_string_display() {
331 let s = StyledString::new("hello").with_style(codes::GREEN);
332 let output = format!("{}", s);
333 assert!(output.contains(codes::GREEN));
334 assert!(output.contains("hello"));
335 assert!(output.contains(codes::RESET));
336 }
337
338 #[test]
339 fn test_styled_string_new() {
340 let s = StyledString::new("test content");
341 assert!(s.to_string().contains("test content"));
342 }
343
344 #[test]
345 fn test_styled_string_from_string() {
346 let s = StyledString::new(String::from("owned string"));
347 assert!(s.to_string().contains("owned string"));
348 }
349
350 #[test]
351 fn test_codes_module() {
352 assert_eq!(codes::RESET, "\x1b[0m");
354 assert_eq!(codes::BOLD, "\x1b[1m");
355 assert_eq!(codes::DIMMED, "\x1b[2m");
356 assert_eq!(codes::RED, "\x1b[31m");
357 assert_eq!(codes::GREEN, "\x1b[32m");
358 assert_eq!(codes::ON_RED, "\x1b[41m");
359 }
360
361 #[test]
362 fn test_styled_string_direct_color_methods() {
363 let s = StyledString::new("test");
365 assert!(s.red().to_string().contains(codes::RED));
366
367 let s = StyledString::new("test");
368 assert!(s.green().to_string().contains(codes::GREEN));
369
370 let s = StyledString::new("test");
371 assert!(s.yellow().to_string().contains(codes::YELLOW));
372
373 let s = StyledString::new("test");
374 assert!(s.blue().to_string().contains(codes::BLUE));
375 }
376
377 #[test]
378 fn test_styled_string_direct_bright_methods() {
379 let s = StyledString::new("test");
380 assert!(s.bright_red().to_string().contains(codes::BRIGHT_RED));
381
382 let s = StyledString::new("test");
383 assert!(s.bright_green().to_string().contains(codes::BRIGHT_GREEN));
384 }
385
386 #[test]
387 fn test_styled_string_magenta() {
388 let s = StyledString::new("test").magenta();
389 assert!(s.to_string().contains(codes::MAGENTA));
390 }
391
392 #[test]
393 fn test_styled_string_cyan() {
394 let s = StyledString::new("test").cyan();
395 assert!(s.to_string().contains(codes::CYAN));
396 }
397
398 #[test]
399 fn test_styled_string_white() {
400 let s = StyledString::new("test").white();
401 assert!(s.to_string().contains(codes::WHITE));
402 }
403
404 #[test]
405 fn test_styled_string_bright_yellow() {
406 let s = StyledString::new("test").bright_yellow();
407 assert!(s.to_string().contains(codes::BRIGHT_YELLOW));
408 }
409
410 #[test]
411 fn test_styled_string_bright_blue() {
412 let s = StyledString::new("test").bright_blue();
413 assert!(s.to_string().contains(codes::BRIGHT_BLUE));
414 }
415
416 #[test]
417 fn test_styled_string_bright_magenta() {
418 let s = StyledString::new("test").bright_magenta();
419 assert!(s.to_string().contains(codes::BRIGHT_MAGENTA));
420 }
421
422 #[test]
423 fn test_styled_string_bright_cyan() {
424 let s = StyledString::new("test").bright_cyan();
425 assert!(s.to_string().contains(codes::BRIGHT_CYAN));
426 }
427
428 #[test]
429 fn test_styled_string_bright_white() {
430 let s = StyledString::new("test").bright_white();
431 assert!(s.to_string().contains(codes::BRIGHT_WHITE));
432 }
433
434 #[test]
435 fn test_styled_string_on_red() {
436 let s = StyledString::new("test").on_red();
437 assert!(s.to_string().contains(codes::ON_RED));
438 }
439
440 #[test]
441 fn test_styled_string_bold() {
442 let s = StyledString::new("test").bold();
443 assert!(s.to_string().contains(codes::BOLD));
444 }
445
446 #[test]
447 fn test_styled_string_dimmed() {
448 let s = StyledString::new("test").dimmed();
449 assert!(s.to_string().contains(codes::DIMMED));
450 }
451}