From c7e21ec0e44a21ee4eb5fccd8bd6ceacc116707a Mon Sep 17 00:00:00 2001 From: kurtlawrence Date: Sat, 11 Jan 2020 09:12:53 +1000 Subject: [PATCH] Expose colored string (#73) * increased documentation * format using rustfmt --- examples/control.rs | 20 ++- src/color.rs | 4 +- src/control.rs | 20 ++- src/lib.rs | 260 ++++++++++++++++++++++++++++++++------ src/style.rs | 42 ++++-- tests/ansi_term_compat.rs | 5 +- 6 files changed, 283 insertions(+), 68 deletions(-) diff --git a/examples/control.rs b/examples/control.rs index 564b477..b50b426 100644 --- a/examples/control.rs +++ b/examples/control.rs @@ -14,17 +14,29 @@ fn main() { colored::control::set_virtual_terminal(true); println!("{}", "stdout: Virtual Terminal is in use".bright_green()); colored::control::set_virtual_terminal(false); - println!("{}", "stderr: Virtual Terminal is NOT in use, escape chars should be visible".bright_red()); + println!( + "{}", + "stderr: Virtual Terminal is NOT in use, escape chars should be visible".bright_red() + ); colored::control::set_virtual_terminal(true); - println!("{}", "stdout: Virtual Terminal is in use AGAIN and should be green!".bright_green()); + println!( + "{}", + "stdout: Virtual Terminal is in use AGAIN and should be green!".bright_green() + ); colored::control::set_virtual_terminal(true); // again with stderr eprintln!("{}", "stderr: Virtual Terminal is in use".bright_green()); colored::control::set_virtual_terminal(false); - eprintln!("{}", "stderr: Virtual Terminal is NOT in use, escape chars should be visible".bright_red()); + eprintln!( + "{}", + "stderr: Virtual Terminal is NOT in use, escape chars should be visible".bright_red() + ); colored::control::set_virtual_terminal(true); - eprintln!("{}", "stderr: Virtual Terminal is in use AGAIN and should be green!".bright_green()); + eprintln!( + "{}", + "stderr: Virtual Terminal is in use AGAIN and should be green!".bright_green() + ); } fn both() { diff --git a/src/color.rs b/src/color.rs index 517967b..79e0a83 100644 --- a/src/color.rs +++ b/src/color.rs @@ -3,6 +3,7 @@ use std::str::FromStr; /// The 8 standard colors. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[allow(missing_docs)] pub enum Color { Black, Red, @@ -21,6 +22,8 @@ pub enum Color { BrightCyan, BrightWhite, } + +#[allow(missing_docs)] impl Color { pub fn to_fg_str(&self) -> &str { match *self { @@ -206,6 +209,5 @@ mod tests { let color: Result = "bloublou".parse(); assert_eq!(Err(()), color) } - } } diff --git a/src/control.rs b/src/control.rs index ab6289a..7ad6e62 100644 --- a/src/control.rs +++ b/src/control.rs @@ -33,7 +33,7 @@ pub fn set_virtual_terminal(use_virtual: bool) -> Result<(), ()> { processenv::GetStdHandle, winbase::STD_OUTPUT_HANDLE, wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING, - } + }, }; unsafe { @@ -41,13 +41,18 @@ pub fn set_virtual_terminal(use_virtual: bool) -> Result<(), ()> { let mut original_mode: DWORD = 0; GetConsoleMode(handle, &mut original_mode); - let enabled = original_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == ENABLE_VIRTUAL_TERMINAL_PROCESSING; + let enabled = original_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING + == ENABLE_VIRTUAL_TERMINAL_PROCESSING; match (use_virtual, enabled) { // not enabled, should be enabled - (true, false) => SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING | original_mode), + (true, false) => { + SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING | original_mode) + } // already enabled, should be disabled - (false, true) => SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING ^ original_mode), + (false, true) => { + SetConsoleMode(handle, ENABLE_VIRTUAL_TERMINAL_PROCESSING ^ original_mode) + } _ => 0, }; } @@ -55,6 +60,7 @@ pub fn set_virtual_terminal(use_virtual: bool) -> Result<(), ()> { Ok(()) } +/// A flag to to if coloring should occur. pub struct ShouldColorize { clicolor: bool, clicolor_force: Option, @@ -76,6 +82,7 @@ pub fn unset_override() { } lazy_static! { +/// The persistent [`ShouldColorize`]. pub static ref SHOULD_COLORIZE: ShouldColorize = ShouldColorize::from_env(); } @@ -96,8 +103,6 @@ impl ShouldColorize { /// `CLICOLOR_FORCE` takes highest priority, followed by `NO_COLOR`, /// followed by `CLICOLOR` combined with tty check. pub fn from_env() -> Self { - use std::io; - ShouldColorize { clicolor: ShouldColorize::normalize_env(env::var("CLICOLOR")).unwrap_or_else(|| true) && atty::is(atty::Stream::Stdout), @@ -109,6 +114,7 @@ impl ShouldColorize { } } + /// Returns if the current coloring is expected. pub fn should_colorize(&self) -> bool { if self.has_manual_override.load(Ordering::Relaxed) { return self.manual_override.load(Ordering::Relaxed); @@ -121,12 +127,14 @@ impl ShouldColorize { self.clicolor } + /// Use this to force colored to ignore the environment and always/never colorize pub fn set_override(&self, override_colorize: bool) { self.has_manual_override.store(true, Ordering::Relaxed); self.manual_override .store(override_colorize, Ordering::Relaxed); } + /// Remove the manual override and let the environment decide if it's ok to colorize pub fn unset_override(&self) { self.has_manual_override.store(false, Ordering::Relaxed); } diff --git a/src/lib.rs b/src/lib.rs index ad72ff9..9f9f57d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(unused_imports, dead_code, unused_parens)] - //!Coloring terminal so simple, you already know how to do it ! //! //! use colored::Colorize; @@ -23,6 +21,7 @@ //! //! See [the `Colorize` trait](./trait.Colorize.html) for all the methods. //! +#![warn(missing_docs)] extern crate atty; #[macro_use] @@ -44,6 +43,8 @@ use std::fmt; use std::ops::Deref; use std::string::String; +pub use style::{Style, Styles}; + /// A string that may have color and/or style applied to it. #[derive(Clone, Debug, PartialEq, Eq)] pub struct ColoredString { @@ -57,116 +58,225 @@ pub struct ColoredString { /// /// You can use `colored` effectively simply by importing this trait /// and then using its methods on `String` and `&str`. +#[allow(missing_docs)] pub trait Colorize { // Font Colors - fn black(self) -> ColoredString where Self: Sized { + fn black(self) -> ColoredString + where + Self: Sized, + { self.color(Color::Black) } - fn red(self) -> ColoredString where Self: Sized { + fn red(self) -> ColoredString + where + Self: Sized, + { self.color(Color::Red) } - fn green(self) -> ColoredString where Self: Sized { + fn green(self) -> ColoredString + where + Self: Sized, + { self.color(Color::Green) } - fn yellow(self) -> ColoredString where Self: Sized { + fn yellow(self) -> ColoredString + where + Self: Sized, + { self.color(Color::Yellow) } - fn blue(self) -> ColoredString where Self: Sized { + fn blue(self) -> ColoredString + where + Self: Sized, + { self.color(Color::Blue) } - fn magenta(self) -> ColoredString where Self: Sized { + fn magenta(self) -> ColoredString + where + Self: Sized, + { self.color(Color::Magenta) } - fn purple(self) -> ColoredString where Self: Sized { + fn purple(self) -> ColoredString + where + Self: Sized, + { self.color(Color::Magenta) } - fn cyan(self) -> ColoredString where Self: Sized { + fn cyan(self) -> ColoredString + where + Self: Sized, + { self.color(Color::Cyan) } - fn white(self) -> ColoredString where Self: Sized { + fn white(self) -> ColoredString + where + Self: Sized, + { self.color(Color::White) } - fn bright_black(self) -> ColoredString where Self: Sized { + fn bright_black(self) -> ColoredString + where + Self: Sized, + { self.color(Color::BrightBlack) } - fn bright_red(self) -> ColoredString where Self: Sized { + fn bright_red(self) -> ColoredString + where + Self: Sized, + { self.color(Color::BrightRed) } - fn bright_green(self) -> ColoredString where Self: Sized { + fn bright_green(self) -> ColoredString + where + Self: Sized, + { self.color(Color::BrightGreen) } - fn bright_yellow(self) -> ColoredString where Self: Sized { + fn bright_yellow(self) -> ColoredString + where + Self: Sized, + { self.color(Color::BrightYellow) } - fn bright_blue(self) -> ColoredString where Self: Sized { + fn bright_blue(self) -> ColoredString + where + Self: Sized, + { self.color(Color::BrightBlue) } - fn bright_magenta(self) -> ColoredString where Self: Sized { + fn bright_magenta(self) -> ColoredString + where + Self: Sized, + { self.color(Color::BrightMagenta) } - fn bright_purple(self) -> ColoredString where Self: Sized { + fn bright_purple(self) -> ColoredString + where + Self: Sized, + { self.color(Color::BrightMagenta) } - fn bright_cyan(self) -> ColoredString where Self: Sized { + fn bright_cyan(self) -> ColoredString + where + Self: Sized, + { self.color(Color::BrightCyan) } - fn bright_white(self) -> ColoredString where Self: Sized { + fn bright_white(self) -> ColoredString + where + Self: Sized, + { self.color(Color::BrightWhite) } fn color>(self, color: S) -> ColoredString; // Background Colors - fn on_black(self) -> ColoredString where Self: Sized { + fn on_black(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::Black) } - fn on_red(self) -> ColoredString where Self: Sized { + fn on_red(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::Red) } - fn on_green(self) -> ColoredString where Self: Sized { + fn on_green(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::Green) } - fn on_yellow(self) -> ColoredString where Self: Sized { + fn on_yellow(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::Yellow) } - fn on_blue(self) -> ColoredString where Self: Sized { + fn on_blue(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::Blue) } - fn on_magenta(self) -> ColoredString where Self: Sized { + fn on_magenta(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::Magenta) } - fn on_purple(self) -> ColoredString where Self: Sized { + fn on_purple(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::Magenta) } - fn on_cyan(self) -> ColoredString where Self: Sized { + fn on_cyan(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::Cyan) } - fn on_white(self) -> ColoredString where Self: Sized { + fn on_white(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::White) } - fn on_bright_black(self) -> ColoredString where Self: Sized { + fn on_bright_black(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::BrightBlack) } - fn on_bright_red(self) -> ColoredString where Self: Sized { + fn on_bright_red(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::BrightRed) } - fn on_bright_green(self) -> ColoredString where Self: Sized { + fn on_bright_green(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::BrightGreen) } - fn on_bright_yellow(self) -> ColoredString where Self: Sized { + fn on_bright_yellow(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::BrightYellow) } - fn on_bright_blue(self) -> ColoredString where Self: Sized { + fn on_bright_blue(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::BrightBlue) } - fn on_bright_magenta(self) -> ColoredString where Self: Sized { + fn on_bright_magenta(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::BrightMagenta) } - fn on_bright_purple(self) -> ColoredString where Self: Sized { + fn on_bright_purple(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::BrightMagenta) } - fn on_bright_cyan(self) -> ColoredString where Self: Sized { + fn on_bright_cyan(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::BrightCyan) } - fn on_bright_white(self) -> ColoredString where Self: Sized { + fn on_bright_white(self) -> ColoredString + where + Self: Sized, + { self.on_color(Color::BrightWhite) } fn on_color>(self, color: S) -> ColoredString; @@ -188,14 +298,60 @@ pub trait Colorize { } impl ColoredString { + /// Get the current background color applied. + /// + /// ```rust + /// # use colored::*; + /// let cstr = "".blue(); + /// assert_eq!(cstr.fgcolor(), Some(Color::Blue)); + /// let cstr = cstr.clear(); + /// assert_eq!(cstr.fgcolor(), None); + /// ``` + pub fn fgcolor(&self) -> Option { + self.fgcolor.as_ref().copied() + } + + /// Get the current background color applied. + /// + /// ```rust + /// # use colored::*; + /// let cstr = "".on_blue(); + /// assert_eq!(cstr.bgcolor(), Some(Color::Blue)); + /// let cstr = cstr.clear(); + /// assert_eq!(cstr.bgcolor(), None); + /// ``` + pub fn bgcolor(&self) -> Option { + self.bgcolor.as_ref().copied() + } + + /// Get the current [`Style`] which can be check if it contains a [`Styles`]. + /// + /// ```rust + /// # use colored::*; + /// let colored = "".bold().italic(); + /// assert_eq!(colored.style().contains(Styles::Bold), true); + /// assert_eq!(colored.style().contains(Styles::Italic), true); + /// assert_eq!(colored.style().contains(Styles::Dimmed), false); + /// ``` + pub fn style(&self) -> style::Style { + self.style + } + + /// Checks if the colored string has no color or styling. + /// + /// ```rust + /// # use colored::*; + /// let cstr = "".red(); + /// assert_eq!(cstr.is_plain(), false); + /// let cstr = cstr.clear(); + /// assert_eq!(cstr.is_plain(), true); + /// ``` pub fn is_plain(&self) -> bool { (self.bgcolor.is_none() && self.fgcolor.is_none() && self.style == style::CLEAR) } #[cfg(not(feature = "no-color"))] fn has_colors(&self) -> bool { - use control; - control::SHOULD_COLORIZE.should_colorize() } @@ -412,7 +568,7 @@ impl<'a> Colorize for &'a str { impl fmt::Display for ColoredString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if !self.has_colors() || self.is_plain() { - return (::fmt(&self.input, f)); + return ::fmt(&self.input, f); } // XXX: see tests. Useful when nesting colored strings @@ -632,4 +788,26 @@ mod tests { fn on_bright_color_fn() { assert_eq!("blue".on_bright_blue(), "blue".on_color("bright blue")) } + + #[test] + fn exposing_tests() { + let cstring = "".red(); + assert_eq!(cstring.fgcolor(), Some(Color::Red)); + assert_eq!(cstring.bgcolor(), None); + + let cstring = cstring.clear(); + assert_eq!(cstring.fgcolor(), None); + assert_eq!(cstring.bgcolor(), None); + + let cstring = cstring.blue().on_bright_yellow(); + assert_eq!(cstring.fgcolor(), Some(Color::Blue)); + assert_eq!(cstring.bgcolor(), Some(Color::BrightYellow)); + + let cstring = cstring.bold().italic(); + assert_eq!(cstring.fgcolor(), Some(Color::Blue)); + assert_eq!(cstring.bgcolor(), Some(Color::BrightYellow)); + assert_eq!(cstring.style().contains(Styles::Bold), true); + assert_eq!(cstring.style().contains(Styles::Italic), true); + assert_eq!(cstring.style().contains(Styles::Dimmed), false); + } } diff --git a/src/style.rs b/src/style.rs index bf2cfd6..8836892 100644 --- a/src/style.rs +++ b/src/style.rs @@ -21,10 +21,12 @@ static STYLES: [(u8, Styles); 8] = [ pub static CLEAR: Style = Style(CLEARV); +/// A combinatorial style such as bold, italics, dimmed, etc. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Style(u8); #[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[allow(missing_docs)] pub enum Styles { Clear, Bold, @@ -85,7 +87,21 @@ impl Styles { } impl Style { - pub fn to_str(self) -> String { + /// Check if the current style has one of [`Styles`](Styles) switched on. + /// + /// ```rust + /// # use colored::*; + /// let colored = "".bold().italic(); + /// assert_eq!(colored.style().contains(Styles::Bold), true); + /// assert_eq!(colored.style().contains(Styles::Italic), true); + /// assert_eq!(colored.style().contains(Styles::Dimmed), false); + /// ``` + pub fn contains(&self, style: Styles) -> bool { + let s = style.to_u8(); + self.0 & s == s + } + + pub(crate) fn to_str(self) -> String { let styles = Styles::from_u8(self.0).unwrap_or_default(); styles .iter() @@ -94,21 +110,14 @@ impl Style { .join(";") } - pub fn new(from: Styles) -> Style { - Style(from.to_u8()) - } - - pub fn from_both(one: Style, two: Styles) -> Style { - Style(one.0 | two.to_u8()) - } - - pub fn add(&mut self, two: Styles) { + pub(crate) fn add(&mut self, two: Styles) { self.0 |= two.to_u8(); } } #[cfg(test)] mod tests { + use super::*; mod u8_to_styles_invalid_is_none { use super::super::CLEARV; @@ -170,9 +179,9 @@ mod tests { }; fn style_from_multiples(styles: &[Styles]) -> Style { - let mut res = Style::new(styles[0]); + let mut res = Style(styles[0].to_u8()); for s in &styles[1..] { - res = Style::from_both(res, *s) + res = Style(res.0 | s.to_u8()); } res } @@ -274,4 +283,13 @@ mod tests { test_combine!(s) } } + + fn test_style_contains() { + let mut style = Style(Styles::Bold.to_u8()); + style.add(Styles::Italic); + + assert_eq!(style.contains(Styles::Bold), true); + assert_eq!(style.contains(Styles::Italic), true); + assert_eq!(style.contains(Styles::Dimmed), false); + } } diff --git a/tests/ansi_term_compat.rs b/tests/ansi_term_compat.rs index 0ed7f35..633ff4f 100644 --- a/tests/ansi_term_compat.rs +++ b/tests/ansi_term_compat.rs @@ -130,10 +130,7 @@ mod compat_overrides { fn overrides1() { let s = "test string"; let ansi = Colour::Red.on(Colour::Black).on(Colour::Blue).paint(s); - assert_eq!( - ansi.to_string(), - s.red().on_blue().to_string() - ); + assert_eq!(ansi.to_string(), s.red().on_blue().to_string()); } #[test]