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