Skip to content

Commit

Permalink
Add and Expose Methods of easily Creating and Mutating `ColoredString…
Browse files Browse the repository at this point in the history
…`'s and `Style`'s for Users (#154)

* Initial change (Deref<Target = String> + DerefMut for ColoredString).

* Add an example of modifying a ColoredString's text content with DerefMut<Target = String>.

* Add methods copy_fgcolor, copy_bgcolor, and copy_style to ColoredString.

* Create traits Colorized and CopyColorize, moved style copying functions from ColoredString into CopyColorize, and add StyleTemplate struct to assist with CopyColorize.

* Create, implement, and expose methods of creating and modifying Style values for the user.

* Fmt + add new_from method to StyleTemplate.

* Since Style now implements default, move Default impls for ColoredString and StyleTemplate to derive.

* Improve docs for StyleTemplate and espeically Style to be more helpful.

* Add builder pattern methods to Style to make them easier to construct and modify.

* Add ColorizedMut trait for univeral direct color and style setting.

* Impl FromIterator<Styles> for Style and update docs to mention and use that.

* Fix docs (remove mention in docs for Style of CLEAR which is private).

* Move tests for Style bitwise operations into their own testing submodule.

* Fix typos introduced in this branch.

* Add big docs for ColoredString in light of all the new implementations as a guide for rustling users.

* Make doc examples for Style more readable.

* Add impls of bitwise logic ops for Styles for quick creation of Style's. Also, add doc for Styles and improve tests.

* Remove StyleTemplate as it didn't really serve enough purpose to justify it existing.

* Add clearing methods to ColorizedMut, updated docs to not use 'set to None' and generally improve ColorizedMut docs.

* Change section headers for Style docs to be h2's because they look nicer and change the wording on the first section (renamed to "Creation")

* Touch up doc for CopyColorize.

* Touch up ColorizedMut doc and fix some typos.

* Expose fields of ColoredString, remove Colored, ColoredMut, and CopyColorize as they are now unnesecary, and modify docs to reflect the new idioms.

* Add note in crate root docs about ColoredString and to see its docs for more info.

* Remove modifying_colored_strings example as it was perfectly well explained in the ColoredString docs.

* Update CHANGELOG.md

* Remove ColoredString.input.[str method] antipattern from docs.

* Oops. diff.txt was temp and not supposed to be in there

* Add missing assign ops involving Styles and add missing tests for ops.

* Add with_style and with_color_and_style methods to Colorize (I can't wait for another PR tbh).

* Reset <ColoredString as Deref>::Target to str and change docs and changelog to reflect this. Also, mark ColoredString as non_exhaustive.

* Remove with_style and with_color_and_style from Colorize (they might be back in some form in the future).

* Somehow the merge messed some formatting up.
  • Loading branch information
JustAnotherCodemonkey authored Dec 12, 2023
1 parent 682adf8 commit 949f601
Show file tree
Hide file tree
Showing 4 changed files with 795 additions and 46 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@
- 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<Styles>` 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<Styles>` 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`.

Expand Down
101 changes: 77 additions & 24 deletions src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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 },
}
}
Expand Down Expand Up @@ -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 }
Expand Down
134 changes: 116 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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<Color>,
bgcolor: Option<Color>,
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<Color>,
/// The background color (if any). None means that the text will be printed
/// without a special background.
pub bgcolor: Option<Color>,
/// 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.
Expand Down Expand Up @@ -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<Color> {
self.fgcolor.as_ref().copied()
}
Expand All @@ -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<Color> {
self.bgcolor.as_ref().copied()
}
Expand All @@ -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
Expand Down Expand Up @@ -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 <Self as Deref>::Target {
&mut self.input
}
}

impl From<String> for ColoredString {
fn from(s: String) -> Self {
ColoredString {
Expand Down Expand Up @@ -863,6 +959,8 @@ mod tests {

#[test]
fn exposing_tests() {
#![allow(deprecated)]

let cstring = "".red();
assert_eq!(cstring.fgcolor(), Some(Color::Red));
assert_eq!(cstring.bgcolor(), None);
Expand Down
Loading

0 comments on commit 949f601

Please sign in to comment.