stylic 0.3.1

A simple, fast library for styling text with ANSI escape codes
Documentation
use core::fmt;
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};

use crate::builder::builder_methods;

/// A style that can be applied to a value.
///
/// Has builder methods for building a style. See crate level docs for examples
/// of these.
/// ```
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[allow(missing_docs)]
pub struct Style {
    /// Foreground color.
    pub fg: Color,

    /// Background color.
    pub bg: Color,

    /// Underline color.
    ///
    /// This sets the color used for underlining when the `underline` attribute
    /// is enabled, but does not enable underlining by itself.
    pub underline: Color,

    /// Attributes such as bold, italic, etc.
    pub attributes: Attributes,
}

impl Style {
    /// Create a new style with no attributes and default colors.
    #[inline]
    pub const fn new() -> Self {
        Self {
            fg: Color::Default,
            bg: Color::Default,
            underline: Color::Default,
            attributes: Attributes::new(),
        }
    }

    /// Check if the style is empty.
    #[inline]
    pub const fn is_empty(&self) -> bool {
        self.fg.is_default()
            && self.bg.is_default()
            && self.underline.is_default()
            && self.attributes.is_empty()
    }

    builder_methods! { s => s }
}

/// A color.
///
/// Implements `From<BasicColor>`, `From<u8>`, and `From<(u8, u8, u8)>` for convenient construction
/// of basic colors, extended (256-color palette) colors, and RGB colors respectively.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Color {
    /// The default color.
    #[default]
    Default,

    /// A color from the basic 16-color palette.
    Basic(BasicColor),

    /// A color from the extended 256-color palette.
    Extended(u8),

    /// An RGB color.
    Rgb(u8, u8, u8),
}

impl Color {
    /// Check if this color is the default color.
    ///
    /// Returns `true` if this color is `Color::Default`, otherwise returns `false`.
    #[inline]
    pub const fn is_default(&self) -> bool {
        matches!(self, Self::Default)
    }

    /// Return the bright variant of this color if applicable.
    ///
    /// Otherwise, return the color itself.
    #[inline]
    pub const fn bright(self) -> Self {
        match self {
            Self::Basic(color) => Self::Basic(color.bright()),
            _ => self,
        }
    }

    /// Return the non-bright variant of this color if applicable.
    ///
    /// Otherwise, return the color itself.
    #[inline]
    pub const fn not_bright(self) -> Self {
        match self {
            Self::Basic(color) => Self::Basic(color.not_bright()),
            _ => self,
        }
    }
}

/// A color from the basic 16-color palette.
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BasicColor {
    Black,
    Red,
    Green,
    Yellow,
    Blue,
    Magenta,
    Cyan,
    White,

    BrightBlack,
    BrightRed,
    BrightGreen,
    BrightYellow,
    BrightBlue,
    BrightMagenta,
    BrightCyan,
    BrightWhite,
}

impl From<BasicColor> for Color {
    #[inline]
    fn from(color: BasicColor) -> Self {
        Self::Basic(color)
    }
}

impl From<u8> for Color {
    #[inline]
    fn from(color: u8) -> Self {
        Self::Extended(color)
    }
}

impl From<(u8, u8, u8)> for Color {
    #[inline]
    fn from(color: (u8, u8, u8)) -> Self {
        Self::Rgb(color.0, color.1, color.2)
    }
}

impl BasicColor {
    /// Return the bright variant of this color.
    #[inline]
    pub const fn bright(self) -> Self {
        match self {
            Self::Black => Self::BrightBlack,
            Self::Red => Self::BrightRed,
            Self::Green => Self::BrightGreen,
            Self::Yellow => Self::BrightYellow,
            Self::Blue => Self::BrightBlue,
            Self::Magenta => Self::BrightMagenta,
            Self::Cyan => Self::BrightCyan,
            Self::White => Self::BrightWhite,

            _ => self,
        }
    }

