| 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
