dots_internal_utils 0.5.0

A set of utility modules intended to be used internally by the dots crate
Documentation
use core::fmt;
use std::ops::{Add, Sub};

use yansi;

#[derive(Clone, Copy)]
enum Color {
    Black,
    Red,
    Green,
    Blue,
    Magenta,
    Yellow,
    Cyan,
    White,
}

#[derive(Clone, Copy)]
enum Effect {
    Bold,
    Dimmed,
    Italic,
    Underline,
    Blink,
    Invert,
    Hidden,
    Strikethrough,
}

const DEFAULT: Style = Style {
    color: None,
    background: None,
    bold: false,
    dim: false,
    italic: false,
    underlined: false,
    blink: false,
    invert: false,
    hidden: false,
    strikethrough: false,
};

#[derive(Clone, Copy)]
pub struct Style {
    color: Option<Color>,
    background: Option<Color>,
    bold: bool,
    dim: bool,
    italic: bool,
    underlined: bool,
    blink: bool,
    invert: bool,
    hidden: bool,
    strikethrough: bool,
}

impl Style {
    pub fn detect_color_support() {
        if supports_color::on(supports_color::Stream::Stdout).is_none() {
            yansi::Paint::disable();
        }
    }

    pub const fn new() -> Style {
        Style { ..DEFAULT }
    }

    #[must_use]
    const fn set_color(mut self, color: Color) -> Self {
        self.color = Some(color);
        self
    }

    #[must_use]
    const fn set_background(mut self, color: Color) -> Self {
        self.background = Some(color);
        self
    }

    #[must_use]
    const fn set_effect(mut self, effect: Effect, enabled: bool) -> Self {
        match effect {
            Effect::Bold => self.bold = enabled,
            Effect::Dimmed => self.dim = enabled,
            Effect::Italic => self.italic = enabled,
            Effect::Underline => self.underlined = enabled,
            Effect::Blink => self.blink = enabled,
            Effect::Invert => self.invert = enabled,
            Effect::Hidden => self.hidden = enabled,
            Effect::Strikethrough => self.strikethrough = enabled,
        }
        self
    }

    #[must_use]
    pub const fn black(&self) -> Style {
        self.set_color(Color::Black)
    }
    #[must_use]
    pub const fn red(&self) -> Style {
        self.set_color(Color::Red)
    }
    #[must_use]
    pub const fn green(&self) -> Style {
        self.set_color(Color::Green)
    }
    #[must_use]
    pub const fn yellow(&self) -> Style {
        self.set_color(Color::Yellow)
    }
    #[must_use]
    pub const fn blue(&self) -> Style {
        self.set_color(Color::Blue)
    }
    #[must_use]
    pub const fn magenta(&self) -> Style {
        self.set_color(Color::Magenta)
    }
    #[must_use]
    pub const fn cyan(&self) -> Style {
        self.set_color(Color::Cyan)
    }
    #[must_use]
    pub const fn white(&self) -> Style {
        self.set_color(Color::White)
    }

    #[must_use]
    pub const fn on_black(&self) -> Style {
        self.set_background(Color::Black)
    }
    #[must_use]
    pub const fn on_red(&self) -> Style {
        self.set_background(Color::Red)
    }
    #[must_use]
    pub const fn on_green(&self) -> Style {
        self.set_background(Color::Green)
    }
    #[must_use]
    pub const fn on_yellow(&self) -> Style {
        self.set_background(Color::Yellow)
    }
    #[must_use]
    pub const fn on_blue(&self) -> Style {
        self.set_background(Color::Blue)
    }
    #[must_use]
    pub const fn on_magenta(&self) -> Style {
        self.set_background(Color::Magenta)
    }
    #[must_use]
    pub const fn on_cyan(&self) -> Style {
        self.set_background(Color::Cyan)
    }
    #[must_use]
    pub const fn on_white(&self) -> Style {
        self.set_background(Color::White)
    }

