icmd 0.1.0

A command-line software framework
Documentation
//! Terminal styling and border patterns for ICmd.
//! This module provides traits and implementations for applying ANSI-based styles
//! to terminal text as well as defining reusable border patterns.

use std::fmt::Debug;

use color::AsBackground;

/// A trait for styles that can be applied to text in a terminal.
pub trait Style: Sized + Clone {
    /// Returns the prefix string for this style.
    fn prefix(&self) -> String;

    /// Combines this style with another style, returning a new style.
    fn and(self, other: impl Style) -> GeneralStyle {
        GeneralStyle::new(self.prefix() + &other.prefix()).into()
    }
}

/// A trait for color styles that can be applied to text in a terminal.
pub trait ColorStyle: Style {}

/// A trait for text styles that can be applied to text in a terminal.
pub trait TextStyle: Style {}

/// A struct representing a custom border style.
pub trait BorderPattern {
    fn get_top_left(&self) -> char;
    fn get_top_right(&self) -> char;
    fn get_bottom_left(&self) -> char;
    fn get_bottom_right(&self) -> char;
    fn get_top(&self) -> char;
    fn get_bottom(&self) -> char;
    fn get_left(&self) -> char;
    fn get_right(&self) -> char;
}

/// A struct representing a character or escaped control sequence in a terminal.
#[derive(Clone, Debug)]
pub struct TChar {
    pub char: char,
    pub control: String,
}

impl TChar {
    /// Create a new tchar from a char.
    pub fn new(c: char, ctrl: String) -> Self {
        TChar {
            char: c,
            control: ctrl,
        }
    }
}

/// A struct representing a string of tchars in a terminal.
#[derive(Debug, Clone)]
pub struct TString {
    pub chars: Vec<TChar>,
}

impl TString {
    /// Create a new empty TString.
    pub fn new() -> Self {
        TString { chars: Vec::new() }
    }

    /// Create a new TString from a string.
    pub fn from_str(s: &str, style: impl Into<GeneralStyle>) -> Self {
        let mut chars = Vec::new();
        let style = style.into().clone();
        for c in s.chars() {
            chars.push(TChar::new(c, style.prefix()));
        }
        TString { chars }
    }

    /// Convert a TString to a string.
    pub fn make_string(&self) -> String {
        self.chars
            .iter()
            .map(|c| format!("{}{}", c.control, c.char))
            .collect()
    }

    pub fn styled(&self, style: impl Into<GeneralStyle>) -> TString {
        let style = style.into().clone();
        let chars = self
            .chars
            .iter()
            .map(move |c| {
                let mut new_c = c.clone();
                new_c.control = style.prefix() + &new_c.control;
                new_c
            })
            .collect();
        TString { chars }
    }

    /// Returns the length of the string, counting only text characters.
    pub fn len(&self) -> usize {
        self.chars.len()
    }
}

#[derive(Debug, Clone)]
pub struct GeneralStyle {
    /// The prefix string for this style.
    pub prefix: String,
}

impl GeneralStyle {
    /// Create a new GeneralStyle with the given prefix.
    pub fn new(prefix: String) -> Self {
        GeneralStyle { prefix }
    }
}

impl GeneralStyle {
    pub fn from_style<T: Style>(style: T) -> Self {
        GeneralStyle::new(style.prefix())
    }
}

impl Style for GeneralStyle {
    /// Returns the prefix string for this style.
    fn prefix(&self) -> String {
        self.prefix.clone()
    }
}

#[derive(Debug, Clone)]
pub struct NoStyle;

impl Style for NoStyle {
    fn prefix(&self) -> String {
        String::new()
    }
}

impl TextStyle for NoStyle {}
impl ColorStyle for NoStyle {}

impl From<NoStyle> for GeneralStyle {
    fn from(_: NoStyle) -> Self {
        GeneralStyle::new(String::new())
    }
}

#[derive(Debug, Clone)]
pub struct RgbColor(pub u8, pub u8, pub u8);

impl Style for RgbColor {
    fn prefix(&self) -> String {
        format!("\x1b[38;2;{};{};{}m", self.0, self.1, self.2)
    }
}

