1 | //!Coloring terminal so simple, you already know how to do it ! |
2 | //! |
3 | //! use colored::Colorize; |
4 | //! |
5 | //! "this is blue".blue(); |
6 | //! "this is red".red(); |
7 | //! "this is red on blue".red().on_blue(); |
8 | //! "this is also red on blue".on_blue().red(); |
9 | //! "you can use truecolor values too!".truecolor(0, 255, 136); |
10 | //! "background truecolor also works :)".on_truecolor(135, 28, 167); |
11 | //! "you can also make bold comments".bold(); |
12 | //! println!("{} {} {}", "or use".cyan(), "any".italic().yellow(), "string type".cyan()); |
13 | //! "or change advice. This is red".yellow().blue().red(); |
14 | //! "or clear things up. This is default color and style".red().bold().clear(); |
15 | //! "purple and magenta are the same".purple().magenta(); |
16 | //! "bright colors are also allowed".bright_blue().on_bright_white(); |
17 | //! "you can specify color by string".color("blue").on_color("red"); |
18 | //! "and so are normal and clear".normal().clear(); |
19 | //! String::from("this also works!").green().bold(); |
20 | //! format!("{:30}", "format works as expected. This will be padded".blue()); |
21 | //! format!("{:.3}", "and this will be green but truncated to 3 chars".green()); |
22 | //! |
23 | //! |
24 | //! See [the `Colorize` trait](./trait.Colorize.html) for all the methods. |
25 | //! |
26 | #![warn (missing_docs)] |
27 | |
28 | extern crate is_terminal; |
29 | #[macro_use ] |
30 | extern crate lazy_static; |
31 | |
32 | #[cfg (test)] |
33 | extern crate rspec; |
34 | |
35 | mod color; |
36 | pub mod control; |
37 | mod style; |
38 | |
39 | pub use self::customcolors::CustomColor; |
40 | |
41 | /// Custom colors support. |
42 | pub mod customcolors; |
43 | |
44 | pub use color::*; |
45 | |
46 | use std::{borrow::Cow, fmt, ops::Deref}; |
47 | |
48 | pub use style::{Style, Styles}; |
49 | |
50 | /// A string that may have color and/or style applied to it. |
51 | #[derive (Clone, Debug, PartialEq, Eq)] |
52 | pub struct ColoredString { |
53 | input: String, |
54 | fgcolor: Option<Color>, |
55 | bgcolor: Option<Color>, |
56 | style: style::Style, |
57 | } |
58 | |
59 | /// The trait that enables something to be given color. |
60 | /// |
61 | /// You can use `colored` effectively simply by importing this trait |
62 | /// and then using its methods on `String` and `&str`. |
63 | #[allow (missing_docs)] |
64 | pub trait Colorize { |
65 | // Font Colors |
66 | fn black(self) -> ColoredString |
67 | where |
68 | Self: Sized, |
69 | { |
70 | self.color(Color::Black) |
71 | } |
72 | fn red(self) -> ColoredString |
73 | where |
74 | Self: Sized, |
75 | { |
76 | self.color(Color::Red) |
77 | } |
78 | fn green(self) -> ColoredString |
79 | where |
80 | Self: Sized, |
81 | { |
82 | self.color(Color::Green) |
83 | } |
84 | fn yellow(self) -> ColoredString |
85 | where |
86 | Self: Sized, |
87 | { |
88 | self.color(Color::Yellow) |
89 | } |
90 | fn blue(self) -> ColoredString |
91 | where |
92 | Self: Sized, |
93 | { |
94 | self.color(Color::Blue) |
95 | } |
96 | fn magenta(self) -> ColoredString |
97 | where |
98 | Self: Sized, |
99 | { |
100 | self.color(Color::Magenta) |
101 | } |
102 | fn purple(self) -> ColoredString |
103 | where |
104 | Self: Sized, |
105 | { |
106 | self.color(Color::Magenta) |
107 | } |
108 | fn cyan(self) -> ColoredString |
109 | where |
110 | Self: Sized, |
111 | { |
112 | self.color(Color::Cyan) |
113 | } |
114 | fn white(self) -> ColoredString |
115 | where |
116 | Self: Sized, |
117 | { |
118 | self.color(Color::White) |
119 | } |
120 | fn bright_black(self) -> ColoredString |
121 | where |
122 | Self: Sized, |
123 | { |
124 | self.color(Color::BrightBlack) |
125 | } |
126 | fn bright_red(self) -> ColoredString |
127 | where |
128 | Self: Sized, |
129 | { |
130 | self.color(Color::BrightRed) |
131 | } |
132 | fn bright_green(self) -> ColoredString |
133 | where |
134 | Self: Sized, |
135 | { |
136 | self.color(Color::BrightGreen) |
137 | } |
138 | fn bright_yellow(self) -> ColoredString |
139 | where |
140 | Self: Sized, |
141 | { |
142 | self.color(Color::BrightYellow) |
143 | } |
144 | fn bright_blue(self) -> ColoredString |
145 | where |
146 | Self: Sized, |
147 | { |
148 | self.color(Color::BrightBlue) |
149 | } |
150 | fn bright_magenta(self) -> ColoredString |
151 | where |
152 | Self: Sized, |
153 | { |
154 | self.color(Color::BrightMagenta) |
155 | } |
156 | fn bright_purple(self) -> ColoredString |
157 | where |
158 | Self: Sized, |
159 | { |
160 | self.color(Color::BrightMagenta) |
161 | } |
162 | fn bright_cyan(self) -> ColoredString |
163 | where |
164 | Self: Sized, |
165 | { |
166 | self.color(Color::BrightCyan) |
167 | } |
168 | fn bright_white(self) -> ColoredString |
169 | where |
170 | Self: Sized, |
171 | { |
172 | self.color(Color::BrightWhite) |
173 | } |
174 | fn truecolor(self, r: u8, g: u8, b: u8) -> ColoredString |
175 | where |
176 | Self: Sized, |
177 | { |
178 | self.color(Color::TrueColor { r, g, b }) |
179 | } |
180 | fn custom_color(self, color: CustomColor) -> ColoredString |
181 | where |
182 | Self: Sized, |
183 | { |
184 | self.color(Color::TrueColor { |
185 | r: color.r, |
186 | g: color.g, |
187 | b: color.b, |
188 | }) |
189 | } |
190 | fn color<S: Into<Color>>(self, color: S) -> ColoredString; |
191 | // Background Colors |
192 | fn on_black(self) -> ColoredString |
193 | where |
194 | Self: Sized, |
195 | { |
196 | self.on_color(Color::Black) |
197 | } |
198 | fn on_red(self) -> ColoredString |
199 | where |
200 | Self: Sized, |
201 | { |
202 | self.on_color(Color::Red) |
203 | } |
204 | fn on_green(self) -> ColoredString |
205 | where |
206 | Self: Sized, |
207 | { |
208 | self.on_color(Color::Green) |
209 | } |
210 | fn on_yellow(self) -> ColoredString |
211 | where |
212 | Self: Sized, |
213 | { |
214 | self.on_color(Color::Yellow) |
215 | } |
216 | fn on_blue(self) -> ColoredString |
217 | where |
218 | Self: Sized, |
219 | { |
220 | self.on_color(Color::Blue) |
221 | } |
222 | fn on_magenta(self) -> ColoredString |
223 | where |
224 | Self: Sized, |
225 | { |
226 | self.on_color(Color::Magenta) |
227 | } |
228 | fn on_purple(self) -> ColoredString |
229 | where |
230 | Self: Sized, |
231 | { |
232 | self.on_color(Color::Magenta) |
233 | } |
234 | fn on_cyan(self) -> ColoredString |
235 | where |
236 | Self: Sized, |
237 | { |
238 | self.on_color(Color::Cyan) |
239 | } |
240 | fn on_white(self) -> ColoredString |
241 | where |
242 | Self: Sized, |
243 | { |
244 | self.on_color(Color::White) |
245 | } |
246 | fn on_bright_black(self) -> ColoredString |
247 | where |
248 | Self: Sized, |
249 | { |
250 | self.on_color(Color::BrightBlack) |
251 | } |
252 | fn on_bright_red(self) -> ColoredString |
253 | where |
254 | Self: Sized, |
255 | { |
256 | self.on_color(Color::BrightRed) |
257 | } |
258 | fn on_bright_green(self) -> ColoredString |
259 | where |
260 | Self: Sized, |
261 | { |
262 | self.on_color(Color::BrightGreen) |
263 | } |
264 | fn on_bright_yellow(self) -> ColoredString |
265 | where |
266 | Self: Sized, |
267 | { |
268 | self.on_color(Color::BrightYellow) |
269 | } |
270 | fn on_bright_blue(self) -> ColoredString |
271 | where |
272 | Self: Sized, |
273 | { |
274 | self.on_color(Color::BrightBlue) |
275 | } |
276 | fn on_bright_magenta(self) -> ColoredString |
277 | where |
278 | Self: Sized, |
279 | { |
280 | self.on_color(Color::BrightMagenta) |
281 | } |
282 | fn on_bright_purple(self) -> ColoredString |
283 | where |
284 | Self: Sized, |
285 | { |
286 | self.on_color(Color::BrightMagenta) |
287 | } |
288 | fn on_bright_cyan(self) -> ColoredString |
289 | where |
290 | Self: Sized, |
291 | { |
292 | self.on_color(Color::BrightCyan) |
293 | } |
294 | fn on_bright_white(self) -> ColoredString |
295 | where |
296 | Self: Sized, |
297 | { |
298 | self.on_color(Color::BrightWhite) |
299 | } |
300 | fn on_truecolor(self, r: u8, g: u8, b: u8) -> ColoredString |
301 | where |
302 | Self: Sized, |
303 | { |
304 | self.on_color(Color::TrueColor { r, g, b }) |
305 | } |
306 | fn on_custom_color(self, color: CustomColor) -> ColoredString |
307 | where |
308 | Self: Sized, |
309 | { |
310 | self.on_color(Color::TrueColor { |
311 | r: color.r, |
312 | g: color.g, |
313 | b: color.b, |
314 | }) |
315 | } |
316 | fn on_color<S: Into<Color>>(self, color: S) -> ColoredString; |
317 | // Styles |
318 | fn clear(self) -> ColoredString; |
319 | fn normal(self) -> ColoredString; |
320 | fn bold(self) -> ColoredString; |
321 | fn dimmed(self) -> ColoredString; |
322 | fn italic(self) -> ColoredString; |
323 | fn underline(self) -> ColoredString; |
324 | fn blink(self) -> ColoredString; |
325 | #[deprecated (since = "1.5.2" , note = "Users should use reversed instead" )] |
326 | fn reverse(self) -> ColoredString; |
327 | fn reversed(self) -> ColoredString; |
328 | fn hidden(self) -> ColoredString; |
329 | fn strikethrough(self) -> ColoredString; |
330 | } |
331 | |
332 | impl ColoredString { |
333 | /// Get the current background color applied. |
334 | /// |
335 | /// ```rust |
336 | /// # use colored::*; |
337 | /// let cstr = "" .blue(); |
338 | /// assert_eq!(cstr.fgcolor(), Some(Color::Blue)); |
339 | /// let cstr = cstr.clear(); |
340 | /// assert_eq!(cstr.fgcolor(), None); |
341 | /// ``` |
342 | pub fn fgcolor(&self) -> Option<Color> { |
343 | self.fgcolor.as_ref().copied() |
344 | } |
345 | |
346 | /// Get the current background color applied. |
347 | /// |
348 | /// ```rust |
349 | /// # use colored::*; |
350 | /// let cstr = "" .on_blue(); |
351 | /// assert_eq!(cstr.bgcolor(), Some(Color::Blue)); |
352 | /// let cstr = cstr.clear(); |
353 | /// assert_eq!(cstr.bgcolor(), None); |
354 | /// ``` |
355 | pub fn bgcolor(&self) -> Option<Color> { |
356 | self.bgcolor.as_ref().copied() |
357 | } |
358 | |
359 | /// Get the current [`Style`] which can be check if it contains a [`Styles`]. |
360 | /// |
361 | /// ```rust |
362 | /// # use colored::*; |
363 | /// let colored = "" .bold().italic(); |
364 | /// assert_eq!(colored.style().contains(Styles::Bold), true); |
365 | /// assert_eq!(colored.style().contains(Styles::Italic), true); |
366 | /// assert_eq!(colored.style().contains(Styles::Dimmed), false); |
367 | /// ``` |
368 | pub fn style(&self) -> style::Style { |
369 | self.style |
370 | } |
371 | |
372 | /// Checks if the colored string has no color or styling. |
373 | /// |
374 | /// ```rust |
375 | /// # use colored::*; |
376 | /// let cstr = "" .red(); |
377 | /// assert_eq!(cstr.is_plain(), false); |
378 | /// let cstr = cstr.clear(); |
379 | /// assert_eq!(cstr.is_plain(), true); |
380 | /// ``` |
381 | pub fn is_plain(&self) -> bool { |
382 | self.bgcolor.is_none() && self.fgcolor.is_none() && self.style == style::CLEAR |
383 | } |
384 | |
385 | #[cfg (not(feature = "no-color" ))] |
386 | fn has_colors(&self) -> bool { |
387 | control::SHOULD_COLORIZE.should_colorize() |
388 | } |
389 | |
390 | #[cfg (feature = "no-color" )] |
391 | fn has_colors(&self) -> bool { |
392 | false |
393 | } |
394 | |
395 | fn compute_style(&self) -> String { |
396 | if !self.has_colors() || self.is_plain() { |
397 | return String::new(); |
398 | } |
399 | |
400 | let mut res = String::from(" \x1B[" ); |
401 | let mut has_wrote = if self.style != style::CLEAR { |
402 | res.push_str(&self.style.to_str()); |
403 | true |
404 | } else { |
405 | false |
406 | }; |
407 | |
408 | if let Some(ref bgcolor) = self.bgcolor { |
409 | if has_wrote { |
410 | res.push(';' ); |
411 | } |
412 | |
413 | res.push_str(&bgcolor.to_bg_str()); |
414 | has_wrote = true; |
415 | } |
416 | |
417 | if let Some(ref fgcolor) = self.fgcolor { |
418 | if has_wrote { |
419 | res.push(';' ); |
420 | } |
421 | |
422 | res.push_str(&fgcolor.to_fg_str()); |
423 | } |
424 | |
425 | res.push('m' ); |
426 | res |
427 | } |
428 | |
429 | fn escape_inner_reset_sequences(&self) -> Cow<str> { |
430 | if !self.has_colors() || self.is_plain() { |
431 | return self.input.as_str().into(); |
432 | } |
433 | |
434 | // TODO: BoyScoutRule |
435 | let reset = " \x1B[0m" ; |
436 | let style = self.compute_style(); |
437 | let matches: Vec<usize> = self |
438 | .input |
439 | .match_indices(reset) |
440 | .map(|(idx, _)| idx) |
441 | .collect(); |
442 | if matches.is_empty() { |
443 | return self.input.as_str().into(); |
444 | } |
445 | |
446 | let mut input = self.input.clone(); |
447 | input.reserve(matches.len() * style.len()); |
448 | |
449 | for (idx_in_matches, offset) in matches.into_iter().enumerate() { |
450 | // shift the offset to the end of the reset sequence and take in account |
451 | // the number of matches we have escaped (which shift the index to insert) |
452 | let mut offset = offset + reset.len() + idx_in_matches * style.len(); |
453 | |
454 | for cchar in style.chars() { |
455 | input.insert(offset, cchar); |
456 | offset += 1; |
457 | } |
458 | } |
459 | |
460 | input.into() |
461 | } |
462 | } |
463 | |
464 | impl Default for ColoredString { |
465 | fn default() -> Self { |
466 | ColoredString { |
467 | input: String::default(), |
468 | fgcolor: None, |
469 | bgcolor: None, |
470 | style: style::CLEAR, |
471 | } |
472 | } |
473 | } |
474 | |
475 | impl Deref for ColoredString { |
476 | type Target = str; |
477 | fn deref(&self) -> &str { |
478 | &self.input |
479 | } |
480 | } |
481 | |
482 | impl<'a> From<&'a str> for ColoredString { |
483 | fn from(s: &'a str) -> Self { |
484 | ColoredString { |
485 | input: String::from(s), |
486 | ..ColoredString::default() |
487 | } |
488 | } |
489 | } |
490 | |
491 | impl Colorize for ColoredString { |
492 | fn color<S: Into<Color>>(mut self, color: S) -> ColoredString { |
493 | self.fgcolor = Some(color.into()); |
494 | self |
495 | } |
496 | fn on_color<S: Into<Color>>(mut self, color: S) -> ColoredString { |
497 | self.bgcolor = Some(color.into()); |
498 | self |
499 | } |
500 | |
501 | fn clear(self) -> ColoredString { |
502 | ColoredString { |
503 | input: self.input, |
504 | ..ColoredString::default() |
505 | } |
506 | } |
507 | fn normal(self) -> ColoredString { |
508 | self.clear() |
509 | } |
510 | fn bold(mut self) -> ColoredString { |
511 | self.style.add(style::Styles::Bold); |
512 | self |
513 | } |
514 | fn dimmed(mut self) -> ColoredString { |
515 | self.style.add(style::Styles::Dimmed); |
516 | self |
517 | } |
518 | fn italic(mut self) -> ColoredString { |
519 | self.style.add(style::Styles::Italic); |
520 | self |
521 | } |
522 | fn underline(mut self) -> ColoredString { |
523 | self.style.add(style::Styles::Underline); |
524 | self |
525 | } |
526 | fn blink(mut self) -> ColoredString { |
527 | self.style.add(style::Styles::Blink); |
528 | self |
529 | } |
530 | fn reverse(self) -> ColoredString { |
531 | self.reversed() |
532 | } |
533 | fn reversed(mut self) -> ColoredString { |
534 | self.style.add(style::Styles::Reversed); |
535 | self |
536 | } |
537 | fn hidden(mut self) -> ColoredString { |
538 | self.style.add(style::Styles::Hidden); |
539 | self |
540 | } |
541 | fn strikethrough(mut self) -> ColoredString { |
542 | self.style.add(style::Styles::Strikethrough); |
543 | self |
544 | } |
545 | } |
546 | |
547 | impl<'a> Colorize for &'a str { |
548 | fn color<S: Into<Color>>(self, color: S) -> ColoredString { |
549 | ColoredString { |
550 | fgcolor: Some(color.into()), |
551 | input: String::from(self), |
552 | ..ColoredString::default() |
553 | } |
554 | } |
555 | |
556 | fn on_color<S: Into<Color>>(self, color: S) -> ColoredString { |
557 | ColoredString { |
558 | bgcolor: Some(color.into()), |
559 | input: String::from(self), |
560 | ..ColoredString::default() |
561 | } |
562 | } |
563 | |
564 | fn clear(self) -> ColoredString { |
565 | ColoredString { |
566 | input: String::from(self), |
567 | style: style::CLEAR, |
568 | ..ColoredString::default() |
569 | } |
570 | } |
571 | fn normal(self) -> ColoredString { |
572 | self.clear() |
573 | } |
574 | fn bold(self) -> ColoredString { |
575 | ColoredString::from(self).bold() |
576 | } |
577 | fn dimmed(self) -> ColoredString { |
578 | ColoredString::from(self).dimmed() |
579 | } |
580 | fn italic(self) -> ColoredString { |
581 | ColoredString::from(self).italic() |
582 | } |
583 | fn underline(self) -> ColoredString { |
584 | ColoredString::from(self).underline() |
585 | } |
586 | fn blink(self) -> ColoredString { |
587 | ColoredString::from(self).blink() |
588 | } |
589 | fn reverse(self) -> ColoredString { |
590 | self.reversed() |
591 | } |
592 | fn reversed(self) -> ColoredString { |
593 | ColoredString::from(self).reversed() |
594 | } |
595 | fn hidden(self) -> ColoredString { |
596 | ColoredString::from(self).hidden() |
597 | } |
598 | fn strikethrough(self) -> ColoredString { |
599 | ColoredString::from(self).strikethrough() |
600 | } |
601 | } |
602 | |
603 | impl fmt::Display for ColoredString { |
604 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
605 | if !self.has_colors() || self.is_plain() { |
606 | return <String as fmt::Display>::fmt(&self.input, f); |
607 | } |
608 | |
609 | // XXX: see tests. Useful when nesting colored strings |
610 | let escaped_input: Cow<'_, str> = self.escape_inner_reset_sequences(); |
611 | |
612 | f.write_str(&self.compute_style())?; |
613 | escaped_input.fmt(f)?; |
614 | f.write_str(data:" \x1B[0m" )?; |
615 | Ok(()) |
616 | } |
617 | } |
618 | |
619 | #[cfg (test)] |
620 | mod tests { |
621 | use super::*; |
622 | |
623 | #[test ] |
624 | fn formatting() { |
625 | // respect the formatting. Escape sequence add some padding so >= 40 |
626 | assert!(format!(" {:40}" , "" .blue()).len() >= 40); |
627 | // both should be truncated to 1 char before coloring |
628 | assert_eq!( |
629 | format!(" {:1.1}" , "toto" .blue()).len(), |
630 | format!(" {:1.1}" , "1" .blue()).len() |
631 | ) |
632 | } |
633 | |
634 | #[test ] |
635 | fn it_works() { |
636 | let toto = "toto" ; |
637 | println!(" {}" , toto.red()); |
638 | println!(" {}" , String::from(toto).red()); |
639 | println!(" {}" , toto.blue()); |
640 | |
641 | println!("blue style ****" ); |
642 | println!(" {}" , toto.bold()); |
643 | println!(" {}" , "yeah ! Red bold !" .red().bold()); |
644 | println!(" {}" , "yeah ! Yellow bold !" .bold().yellow()); |
645 | println!(" {}" , toto.bold().blue()); |
646 | println!(" {}" , toto.blue().bold()); |
647 | println!(" {}" , toto.blue().bold().underline()); |
648 | println!(" {}" , toto.blue().italic()); |
649 | println!("******" ); |
650 | println!("test clearing" ); |
651 | println!(" {}" , "red cleared" .red().clear()); |
652 | println!(" {}" , "bold cyan cleared" .bold().cyan().clear()); |
653 | println!("******" ); |
654 | println!("Bg tests" ); |
655 | println!(" {}" , toto.green().on_blue()); |
656 | println!(" {}" , toto.on_magenta().yellow()); |
657 | println!(" {}" , toto.purple().on_yellow()); |
658 | println!(" {}" , toto.magenta().on_white()); |
659 | println!(" {}" , toto.cyan().on_green()); |
660 | println!(" {}" , toto.black().on_white()); |
661 | println!("******" ); |
662 | println!(" {}" , toto.green()); |
663 | println!(" {}" , toto.yellow()); |
664 | println!(" {}" , toto.purple()); |
665 | println!(" {}" , toto.magenta()); |
666 | println!(" {}" , toto.cyan()); |
667 | println!(" {}" , toto.white()); |
668 | println!(" {}" , toto.white().red().blue().green()); |
669 | println!(" {}" , toto.truecolor(255, 0, 0)); |
670 | println!(" {}" , toto.truecolor(255, 255, 0)); |
671 | println!(" {}" , toto.on_truecolor(0, 80, 80)); |
672 | // uncomment to see term output |
673 | // assert!(false) |
674 | } |
675 | |
676 | #[test ] |
677 | fn compute_style_empty_string() { |
678 | assert_eq!("" , "" .clear().compute_style()); |
679 | } |
680 | |
681 | #[cfg_attr (feature = "no-color" , ignore)] |
682 | #[test ] |
683 | fn compute_style_simple_fg_blue() { |
684 | let blue = " \x1B[34m" ; |
685 | |
686 | assert_eq!(blue, "" .blue().compute_style()); |
687 | } |
688 | |
689 | #[cfg_attr (feature = "no-color" , ignore)] |
690 | #[test ] |
691 | fn compute_style_simple_bg_blue() { |
692 | let on_blue = " \x1B[44m" ; |
693 | |
694 | assert_eq!(on_blue, "" .on_blue().compute_style()); |
695 | } |
696 | |
697 | #[cfg_attr (feature = "no-color" , ignore)] |
698 | #[test ] |
699 | fn compute_style_blue_on_blue() { |
700 | let blue_on_blue = " \x1B[44;34m" ; |
701 | |
702 | assert_eq!(blue_on_blue, "" .blue().on_blue().compute_style()); |
703 | } |
704 | |
705 | #[cfg_attr (feature = "no-color" , ignore)] |
706 | #[test ] |
707 | fn compute_style_simple_fg_bright_blue() { |
708 | let blue = " \x1B[94m" ; |
709 | |
710 | assert_eq!(blue, "" .bright_blue().compute_style()); |
711 | } |
712 | |
713 | #[cfg_attr (feature = "no-color" , ignore)] |
714 | #[test ] |
715 | fn compute_style_simple_bg_bright_blue() { |
716 | let on_blue = " \x1B[104m" ; |
717 | |
718 | assert_eq!(on_blue, "" .on_bright_blue().compute_style()); |
719 | } |
720 | |
721 | #[cfg_attr (feature = "no-color" , ignore)] |
722 | #[test ] |
723 | fn compute_style_bright_blue_on_bright_blue() { |
724 | let blue_on_blue = " \x1B[104;94m" ; |
725 | |
726 | assert_eq!( |
727 | blue_on_blue, |
728 | "" .bright_blue().on_bright_blue().compute_style() |
729 | ); |
730 | } |
731 | |
732 | #[cfg_attr (feature = "no-color" , ignore)] |
733 | #[test ] |
734 | fn compute_style_simple_bold() { |
735 | let bold = " \x1B[1m" ; |
736 | |
737 | assert_eq!(bold, "" .bold().compute_style()); |
738 | } |
739 | |
740 | #[cfg_attr (feature = "no-color" , ignore)] |
741 | #[test ] |
742 | fn compute_style_blue_bold() { |
743 | let blue_bold = " \x1B[1;34m" ; |
744 | |
745 | assert_eq!(blue_bold, "" .blue().bold().compute_style()); |
746 | } |
747 | |
748 | #[cfg_attr (feature = "no-color" , ignore)] |
749 | #[test ] |
750 | fn compute_style_blue_bold_on_blue() { |
751 | let blue_bold_on_blue = " \x1B[1;44;34m" ; |
752 | |
753 | assert_eq!( |
754 | blue_bold_on_blue, |
755 | "" .blue().bold().on_blue().compute_style() |
756 | ); |
757 | } |
758 | |
759 | #[test ] |
760 | fn escape_reset_sequence_spec_should_do_nothing_on_empty_strings() { |
761 | let style = ColoredString::default(); |
762 | let expected = String::new(); |
763 | |
764 | let output = style.escape_inner_reset_sequences(); |
765 | |
766 | assert_eq!(expected, output); |
767 | } |
768 | |
769 | #[test ] |
770 | fn escape_reset_sequence_spec_should_do_nothing_on_string_with_no_reset() { |
771 | let style = ColoredString { |
772 | input: String::from("hello world !" ), |
773 | ..ColoredString::default() |
774 | }; |
775 | |
776 | let expected = String::from("hello world !" ); |
777 | let output = style.escape_inner_reset_sequences(); |
778 | |
779 | assert_eq!(expected, output); |
780 | } |
781 | |
782 | #[cfg_attr (feature = "no-color" , ignore)] |
783 | #[test ] |
784 | fn escape_reset_sequence_spec_should_replace_inner_reset_sequence_with_current_style() { |
785 | let input = format!("start {} end" , String::from("hello world !" ).red()); |
786 | let style = input.blue(); |
787 | |
788 | let output = style.escape_inner_reset_sequences(); |
789 | let blue = " \x1B[34m" ; |
790 | let red = " \x1B[31m" ; |
791 | let reset = " \x1B[0m" ; |
792 | let expected = format!("start {}hello world ! {}{} end" , red, reset, blue); |
793 | assert_eq!(expected, output); |
794 | } |
795 | |
796 | #[cfg_attr (feature = "no-color" , ignore)] |
797 | #[test ] |
798 | fn escape_reset_sequence_spec_should_replace_multiple_inner_reset_sequences_with_current_style() |
799 | { |
800 | let italic_str = String::from("yo" ).italic(); |
801 | let input = format!( |
802 | "start 1: {} 2: {} 3: {} end" , |
803 | italic_str, italic_str, italic_str |
804 | ); |
805 | let style = input.blue(); |
806 | |
807 | let output = style.escape_inner_reset_sequences(); |
808 | let blue = " \x1B[34m" ; |
809 | let italic = " \x1B[3m" ; |
810 | let reset = " \x1B[0m" ; |
811 | let expected = format!( |
812 | "start 1: {}yo {}{} 2: {}yo {}{} 3: {}yo {}{} end" , |
813 | italic, reset, blue, italic, reset, blue, italic, reset, blue |
814 | ); |
815 | |
816 | println!("first: {}\nsecond: {}" , expected, output); |
817 | |
818 | assert_eq!(expected, output); |
819 | } |
820 | |
821 | #[test ] |
822 | fn color_fn() { |
823 | assert_eq!("blue" .blue(), "blue" .color("blue" )) |
824 | } |
825 | |
826 | #[test ] |
827 | fn on_color_fn() { |
828 | assert_eq!("blue" .on_blue(), "blue" .on_color("blue" )) |
829 | } |
830 | |
831 | #[test ] |
832 | fn bright_color_fn() { |
833 | assert_eq!("blue" .bright_blue(), "blue" .color("bright blue" )) |
834 | } |
835 | |
836 | #[test ] |
837 | fn on_bright_color_fn() { |
838 | assert_eq!("blue" .on_bright_blue(), "blue" .on_color("bright blue" )) |
839 | } |
840 | |
841 | #[test ] |
842 | fn exposing_tests() { |
843 | let cstring = "" .red(); |
844 | assert_eq!(cstring.fgcolor(), Some(Color::Red)); |
845 | assert_eq!(cstring.bgcolor(), None); |
846 | |
847 | let cstring = cstring.clear(); |
848 | assert_eq!(cstring.fgcolor(), None); |
849 | assert_eq!(cstring.bgcolor(), None); |
850 | |
851 | let cstring = cstring.blue().on_bright_yellow(); |
852 | assert_eq!(cstring.fgcolor(), Some(Color::Blue)); |
853 | assert_eq!(cstring.bgcolor(), Some(Color::BrightYellow)); |
854 | |
855 | let cstring = cstring.bold().italic(); |
856 | assert_eq!(cstring.fgcolor(), Some(Color::Blue)); |
857 | assert_eq!(cstring.bgcolor(), Some(Color::BrightYellow)); |
858 | assert_eq!(cstring.style().contains(Styles::Bold), true); |
859 | assert_eq!(cstring.style().contains(Styles::Italic), true); |
860 | assert_eq!(cstring.style().contains(Styles::Dimmed), false); |
861 | } |
862 | } |
863 | |