mod color;
pub mod control;
mod style;
pub use self::customcolors::CustomColor;
pub mod customcolors;
use std::{
borrow::Cow,
fmt,
ops::{Deref, DerefMut},
};
pub use color::*;
pub use style::{Style, Styles};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct ColoredString<'a> {
pub input: Cow<'a, str>,
pub fgcolor: Option<Color>,
pub bgcolor: Option<Color>,
pub style: style::Style,
}
macro_rules! impl_basic_fg_colors {
($(($method:ident, $color:ident)),*) => {
$(
fn $method(self) -> ColoredString<'a>
where
Self: Sized,
{
self.color(Color::$color)
}
)*
}
}
macro_rules! impl_basic_bg_colors {
($(($method:ident, $color:ident)),*) => {
$(
fn $method(self) -> ColoredString<'a>
where
Self: Sized,
{
self.on_color(Color::$color)
}
)*
}
}
#[allow(missing_docs)]
pub trait Colorize<'a> {
impl_basic_fg_colors! {
(black, Black),
(red, Red),
(green, Green),
(yellow, Yellow),
(blue, Blue),
(magenta, Magenta),
(purple, Magenta), (cyan, Cyan),
(white, White),
(bright_black, BrightBlack),
(bright_red, BrightRed),
(bright_green, BrightGreen),
(bright_yellow, BrightYellow),
(bright_blue, BrightBlue),
(bright_magenta, BrightMagenta),
(bright_purple, BrightMagenta), (bright_cyan, BrightCyan),
(bright_white, BrightWhite)
}
fn ansi256color(self, idx: u8) -> ColoredString<'a>
where
Self: Sized,
{
self.color(Color::Ansi256 { idx })
}
fn truecolor(self, r: u8, g: u8, b: u8) -> ColoredString<'a>
where
Self: Sized,
{
self.color(Color::TrueColor { r, g, b })
}
fn hexcolor<S>(self, hex: S) -> ColoredString<'a>
where
Self: Sized,
S: AsRef<str>,
{
self.color(parse_hex(hex.as_ref()).unwrap())
}
fn try_hexcolor<S>(self, hex: S) -> Option<ColoredString<'a>>
where
Self: Sized,
S: AsRef<str>,
{
parse_hex(hex.as_ref()).map(|color| self.color(color))
}
fn custom_color<T>(self, color: T) -> ColoredString<'a>
where
Self: Sized,
T: Into<CustomColor>,
{
let color = color.into();
self.color(Color::TrueColor {
r: color.r,
g: color.g,
b: color.b,
})
}
fn color<S: Into<Color>>(self, color: S) -> ColoredString<'a>;
impl_basic_bg_colors! {
(on_black, Black),
(on_red, Red),
(on_green, Green),
(on_yellow, Yellow),
(on_blue, Blue),
(on_magenta, Magenta),
(on_purple, Magenta), (on_cyan, Cyan),
(on_white, White),
(on_bright_black, BrightBlack),
(on_bright_red, BrightRed),
(on_bright_green, BrightGreen),
(on_bright_yellow, BrightYellow),
(on_bright_blue, BrightBlue),
(on_bright_magenta, BrightMagenta),
(on_bright_purple, BrightMagenta), (on_bright_cyan, BrightCyan),
(on_bright_white, BrightWhite)
}
fn on_ansi256color(self, idx: u8) -> ColoredString<'a>
where
Self: Sized,
{
self.on_color(Color::Ansi256 { idx })
}
fn on_truecolor(self, r: u8, g: u8, b: u8) -> ColoredString<'a>
where
Self: Sized,
{
self.on_color(Color::TrueColor { r, g, b })
}
fn on_hexcolor<S>(self, hex: S) -> ColoredString<'a>
where
Self: Sized,
S: AsRef<str>,
{
self.on_color(parse_hex(hex.as_ref()).unwrap())
}
fn try_on_hexcolor<S>(self, hex: S) -> Option<ColoredString<'a>>
where
Self: Sized,
S: AsRef<str>,
{
parse_hex(hex.as_ref()).map(|color| self.on_color(color))
}
fn on_custom_color<T>(self, color: T) -> ColoredString<'a>
where
Self: Sized,
T: Into<CustomColor>,
{
let color = color.into();
self.on_color(Color::TrueColor {
r: color.r,
g: color.g,
b: color.b,
})
}
fn on_color<S: Into<Color>>(self, color: S) -> ColoredString<'a>;
fn clear(self) -> ColoredString<'a>;
fn normal(self) -> ColoredString<'a>;
fn bold(self) -> ColoredString<'a>;
fn dimmed(self) -> ColoredString<'a>;
fn italic(self) -> ColoredString<'a>;
fn underline(self) -> ColoredString<'a>;
fn blink(self) -> ColoredString<'a>;
fn reversed(self) -> ColoredString<'a>;
fn hidden(self) -> ColoredString<'a>;
fn strikethrough(self) -> ColoredString<'a>;
}
impl ColoredString<'_> {
pub fn clear_fgcolor(&mut self) {
self.fgcolor = None;
}
pub fn clear_bgcolor(&mut self) {
self.bgcolor = None;
}
pub fn clear_style(&mut self) {
self.style = Style::default();
}
#[must_use]
pub fn is_plain(&self) -> bool {
self.bgcolor.is_none()
&& self.fgcolor.is_none()
&& self.style == style::CLEAR
}
fn has_colors() -> bool {
control::get_current_color_level() != control::ColorLevel::None
}
fn compute_style(&self) -> String {
if !Self::has_colors() || self.is_plain() {
return String::new();
}
let mut res = String::from("\x1B[");
let mut has_wrote = if self.style == style::CLEAR {
false
} else {
res.push_str(&self.style.to_str());
true
};
if let Some(ref bgcolor) = self.bgcolor {
if has_wrote {
res.push(';');
}
res.push_str(&bgcolor.to_bg_str());
has_wrote = true;
}
if let Some(ref fgcolor) = self.fgcolor {
if has_wrote {
res.push(';');
}
res.push_str(&fgcolor.to_fg_str());
}
res.push('m');
res
}
fn escape_inner_reset_sequences(&self) -> Cow<str> {
if !Self::has_colors() || self.is_plain() {
return Cow::Borrowed(self.input.as_ref());
}
let reset = "\x1B[0m";
if !self.input.contains(reset) {
return Cow::Borrowed(self.input.as_ref());
}
let style = self.compute_style();
let matches: Vec<_> = self.input.match_indices(reset).collect();
let additional_space = matches.len() * style.len();
let mut result =
String::with_capacity(self.input.len() + additional_space);
let mut last_end = 0;
for (idx, _) in matches {
result.push_str(&self.input[last_end..idx]);
result.push_str(reset);
result.push_str(&style);
last_end = idx + reset.len();
}
if last_end < self.input.len() {
result.push_str(&self.input[last_end..]);
}
Cow::Owned(result)
}
}
impl Deref for ColoredString<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.input.as_ref()
}
}
impl DerefMut for ColoredString<'_> {
fn deref_mut(&mut self) -> &mut <Self as Deref>::Target {
self.input.to_mut()
}
}
impl<'a, T: Into<Cow<'a, str>>> From<T> for ColoredString<'a> {
fn from(s: T) -> Self {
ColoredString {
input: s.into(),
..ColoredString::default()
}
}
}
macro_rules! impl_coloredstring_style_methods {
($(($method:ident, $style:expr)),*) => {
$(
fn $method(mut self) -> ColoredString<'a> {
self.style.add($style);
self
}
)*
}
}
impl<'a> Colorize<'a> for ColoredString<'a> {
fn color<S: Into<Color>>(mut self, color: S) -> ColoredString<'a> {
self.fgcolor = Some(color.into());
self
}
fn on_color<S: Into<Color>>(mut self, color: S) -> ColoredString<'a> {
self.bgcolor = Some(color.into());
self
}
fn clear(self) -> ColoredString<'a> {
Self {
input: self.input,
..Self::default()
}
}
fn normal(self) -> ColoredString<'a> {
self.clear()
}
impl_coloredstring_style_methods! {
(bold, style::Styles::Bold),
(dimmed, style::Styles::Dimmed),
(italic, style::Styles::Italic),
(underline, style::Styles::Underline),
(blink, style::Styles::Blink),
(reversed, style::Styles::Reversed),
(hidden, style::Styles::Hidden),
(strikethrough, style::Styles::Strikethrough)
}
}
macro_rules! impl_str_style_methods {
($(($method:ident)),*) => {
$(
fn $method(self) -> ColoredString<'a> {
ColoredString::from(self).$method()
}
)*
}
}
impl<'a> Colorize<'a> for &'a str {
fn color<S: Into<Color>>(self, color: S) -> ColoredString<'a> {
ColoredString {
fgcolor: Some(color.into()),
input: Cow::Borrowed(self),
..ColoredString::default()
}
}
fn on_color<S: Into<Color>>(self, color: S) -> ColoredString<'a> {
ColoredString {
bgcolor: Some(color.into()),
input: Cow::Borrowed(self),
..ColoredString::default()
}
}
fn clear(self) -> ColoredString<'a> {
ColoredString {
input: Cow::Borrowed(self),
style: style::CLEAR,
..ColoredString::default()
}
}
fn normal(self) -> ColoredString<'a> {
self.clear()
}
impl_str_style_methods! {
(bold),
(dimmed),
(italic),
(underline),
(blink),
(reversed),
(hidden),
(strikethrough)
}
}
impl fmt::Display for ColoredString<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if !Self::has_colors() || self.is_plain() {
return write!(f, "{}", self.input);
}
let escaped_input = self.escape_inner_reset_sequences();
f.write_str(&self.compute_style())?;
write!(f, "{}", escaped_input)?;
f.write_str("\x1B[0m")?;
Ok(())
}
}
fn parse_hex(s: &str) -> Option<Color> {
let s = s.strip_prefix('#').unwrap_or(s);
match s.len() {
6 => {
let r = u8::from_str_radix(&s[0..2], 16).ok()?;
let g = u8::from_str_radix(&s[2..4], 16).ok()?;
let b = u8::from_str_radix(&s[4..6], 16).ok()?;
Some(Color::TrueColor { r, g, b })
}
3 => {
let r = u8::from_str_radix(&s[0..1], 16).ok()?;
let g = u8::from_str_radix(&s[1..2], 16).ok()?;
let b = u8::from_str_radix(&s[2..3], 16).ok()?;
Some(Color::TrueColor {
r: r * 17, g: g * 17,
b: b * 17,
})
}
_ => None,
}
}