diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 77d5bf8..5ca8238 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,6 +52,8 @@ jobs: name: Check semver compatibility runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 - name: Check semver uses: obi1kenobi/cargo-semver-checks-action@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 84cc84b..57e5f9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ # Unreleased - Document crate MSRV of `1.70`. -- Handle errors in `set_virtual_terminal`. + +- Updated top-level docs to include a note about `ColoredString`\'s role in the `Colorize` pipeline as well as link to it to suggest learning more about how to manipulate existing `ColoredString`\'s. +- Changes to `ColoredString`: + - Expose fields. + - **[DEPRECATION]:** Deprecated methods `fgcolor`, `bgcolor`, and `style` due to their obsolescence in the face of the exposing of their represented fields. + - Add methods for clearing specific elements of `fgcolor`, `bgcolor`, and `style`. + - Change Default implementation to be via derive as Style now implements Default (see changes to Style below). + - Add implementation of `DerefMut`. + - Updated docs to reflect the above changes as well as generally greatly expand them. +- Changes to `Style`: + - Implemented `Default` for `Style` (returns `CLEAR`). This exposes a method by which users can create plain `Style`\'s from scratch. + - Implemented `From` for `Style`. This lets users easily create `Style`\'s from specific styles. + - Exposed previously private method `add`. + - Created method `remove` which essentially does the opposite. + - Added builder-style methods in the vein of `Colorize` to add stylings (e.g. `bold`, `underline`, `italic`, `strikethrough`). + - Implemented bitwise operators `BitAnd`, `BitOr`, `BitXor`, and `Not` as well as their representative assignment operators. You can also use a `Styles` as an operand for these. + - Implemented `FromIterator` for Style. +- Changes to `Styles`: + - Implemented bitwise operators `BitAnd`, `BitOr`, `BitXor`, and `Not` which all combine `Styles`\'s and output `Style`\'s. These can also take a `Style` as an operand. +- Added additional testing for all of the above changes. +- Added methods `with_style` and `with_color_and_style` to `Colorize`. # 2.0.4 - Switch from `winapi` to `windows-sys`. diff --git a/src/color.rs b/src/color.rs index a107ef1..b6d796e 100644 --- a/src/color.rs +++ b/src/color.rs @@ -52,7 +52,9 @@ impl Color { Color::BrightMagenta => "95".into(), Color::BrightCyan => "96".into(), Color::BrightWhite => "97".into(), - Color::TrueColor {..} if !truecolor_support() => self.closest_color_euclidean().to_fg_str(), + Color::TrueColor { .. } if !truecolor_support() => { + self.closest_color_euclidean().to_fg_str() + } Color::TrueColor { r, g, b } => format!("38;2;{};{};{}", r, g, b).into(), } } @@ -75,18 +77,24 @@ impl Color { Color::BrightMagenta => "105".into(), Color::BrightCyan => "106".into(), Color::BrightWhite => "107".into(), - Color::TrueColor {..} if !truecolor_support() => self.closest_color_euclidean().to_bg_str(), + Color::TrueColor { .. } if !truecolor_support() => { + self.closest_color_euclidean().to_bg_str() + } Color::TrueColor { r, g, b } => format!("48;2;{};{};{}", r, g, b).into(), } } /// Gets the closest plain color to the TrueColor fn closest_color_euclidean(&self) -> Self { - use Color::*; use std::cmp; + use Color::*; match *self { - TrueColor { r: r1, g: g1, b: b1 } => { + TrueColor { + r: r1, + g: g1, + b: b1, + } => { let colors = vec![ Black, Red, @@ -104,7 +112,9 @@ impl Color { BrightMagenta, BrightCyan, BrightWhite, - ].into_iter().map(|c| (c, c.into_truecolor())); + ] + .into_iter() + .map(|c| (c, c.into_truecolor())); let distances = colors.map(|(c_original, c)| { if let TrueColor { r, g, b } = c { let rd = cmp::max(r, r1) - cmp::min(r, r1); @@ -123,28 +133,67 @@ impl Color { } c => c, } - } fn into_truecolor(self) -> Self { use Color::*; match self { - Black => TrueColor { r: 0, g: 0, b: 0 }, - Red => TrueColor { r: 205, g: 0, b: 0 }, - Green => TrueColor { r: 0, g: 205, b: 0 }, - Yellow => TrueColor { r: 205, g: 205, b: 0 }, - Blue => TrueColor { r: 0, g: 0, b: 238 }, - Magenta => TrueColor { r: 205, g: 0, b: 205 }, - Cyan => TrueColor { r: 0, g: 205, b: 205 }, - White => TrueColor { r: 229, g: 229, b: 229 }, - BrightBlack => TrueColor { r: 127, g: 127, b: 127 }, - BrightRed => TrueColor { r: 255, g: 0, b: 0 }, - BrightGreen => TrueColor { r: 0, g: 255, b: 0 }, - BrightYellow => TrueColor { r: 255, g: 255, b: 0 }, - BrightBlue => TrueColor { r: 92, g: 92, b: 255 }, - BrightMagenta => TrueColor { r: 255, g: 0, b: 255 }, - BrightCyan => TrueColor { r: 0, g: 255, b: 255 }, - BrightWhite => TrueColor { r: 255, g: 255, b: 255 }, + Black => TrueColor { r: 0, g: 0, b: 0 }, + Red => TrueColor { r: 205, g: 0, b: 0 }, + Green => TrueColor { r: 0, g: 205, b: 0 }, + Yellow => TrueColor { + r: 205, + g: 205, + b: 0, + }, + Blue => TrueColor { r: 0, g: 0, b: 238 }, + Magenta => TrueColor { + r: 205, + g: 0, + b: 205, + }, + Cyan => TrueColor { + r: 0, + g: 205, + b: 205, + }, + White => TrueColor { + r: 229, + g: 229, + b: 229, + }, + BrightBlack => TrueColor { + r: 127, + g: 127, + b: 127, + }, + BrightRed => TrueColor { r: 255, g: 0, b: 0 }, + BrightGreen => TrueColor { r: 0, g: 255, b: 0 }, + BrightYellow => TrueColor { + r: 255, + g: 255, + b: 0, + }, + BrightBlue => TrueColor { + r: 92, + g: 92, + b: 255, + }, + BrightMagenta => TrueColor { + r: 255, + g: 0, + b: 255, + }, + BrightCyan => TrueColor { + r: 0, + g: 255, + b: 255, + }, + BrightWhite => TrueColor { + r: 255, + g: 255, + b: 255, + }, TrueColor { r, g, b } => TrueColor { r, g, b }, } } @@ -300,11 +349,15 @@ mod tests { ( $test:ident : ( $r:literal, $g: literal, $b:literal ), $expected:expr ) => { #[test] fn $test() { - let true_color = Color::TrueColor { r: $r, g: $g, b: $b }; + let true_color = Color::TrueColor { + r: $r, + g: $g, + b: $b, + }; let actual = true_color.closest_color_euclidean(); assert_eq!(actual, $expected); } - } + }; } make_euclidean_distance_test! { exact_black: (0, 0, 0), Color::Black } diff --git a/src/control.rs b/src/control.rs index 2d72289..da09888 100644 --- a/src/control.rs +++ b/src/control.rs @@ -10,6 +10,9 @@ use std::sync::atomic::{AtomicBool, Ordering}; /// This is primarily used for Windows 10 environments which will not correctly colorize /// the outputs based on ANSI escape codes. /// +/// The returned `Result` is _always_ `Ok(())`, the return type was kept to ensure backwards +/// compatibility. +/// /// # Notes /// > Only available to `Windows` build targets. /// @@ -25,7 +28,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; #[allow(clippy::result_unit_err)] #[cfg(windows)] pub fn set_virtual_terminal(use_virtual: bool) -> Result<(), ()> { - use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; use windows_sys::Win32::System::Console::{ GetConsoleMode, GetStdHandle, SetConsoleMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING, STD_OUTPUT_HANDLE, @@ -33,17 +35,8 @@ pub fn set_virtual_terminal(use_virtual: bool) -> Result<(), ()> { unsafe { let handle = GetStdHandle(STD_OUTPUT_HANDLE); - if handle == INVALID_HANDLE_VALUE { - return Err(()); - } - let mut original_mode = 0; - // Return value of 0 means that the function failed: - // https://learn.microsoft.com/en-us/windows/console/getconsolemode#return-value - if GetConsoleMode(handle, &mut original_mode) == 0 { - // TODO: It would be prudent to get the error using `GetLastError` here. - return Err(()); - } + GetConsoleMode(handle, &mut original_mode); let enabled = original_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == ENABLE_VIRTUAL_TERMINAL_PROCESSING; diff --git a/src/lib.rs b/src/lib.rs index e17cc44..10d34d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,10 @@ //! //! See [the `Colorize` trait](./trait.Colorize.html) for all the methods. //! +//! Note: The methods of [`Colorize`], when used on [`str`]'s, return +//! [`ColoredString`]'s. See [`ColoredString`] to learn more about them and +//! what you can do with them beyond continue to use [`Colorize`] to further +//! modify them. #![warn(missing_docs)] #[macro_use] @@ -43,17 +47,94 @@ pub mod customcolors; pub use color::*; -use std::{borrow::Cow, error::Error, fmt, ops::Deref}; +use std::{ + borrow::Cow, + error::Error, + fmt, + ops::{Deref, DerefMut}, +}; pub use style::{Style, Styles}; /// A string that may have color and/or style applied to it. -#[derive(Clone, Debug, PartialEq, Eq)] +/// +/// Commonly created via calling the methods of [`Colorize`] on a &str. +/// All methods of [`Colorize`] either create a new `ColoredString` from +/// the type called on or modify a callee `ColoredString`. See +/// [`Colorize`] for more. +/// +/// The primary usage of `ColoredString`'s is as a way to take text, +/// apply colors and miscillaneous styling to it (such as bold or +/// underline), and then use it to create formatted strings that print +/// to the console with the special styling applied. +/// +/// ## Usage +/// +/// As stated, `ColoredString`'s, once created, can be printed to the +/// console with their colors and style or turned into a string +/// containing special console codes that has the same effect. +/// This is made easy via `ColoredString`'s implementations of +/// [`Display`](std::fmt::Display) and [`ToString`] for those purposes +/// respectively. +/// +/// Printing a `ColoredString` with its style is as easy as: +/// +/// ``` +/// # use colored::*; +/// let cstring: ColoredString = "Bold and Red!".bold().red(); +/// println!("{}", cstring); +/// ``` +/// +/// ## Manipulating the coloring/style of a `ColoredString` +/// +/// Getting or changing the foreground color, background color, and or +/// style of a `ColoredString` is as easy as manually reading / modifying +/// the fields of `ColoredString`. +/// +/// ``` +/// # use colored::*; +/// let mut red_text = "Red".red(); +/// // Changing color using re-assignment and [`Colorize`]: +/// red_text = red_text.blue(); +/// // Manipulating fields of `ColoredString` in-place: +/// red_text.fgcolor = Some(Color::Blue); +/// +/// let styled_text1 = "Bold".bold(); +/// let styled_text2 = "Italic".italic(); +/// let mut styled_text3 = ColoredString::from("Bold and Italic"); +/// styled_text3.style = styled_text1.style | styled_text2.style; +/// ``` +/// +/// ## Modifying the text of a `ColoredString` +/// +/// Modifying the text is as easy as modifying the `input` field of +/// `ColoredString`... +/// +/// ``` +/// # use colored::*; +/// let mut colored_text = "Magenta".magenta(); +/// colored_text = colored_text.blue(); +/// colored_text.input = "Blue".to_string(); +/// // Note: The above is inefficient and `colored_text.input.replace_range(.., "Blue")` would +/// // be more proper. This is just for example. +/// +/// assert_eq!(&*colored_text, "Blue"); +/// ``` +/// +/// Notice how this process preserves the coloring and style. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[non_exhaustive] pub struct ColoredString { - input: String, - fgcolor: Option, - bgcolor: Option, - style: style::Style, + /// The plain text that will have color and style applied to it. + pub input: String, + /// The color of the text as it will be printed. + pub fgcolor: Option, + /// The background color (if any). None means that the text will be printed + /// without a special background. + pub bgcolor: Option, + /// Any special styling to be applied to the text (see Styles for a list of + /// available options). + pub style: style::Style, } /// The trait that enables something to be given color. @@ -345,6 +426,7 @@ impl ColoredString { /// let cstr = cstr.clear(); /// assert_eq!(cstr.fgcolor(), None); /// ``` + #[deprecated(note = "Deprecated due to the exposing of the fgcolor struct field.")] pub fn fgcolor(&self) -> Option { self.fgcolor.as_ref().copied() } @@ -358,6 +440,7 @@ impl ColoredString { /// let cstr = cstr.clear(); /// assert_eq!(cstr.bgcolor(), None); /// ``` + #[deprecated(note = "Deprecated due to the exposing of the bgcolor struct field.")] pub fn bgcolor(&self) -> Option { self.bgcolor.as_ref().copied() } @@ -371,10 +454,28 @@ impl ColoredString { /// assert_eq!(colored.style().contains(Styles::Italic), true); /// assert_eq!(colored.style().contains(Styles::Dimmed), false); /// ``` + #[deprecated(note = "Deprecated due to the exposing of the style struct field.")] pub fn style(&self) -> style::Style { self.style } + /// Clears foreground coloring on this `ColoredString`, meaning that it + /// will be printed with the default terminal text color. + pub fn clear_fgcolor(&mut self) { + self.fgcolor = None; + } + + /// Gets rid of this `ColoredString`'s background. + pub fn clear_bgcolor(&mut self) { + self.bgcolor = None; + } + + /// Clears any special styling and sets it back to the default (plain, + /// maybe colored, text). + pub fn clear_style(&mut self) { + self.style = Style::default(); + } + /// Checks if the colored string has no color or styling. /// /// ```rust @@ -467,24 +568,19 @@ impl ColoredString { } } -impl Default for ColoredString { - fn default() -> Self { - ColoredString { - input: String::default(), - fgcolor: None, - bgcolor: None, - style: style::CLEAR, - } - } -} - impl Deref for ColoredString { type Target = str; - fn deref(&self) -> &str { + fn deref(&self) -> &Self::Target { &self.input } } +impl DerefMut for ColoredString { + fn deref_mut(&mut self) -> &mut ::Target { + &mut self.input + } +} + impl From for ColoredString { fn from(s: String) -> Self { ColoredString { @@ -868,6 +964,8 @@ mod tests { #[test] fn exposing_tests() { + #![allow(deprecated)] + let cstring = "".red(); assert_eq!(cstring.fgcolor(), Some(Color::Red)); assert_eq!(cstring.bgcolor(), None); diff --git a/src/style.rs b/src/style.rs index 8588554..ab02148 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,3 +1,56 @@ +use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}; + +macro_rules! auto_impl_ref_binop_trait { + (impl $trait_name:ident, $method:ident for $t:ty, $u:ty) => { + impl $trait_name<&$u> for $t { + type Output = <$t as $trait_name<$t>>::Output; + + #[inline] + fn $method(self, rhs: &$u) -> Self::Output { + $trait_name::$method(self, *rhs) + } + } + + impl $trait_name<$u> for &$t { + type Output = <$t as $trait_name<$t>>::Output; + + #[inline] + fn $method(self, rhs: $u) -> Self::Output { + $trait_name::$method(*self, rhs) + } + } + + impl $trait_name<&$u> for &$t { + type Output = <$t as $trait_name<$t>>::Output; + + #[inline] + fn $method(self, rhs: &$u) -> Self::Output { + $trait_name::$method(*self, *rhs) + } + } + }; +} + +macro_rules! impl_assign_op_trait { + ( + $trait:ident, $method:ident for $t:ty, $u:ty, using $used_trait:ident::$used_method:ident + ) => { + impl $trait<$u> for $t { + #[inline] + fn $method(&mut self, other: $u) { + *self = $used_trait::$used_method(&*self, other); + } + } + + impl $trait<&$u> for $t { + #[inline] + fn $method(&mut self, other: &$u) { + *self = $used_trait::$used_method(&*self, other); + } + } + }; +} + const CLEARV: u8 = 0b0000_0000; const BOLD: u8 = 0b0000_0001; const UNDERLINE: u8 = 0b0000_0010; @@ -22,9 +75,123 @@ static STYLES: [(u8, Styles); 8] = [ pub static CLEAR: Style = Style(CLEARV); /// A combinatorial style such as bold, italics, dimmed, etc. +/// +/// ## Creation +/// +/// `Style::default()` returns a `Style` with no style switches +/// activated and is the default method of creating a plain `Style`. +/// +/// ## `Style` from a set of `Styles`s / `Styles` iterator +/// +/// `Style` implements `FromIter` which means that it is +/// possible to do the following: +/// +/// ```rust +/// # use colored::*; +/// let style = Style::from_iter([Styles::Bold, Styles::Italic, Styles::Strikethrough]); +/// for styles in [Styles::Bold, Styles::Italic, Styles::Strikethrough] { +/// assert!(style.contains(styles)); +/// } +/// ``` +/// +/// As you can see, this is a good thing to keep in mind, although for +/// most cases, where you're not setting styles dynamically and are +/// simply creating a pre-defined set of styles, using [`Default`] and +/// then using the builder-style methods is likely prettier. +/// +/// ```rust +/// # use colored::*; +/// let many_styles = Style::default() +/// .bold() +/// .underline() +/// .italic() +/// .blink(); +/// ``` +/// +/// ## Implementation of logical bitwise operators +/// +/// `Style` implements bitwise logical operations that operate on +/// the held style switches collectively. By far the most common +/// and useful is the bitwise 'or' operator `|` which combines two +/// styles, merging their combined styles into one. Example: +/// +/// ```rust +/// # use colored::*; +/// let only_bold = Style::from(Styles::Bold); +/// // This line is actually an example of `Styles`'s bitwise logic impls but still. +/// let underline_and_italic = Styles::Underline | Styles::Italic; +/// let all_three = only_bold | underline_and_italic; +/// +/// assert!(all_three.contains(Styles::Bold) +/// && all_three.contains(Styles::Underline) +/// && all_three.contains(Styles::Italic)); +/// ``` +/// +/// This functionality also allows for easily turning off styles +/// of one `Styles` using another by combining the `&` and `!` +/// operators. +/// +/// ```rust +/// # use colored::*; +/// let mut very_loud_style = Style::default() +/// .bold() +/// .underline() +/// .italic() +/// .strikethrough() +/// .hidden(); +/// +/// // Oops! Some of those should not be in there! +/// // This Style now has all styles _except_ the two we don't want +/// // (hidden and strikethough). +/// let remove_mask = +/// !Style::from_iter([Styles::Hidden, Styles::Strikethrough]); +/// very_loud_style &= remove_mask; +/// +/// // `very_loud_style` no longer contains the undesired style +/// // switches... +/// assert!(!very_loud_style.contains(Styles::Hidden) +/// && !very_loud_style.contains(Styles::Strikethrough)); +/// // ...but it retains everything else! +/// assert!(very_loud_style.contains(Styles::Bold)); +/// ``` #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Style(u8); +/// Enum containing all of the available style settings that can be +/// applied to a [`Styles`] and by extension, a colrized type. +/// +/// ## Implementation of bitwise logical operators +/// +/// The implementations of [`BitAnd`], [`BitOr`], [`BitXor`], and +/// [`Not`] are really extensions of [`Style`]'s implementations of +/// the same. [`BitOr`] is great for starting chains of `Styles`'s +/// for creating [`Style`]'s. +/// +/// ``` +/// # use colored::*; +/// let my_styles = +/// // BitOr for Styles (Styles | Styles) = Style +/// Styles::Bold | Styles::Underline +/// // BitOr for Style (Style | Styles) = Style +/// | Styles::Italic; +/// +/// for s in [Styles::Bold, Styles::Underline, Styles::Italic] { +/// assert!(my_styles.contains(s)); +/// } +/// ``` +/// +/// [`Not`] has far fewer use cases but can still find use in +/// turning a `Styles` into a [`Style`] with all styles activated +/// except that `Styles`. +/// +/// ``` +/// # use colored::*; +/// let everything_but_bold = !Styles::Bold; +/// +/// assert!(everything_but_bold.contains(Styles::Underline)); +/// assert!(everything_but_bold.contains(Styles::Strikethrough)); +/// assert!(!everything_but_bold.contains(Styles::Bold)); +/// ``` #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[allow(missing_docs)] pub enum Styles { @@ -86,15 +253,91 @@ impl Styles { } } +impl BitAnd for Styles { + type Output = Style; + + fn bitand(self, rhs: Styles) -> Self::Output { + Style(self.to_u8() & rhs.to_u8()) + } +} + +auto_impl_ref_binop_trait!(impl BitAnd, bitand for Styles, Styles); + +impl BitAnd