impl From<RgbColor> for GeneralStyle {
    fn from(color: RgbColor) -> Self {
        GeneralStyle::new(color.prefix())
    }
}

impl ColorStyle for RgbColor {}

impl Style for AsBackground<RgbColor> {
    fn prefix(&self) -> String {
        format!("\x1b[48;2;{};{};{}m", self.0 .0, self.0 .1, self.0 .2)
    }
}

impl From<AsBackground<RgbColor>> for GeneralStyle {
    fn from(color: AsBackground<RgbColor>) -> Self {
        GeneralStyle::new(color.prefix())
    }
}

impl ColorStyle for AsBackground<RgbColor> {}

/// A macro to create a new TString from a string literal or format string.
#[macro_export]
macro_rules! tstr {
    ($s:expr) => {
        TString::from_str($s)
    };
    ($s:expr, $($args:tt)*) => {
        TString::from_str(format!($s, $($args)*))
    }
}

macro_rules! impl_style {
    ($name:ty, $prefix:expr) => {
        impl Style for $name {
            fn prefix(&self) -> String {
                $prefix.to_string()
            }
        }

        impl ColorStyle for $name {}

        impl From<$name> for GeneralStyle {
            fn from(_: $name) -> Self {
                GeneralStyle::new($prefix.to_string())
            }
        }
    };
}

macro_rules! new_style {
    ($name:ident, $prefix:expr) => {
        #[doc = " A style that can be applied to text in a terminal."]
        #[derive(Debug, Clone)]
        pub struct $name;
        impl Style for $name {
            fn prefix(&self) -> String {
                $prefix.to_string()
            }
        }

        impl From<$name> for GeneralStyle {
            fn from(_: $name) -> Self {
                GeneralStyle::new($prefix.to_string())
            }
        }
    };
    (color, $name:ident, $prefix:expr) => {
        new_style!($name, $prefix);
        impl ColorStyle for $name {}
    };
    (text, $name:ident, $prefix:expr) => {
        new_style!($name, $prefix);
        impl TextStyle for $name {}
    };
}

pub mod color {

    use super::*;

    /// Convert foreground color to background color.
    #[derive(Debug, Clone)]
    pub struct AsBackground<T: ColorStyle>(pub T);

    new_style!(color, Black, "\x1b[30m");
    new_style!(color, Red, "\x1b[31m");
    new_style!(color, Green, "\x1b[32m");
    new_style!(color, Yellow, "\x1b[33m");
    new_style!(color, Blue, "\x1b[34m");
    new_style!(color, Magenta, "\x1b[35m");
    new_style!(color, Cyan, "\x1b[36m");
    new_style!(color, White, "\x1b[37m");
    new_style!(color, Grey, "\x1b[38m");
    new_style!(color, LightBlack, "\x1b[90m");
    new_style!(color, LightRed, "\x1b[91m");
    new_style!(color, LightGreen, "\x1b[92m");
    new_style!(color, LightYellow, "\x1b[93m");
    new_style!(color, LightBlue, "\x1b[94m");
    new_style!(color, LightMagenta, "\x1b[95m");
    new_style!(color, LightCyan, "\x1b[96m");
    new_style!(color, LightWhite, "\x1b[97m");

    impl_style!(AsBackground<Black>, "\x1b[40m");
    impl_style!(AsBackground<Red>, "\x1b[41m");
    impl_style!(AsBackground<Green>, "\x1b[42m");
    impl_style!(AsBackground<Yellow>, "\x1b[43m");
    impl_style!(AsBackground<Blue>, "\x1b[44m");
    impl_style!(AsBackground<Magenta>, "\x1b[45m");
    impl_style!(AsBackground<Cyan>, "\x1b[46m");
    impl_style!(AsBackground<White>, "\x1b[47m");
    impl_style!(AsBackground<Grey>, "\x1b[48m");
    impl_style!(AsBackground<LightBlack>, "\x1b[100m");
    impl_style!(AsBackground<LightRed>, "\x1b[101m");
    impl_style!(AsBackground<LightGreen>, "\x1b[102m");
    impl_style!(AsBackground<LightYellow>, "\x1b[103m");
    impl_style!(AsBackground<LightBlue>, "\x1b[104m");
    impl_style!(AsBackground<LightMagenta>, "\x1b[105m");
    impl_style!(AsBackground<LightCyan>, "\x1b[106m");
    impl_style!(AsBackground<LightWhite>, "\x1b[107m");

