Repository: colored-rs/colored (language: rust)

Issue:
Convert between bright and non-bright colours
I would like it if there was a way to convert, say `Color::Red` to `Color::BrightRed` and back again with a method, maybe by splitting out the bright colours from `Color` into `BrightColor`. To convert,  methods such as `.bright()` on `Color` and `.normal()` on `BrightColor` could be added.


Discussion / hints:
Hi @arzg,

Could you please provide a use case example?
Today I was toying around with creating a [Civilization](https://en.wikipedia.org/wiki/Civilization_(series))-like game with a TUI, and I was considering how I’d implement [Fog of War](https://civilization.fandom.com/wiki/Fog_of_war) for areas of the map that have already been explored. I realised that a nice way to accomplish this would be to represent terrain types as _bright_ text cell background colours, with the explored Fog of War areas having non-bright background colours.
Would it be better to have methods to extract the 3 values of a `ColoredString`?

For example:

```rust
impl ColoredString {
	pub fn fgcolor(&self) -> Option<Color>;
	pub fn bgcolor(&self) -> Option<Color>;
	pub fn style(&self) -> Style;
}
```

The `Style` could also have a method that can check if it contains one of the `Styles`.

This way, rather than implementing very specific functions, you can check if the background colour is a bright variant or not, and appropriately flip.
Sounds good to me!

Fix patch:
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<Color, ()> = "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<bool>,
@@ -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<S: Into<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<S: Into<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<Color> {
+        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<Color> {
+        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 (<String as fmt::Display>::fmt(&self.input, f));
+            return <String as fmt::Display>::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);
+    }
 }


Test patch:
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]