    #[must_use]
    pub const fn bold(&self) -> Style {
        self.set_effect(Effect::Bold, true)
    }
    #[must_use]
    pub const fn dim(&self) -> Style {
        self.set_effect(Effect::Dimmed, true)
    }
    #[must_use]
    pub const fn italic(&self) -> Style {
        self.set_effect(Effect::Italic, true)
    }
    #[must_use]
    pub const fn underlined(&self) -> Style {
        self.set_effect(Effect::Underline, true)
    }
    #[must_use]
    pub const fn blink(&self) -> Style {
        self.set_effect(Effect::Blink, true)
    }
    #[must_use]
    pub const fn invert(&self) -> Style {
        self.set_effect(Effect::Invert, true)
    }
    #[must_use]
    pub const fn hidden(&self) -> Style {
        self.set_effect(Effect::Hidden, true)
    }
    #[must_use]
    pub const fn strikethrough(&self) -> Style {
        self.set_effect(Effect::Strikethrough, true)
    }

    #[must_use]
    const fn merge(&self, style: Style) -> Style {
        Style {
            color: match style.color {
                Some(color) => Some(color),
                None => self.color,
            },
            background: match style.background {
                Some(color) => Some(color),
                None => self.background,
            },
            bold: style.bold || self.bold,
            dim: style.dim || self.dim,
            italic: style.italic || self.italic,
            underlined: style.underlined || self.underlined,
            blink: style.blink || self.blink,
            invert: style.invert || self.invert,
            hidden: style.hidden || self.hidden,
            strikethrough: style.strikethrough || self.strikethrough,
        }
    }

    #[must_use]
    pub fn apply<D: fmt::Display>(&self, val: D) -> impl fmt::Display {
        let style: yansi::Style = self.into();
        style.paint(val)
    }
}

impl From<&Style> for yansi::Style {
    fn from(style: &Style) -> Self {
        let mut converted = yansi::Style::new(yansi::Color::Unset);

        if let Some(fg) = style.color {
            converted = converted.fg(fg.into());
        };

        if let Some(bg) = style.background {
            converted = converted.fg(bg.into());
        };

        if style.bold {
            converted = converted.bold()
        }
        if style.dim {
            converted = converted.dimmed()
        }
        if style.italic {
            converted = converted.italic()
        }
        if style.underlined {
            converted = converted.underline()
        }
        if style.blink {
            converted = converted.blink()
        }
        if style.invert {
            converted = converted.invert()
        }
        if style.hidden {
            converted = converted.hidden()
        }
        if style.strikethrough {
            converted = converted.strikethrough()
        }

        converted
    }
}

impl From<Color> for Style {
    fn from(color: Color) -> Self {
        Style::new().set_color(color)
    }
}

impl From<Effect> for Style {
    fn from(effect: Effect) -> Self {
        Style::new().set_effect(effect, true)
    }
}

impl From<Color> for yansi::Color {
    fn from(color: Color) -> Self {
        match color {
            Color::Black => yansi::Color::Black,
            Color::Red => yansi::Color::Red,
            Color::Green => yansi::Color::Green,
            Color::Blue => yansi::Color::Blue,
            Color::Magenta => yansi::Color::Magenta,
            Color::Cyan => yansi::Color::Cyan,
            Color::Yellow => yansi::Color::Yellow,
            Color::White => yansi::Color::White,
        }
    }
}

impl Add<Style> for Style {
    type Output = Style;
    fn add(self, rhs: Style) -> Self::Output {
        self.merge(rhs)
    }
}

impl Add<Color> for Style {
    type Output = Style;
    fn add(self, rhs: Color) -> Self::Output {
        self.set_color(rhs)
    }
}

impl Add<Effect> for Style {
    type Output = Style;
    fn add(self, effect: Effect) -> Self::Output {
        self.set_effect(effect, true)
    }
}

impl Sub<Effect> for Style {
    type Output = Style;
    fn sub(self, effect: Effect) -> Self::Output {
        self.set_effect(effect, false)
    }
}