    new_style!(text, Bold, "\x1b[1m");
    new_style!(text, Italic, "\x1b[3m");
    new_style!(text, Underline, "\x1b[4m");
    new_style!(text, Blink, "\x1b[5m");
    new_style!(text, Reverse, "\x1b[7m");
    new_style!(text, Hidden, "\x1b[8m");
    new_style!(text, Strikethrough, "\x1b[9m");
    new_style!(text, Reset, "\x1b[0m");
}
#[derive(Debug, Clone)]
pub struct GeneralBorderPattern {
    pub top_left: char,
    pub top_right: char,
    pub bottom_left: char,
    pub bottom_right: char,
    pub top: char,
    pub bottom: char,
    pub left: char,
    pub right: char,
}

impl BorderPattern for GeneralBorderPattern {
    fn get_top_left(&self) -> char {
        self.top_left
    }
    fn get_top_right(&self) -> char {
        self.top_right
    }
    fn get_bottom_left(&self) -> char {
        self.bottom_left
    }
    fn get_bottom_right(&self) -> char {
        self.bottom_right
    }
    fn get_top(&self) -> char {
        self.top
    }
    fn get_bottom(&self) -> char {
        self.bottom
    }
    fn get_left(&self) -> char {
        self.left
    }
    fn get_right(&self) -> char {
        self.right
    }
}

/// Macro to define a new border pattern.
///
/// This macro creates a struct with the given name and implements the BorderPattern
/// trait for it using the provided characters for each border side.
macro_rules! border_pattern {
    ($name:ident, $top_left:expr, $top_right:expr, $bottom_left:expr, $bottom_right:expr, $top:expr, $bottom:expr, $left:expr, $right:expr) => {
        pub struct $name;
        impl BorderPattern for $name {
            fn get_top_left(&self) -> char {
                $top_left
            }
            fn get_top_right(&self) -> char {
                $top_right
            }
            fn get_bottom_left(&self) -> char {
                $bottom_left
            }
            fn get_bottom_right(&self) -> char {
                $bottom_right
            }
            fn get_top(&self) -> char {
                $top
            }
            fn get_bottom(&self) -> char {
                $bottom
            }
            fn get_left(&self) -> char {
                $left
            }
            fn get_right(&self) -> char {
                $right
            }
        }
        impl From<$name> for GeneralBorderPattern {
            fn from(border: $name) -> Self {
                GeneralBorderPattern {
                    top_left: border.get_top_left(),
                    top_right: border.get_top_right(),
                    bottom_left: border.get_bottom_left(),
                    bottom_right: border.get_bottom_right(),
                    top: border.get_top(),
                    bottom: border.get_bottom(),
                    left: border.get_left(),
                    right: border.get_right(),
                }
            }
        }
    };
}

pub mod pattern {
    use super::*;
    #[rustfmt::skip] border_pattern!(NoBorder, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ');
    #[rustfmt::skip] border_pattern!(DefaultBorder, '', '', '', '', '', '', '', '');
    #[rustfmt::skip] border_pattern!(DoubleBorder, '', '', '', '', '', '', '', '');
    #[rustfmt::skip] border_pattern!(DottedBorder, '.', '.', '.', '.', '.', '.', '.', '.');
    #[rustfmt::skip] border_pattern!(DashedBorder, '', '', '', '', '-', '-', '', '');
    #[rustfmt::skip] border_pattern!(SolidBorder, '', '', '', '', '', '', '', '');
    #[rustfmt::skip] border_pattern!(SlashBorder, '/', '/', '/', '/', '/', '/', '/', '/');
    #[rustfmt::skip] border_pattern!(BackSlashBorder, '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\');
    #[rustfmt::skip] border_pattern!(TripleDottedBorder, '', '', '', '', '', '', '', '');
}