    /// Return the non-bright variant of this color.
    #[inline]
    pub const fn not_bright(self) -> Self {
        match self {
            Self::BrightBlack => Self::Black,
            Self::BrightRed => Self::Red,
            Self::BrightGreen => Self::Green,
            Self::BrightYellow => Self::Yellow,
            Self::BrightBlue => Self::Blue,
            Self::BrightMagenta => Self::Magenta,
            Self::BrightCyan => Self::Cyan,
            Self::BrightWhite => Self::White,

            _ => self,
        }
    }
}

/// Attributes such as bold, italic, etc.
///
/// Supports common bitwise operations.
///
/// Represented as a single byte bitfield.
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Attributes(u8);

impl Attributes {
    /// The bold attribute.
    pub const BOLD: Self = Self(1 << 0);

    /// The dim attribute.
    pub const DIM: Self = Self(1 << 1);

    /// The italic attribute.
    pub const ITALIC: Self = Self(1 << 2);

    /// The underlined attribute.
    pub const UNDERLINED: Self = Self(1 << 3);

    /// The blink attribute.
    pub const BLINK: Self = Self(1 << 4);

    /// The inverted attribute.
    pub const INVERTED: Self = Self(1 << 5);

    /// The hidden attribute.
    pub const HIDDEN: Self = Self(1 << 6);

    /// The strikethrough attribute.
    pub const STRIKETHROUGH: Self = Self(1 << 7);

    /// Create an empty attributes set.
    #[inline]
    pub const fn new() -> Self {
        Self(0)
    }

    #[inline]
    pub(crate) const fn into_u8(self) -> u8 {
        self.0
    }

    /// Returns `true` if `self` contains all the attributes of `other`.
    #[inline]
    pub const fn contains(self, other: Self) -> bool {
        self.0 & other.0 == other.0
    }

    /// Returns `true` if `self` is empty.
    #[inline]
    pub const fn is_empty(self) -> bool {
        self.0 == 0
    }

    /// Returns a new set which is the union of `self` and `other`.
    #[inline]
    pub const fn or(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }

    /// Returns a new set which is the intersection of `self` and `other`.
    #[inline]
    pub const fn and(self, other: Self) -> Self {
        Self(self.0 & other.0)
    }

    /// Returns a new set which is the complement of `self`.
    #[inline]
    pub const fn not(self) -> Self {
        Self(!self.0)
    }
}

impl fmt::Debug for Attributes {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut f = f.debug_set();

        macro_rules! impl_debug {
            ($($name:ident)*) => {
                $(
                    if self.contains(Attributes::$name) {
                        f.entry(&format_args!("{}", stringify!($name)));
                    }
                )*
            };
        }

        impl_debug! {
            BOLD
            DIM
            ITALIC
            UNDERLINED
            BLINK
            INVERTED
            HIDDEN
            STRIKETHROUGH
        }

        f.finish()
    }
}

impl BitOr<Attributes> for Attributes {
    type Output = Self;

    #[inline]
    fn bitor(self, rhs: Self) -> Self::Output {
        Self(self.0 | rhs.0)
    }
}

impl BitOrAssign for Attributes {
    #[inline]
    fn bitor_assign(&mut self, rhs: Self) {
        self.0 |= rhs.0;
    }
}

impl BitAnd<Attributes> for Attributes {
    type Output = Self;

    #[inline]
    fn bitand(self, rhs: Self) -> Self::Output {
        Self(self.0 & rhs.0)
    }
}

impl BitAndAssign for Attributes {
    #[inline]
    fn bitand_assign(&mut self, rhs: Self) {
        self.0 &= rhs.0;
    }
}

impl Not for Attributes {
    type Output = Self;

    #[inline]
    fn not(self) -> Self::Output {
        Self(!self.0)
    }
}

impl From<Attributes> for u8 {
    #[inline]
    fn from(value: Attributes) -> Self {
        value.0
    }
}