use crate::color::Color;
use std::fmt;
pub const RESET: &str = "\x1b[0m";
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct Attributes {
bold: bool,
dim: bool,
italic: bool,
underline: bool,
blink: bool,
reverse: bool,
hidden: bool,
strikethrough: bool,
}
#[derive(Debug, Clone)]
pub struct StyledString {
text: String,
fg: Option<Color>,
bg: Option<Color>,
attrs: Attributes,
}
macro_rules! inherent_color_method {
($name:ident, $doc:expr, $color:expr) => {
#[doc = $doc]
pub fn $name(self) -> StyledString {
self.color($color)
}
};
}
macro_rules! inherent_bg_method {
($name:ident, $doc:expr, $color:expr) => {
#[doc = $doc]
pub fn $name(self) -> StyledString {
self.on_color($color)
}
};
}
impl StyledString {
pub fn new(text: impl Into<String>) -> Self {
StyledString {
text: text.into(),
fg: None,
bg: None,
attrs: Attributes::default(),
}
}
pub fn color(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
pub fn on_color(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub fn bold(mut self) -> Self {
self.attrs.bold = true;
self
}
pub fn dim(mut self) -> Self {
self.attrs.dim = true;
self
}
pub fn italic(mut self) -> Self {
self.attrs.italic = true;
self
}
pub fn underline(mut self) -> Self {
self.attrs.underline = true;
self
}
pub fn blink(mut self) -> Self {
self.attrs.blink = true;
self
}
pub fn reverse(mut self) -> Self {
self.attrs.reverse = true;
self
}
pub fn hidden(mut self) -> Self {
self.attrs.hidden = true;
self
}
pub fn strikethrough(mut self) -> Self {
self.attrs.strikethrough = true;
self
}
pub fn fixed(self, n: u8) -> Self {
self.color(Color::Fixed(n))
}
pub fn rgb(self, r: u8, g: u8, b: u8) -> Self {
self.color(Color::Rgb(r, g, b))
}
pub fn on_rgb(self, r: u8, g: u8, b: u8) -> Self {
self.on_color(Color::Rgb(r, g, b))
}
inherent_color_method!(black, "Sets foreground to black.", Color::Black);
inherent_color_method!(red, "Sets foreground to red.", Color::Red);
inherent_color_method!(green, "Sets foreground to green.", Color::Green);
inherent_color_method!(yellow, "Sets foreground to yellow.", Color::Yellow);
inherent_color_method!(blue, "Sets foreground to blue.", Color::Blue);
inherent_color_method!(magenta, "Sets foreground to magenta.", Color::Magenta);
inherent_color_method!(cyan, "Sets foreground to cyan.", Color::Cyan);
inherent_color_method!(white, "Sets foreground to white.", Color::White);
inherent_color_method!(bright_black, "Sets foreground to bright black.", Color::BrightBlack);
inherent_color_method!(bright_red, "Sets foreground to bright red.", Color::BrightRed);
inherent_color_method!(bright_green, "Sets foreground to bright green.", Color::BrightGreen);
inherent_color_method!(bright_yellow, "Sets foreground to bright yellow.", Color::BrightYellow);
inherent_color_method!(bright_blue, "Sets foreground to bright blue.", Color::BrightBlue);
inherent_color_method!(bright_magenta, "Sets foreground to bright magenta.", Color::BrightMagenta);
inherent_color_method!(bright_cyan, "Sets foreground to bright cyan.", Color::BrightCyan);
inherent_color_method!(bright_white, "Sets foreground to bright white.", Color::BrightWhite);
inherent_bg_method!(on_black, "Sets background to black.", Color::Black);
inherent_bg_method!(on_red, "Sets background to red.", Color::Red);
inherent_bg_method!(on_green, "Sets background to green.", Color::Green);
inherent_bg_method!(on_yellow, "Sets background to yellow.", Color::Yellow);
inherent_bg_method!(on_blue, "Sets background to blue.", Color::Blue);
inherent_bg_method!(on_magenta, "Sets background to magenta.", Color::Magenta);
inherent_bg_method!(on_cyan, "Sets background to cyan.", Color::Cyan);
inherent_bg_method!(on_white, "Sets background to white.", Color::White);
inherent_bg_method!(on_bright_black, "Sets background to bright black.", Color::BrightBlack);
inherent_bg_method!(on_bright_red, "Sets background to bright red.", Color::BrightRed);
inherent_bg_method!(on_bright_green, "Sets background to bright green.", Color::BrightGreen);
inherent_bg_method!(on_bright_yellow, "Sets background to bright yellow.", Color::BrightYellow);
inherent_bg_method!(on_bright_blue, "Sets background to bright blue.", Color::BrightBlue);
inherent_bg_method!(on_bright_magenta, "Sets background to bright magenta.", Color::BrightMagenta);
inherent_bg_method!(on_bright_cyan, "Sets background to bright cyan.", Color::BrightCyan);
inherent_bg_method!(on_bright_white, "Sets background to bright white.", Color::BrightWhite);
fn build_prefix(&self) -> String {
let mut codes: Vec<String> = Vec::new();
if self.attrs.bold {
codes.push("1".into());
}
if self.attrs.dim {
codes.push("2".into());
}
if self.attrs.italic {
codes.push("3".into());
}
if self.attrs.underline {
codes.push("4".into());
}
if self.attrs.blink {
codes.push("5".into());
}
if self.attrs.reverse {
codes.push("7".into());
}
if self.attrs.hidden {
codes.push("8".into());
}
if self.attrs.strikethrough {
codes.push("9".into());
}
if let Some(fg) = self.fg {
codes.push(fg.fg_code());
}
if let Some(bg) = self.bg {
codes.push(bg.bg_code());
}
if codes.is_empty() {
String::new()
} else {
format!("\x1b[{}m", codes.join(";"))
}
}
}
impl fmt::Display for StyledString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let prefix = self.build_prefix();
if prefix.is_empty() {
write!(f, "{}", self.text)
} else {
write!(f, "{}{}{}", prefix, self.text, RESET)
}
}
}
macro_rules! trait_color_method {
($name:ident, $doc:expr, $color:expr) => {
#[doc = $doc]
fn $name(self) -> StyledString {
self.stylize().color($color)
}
};
}
macro_rules! trait_bg_method {
($name:ident, $doc:expr, $color:expr) => {
#[doc = $doc]
fn $name(self) -> StyledString {
self.stylize().on_color($color)
}
};
}
pub trait Stylize: Sized {
fn stylize(self) -> StyledString;
fn color(self, color: Color) -> StyledString {
self.stylize().color(color)
}
fn on_color(self, color: Color) -> StyledString {
self.stylize().on_color(color)
}
fn bold(self) -> StyledString {
self.stylize().bold()
}
fn dim(self) -> StyledString {
self.stylize().dim()
}
fn italic(self) -> StyledString {
self.stylize().italic()
}
fn underline(self) -> StyledString {
self.stylize().underline()
}
fn blink(self) -> StyledString {
self.stylize().blink()
}
fn reverse(self) -> StyledString {
self.stylize().reverse()
}
fn hidden(self) -> StyledString {
self.stylize().hidden()
}
fn strikethrough(self) -> StyledString {
self.stylize().strikethrough()
}
fn fixed(self, n: u8) -> StyledString {
self.stylize().color(Color::Fixed(n))
}
fn rgb(self, r: u8, g: u8, b: u8) -> StyledString {
self.stylize().color(Color::Rgb(r, g, b))
}
fn on_rgb(self, r: u8, g: u8, b: u8) -> StyledString {
self.stylize().on_color(Color::Rgb(r, g, b))
}
trait_color_method!(black, "Sets foreground to black.", Color::Black);
trait_color_method!(red, "Sets foreground to red.", Color::Red);
trait_color_method!(green, "Sets foreground to green.", Color::Green);
trait_color_method!(yellow, "Sets foreground to yellow.", Color::Yellow);
trait_color_method!(blue, "Sets foreground to blue.", Color::Blue);
trait_color_method!(magenta, "Sets foreground to magenta.", Color::Magenta);
trait_color_method!(cyan, "Sets foreground to cyan.", Color::Cyan);
trait_color_method!(white, "Sets foreground to white.", Color::White);
trait_color_method!(bright_black, "Sets foreground to bright black.", Color::BrightBlack);
trait_color_method!(bright_red, "Sets foreground to bright red.", Color::BrightRed);
trait_color_method!(bright_green, "Sets foreground to bright green.", Color::BrightGreen);
trait_color_method!(bright_yellow, "Sets foreground to bright yellow.", Color::BrightYellow);
trait_color_method!(bright_blue, "Sets foreground to bright blue.", Color::BrightBlue);
trait_color_method!(bright_magenta, "Sets foreground to bright magenta.", Color::BrightMagenta);
trait_color_method!(bright_cyan, "Sets foreground to bright cyan.", Color::BrightCyan);
trait_color_method!(bright_white, "Sets foreground to bright white.", Color::BrightWhite);
trait_bg_method!(on_black, "Sets background to black.", Color::Black);
trait_bg_method!(on_red, "Sets background to red.", Color::Red);
trait_bg_method!(on_green, "Sets background to green.", Color::Green);
trait_bg_method!(on_yellow, "Sets background to yellow.", Color::Yellow);
trait_bg_method!(on_blue, "Sets background to blue.", Color::Blue);
trait_bg_method!(on_magenta, "Sets background to magenta.", Color::Magenta);
trait_bg_method!(on_cyan, "Sets background to cyan.", Color::Cyan);
trait_bg_method!(on_white, "Sets background to white.", Color::White);
trait_bg_method!(on_bright_black, "Sets background to bright black.", Color::BrightBlack);
trait_bg_method!(on_bright_red, "Sets background to bright red.", Color::BrightRed);
trait_bg_method!(on_bright_green, "Sets background to bright green.", Color::BrightGreen);
trait_bg_method!(on_bright_yellow, "Sets background to bright yellow.", Color::BrightYellow);
trait_bg_method!(on_bright_blue, "Sets background to bright blue.", Color::BrightBlue);
trait_bg_method!(on_bright_magenta, "Sets background to bright magenta.", Color::BrightMagenta);
trait_bg_method!(on_bright_cyan, "Sets background to bright cyan.", Color::BrightCyan);
trait_bg_method!(on_bright_white, "Sets background to bright white.", Color::BrightWhite);
}
impl Stylize for &str {
fn stylize(self) -> StyledString {
StyledString::new(self)
}
}
impl Stylize for String {
fn stylize(self) -> StyledString {
StyledString::new(self)
}
}
pub fn strip_ansi(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
if c == '\x1b' {
if let Some('[') = chars.peek() {
chars.next();
for next in chars.by_ref() {
if next.is_ascii_alphabetic() {
break;
}
}
}
} else {
result.push(c);
}
}
result
}
pub fn parse_styled_components(input: &str) -> Vec<StyledString> {
use crate::Stylize;
let mut segments = Vec::new();
let mut chars = input.chars().peekable();
let mut style_stack: Vec<String> = Vec::new();
while let Some(c) = chars.next() {
if c == '[' {
let mut tag = String::new();
while let Some(&next_c) = chars.peek() {
chars.next();
if next_c == ']' {
break;
}
tag.push(next_c);
}
if tag == "/" || tag == format!("/{}", style_stack.last().unwrap_or(&" ".to_string())) {
style_stack.pop();
} else {
style_stack.push(tag);
}
} else {
let mut text = String::new();
text.push(c);
while let Some(&next_c) = chars.peek() {
if next_c == '[' {
break;
}
text.push(chars.next().unwrap());
}
let mut styled = text.stylize();
for style_tag in &style_stack {
styled = match style_tag.as_str() {
"black" => styled.black(),
"red" => styled.red(),
"green" => styled.green(),
"yellow" => styled.yellow(),
"blue" => styled.blue(),
"magenta" => styled.magenta(),
"cyan" => styled.cyan(),
"white" => styled.white(),
"bright_black" => styled.bright_black(),
"bright_red" => styled.bright_red(),
"bright_green" => styled.bright_green(),
"bright_yellow" => styled.bright_yellow(),
"bright_blue" => styled.bright_blue(),
"bright_magenta" => styled.bright_magenta(),
"bright_cyan" => styled.bright_cyan(),
"bright_white" => styled.bright_white(),
"on_black" => styled.on_black(),
"on_red" => styled.on_red(),
"on_green" => styled.on_green(),
"on_yellow" => styled.on_yellow(),
"on_blue" => styled.on_blue(),
"on_magenta" => styled.on_magenta(),
"on_cyan" => styled.on_cyan(),
"on_white" => styled.on_white(),
"on_bright_black" => styled.on_bright_black(),
"on_bright_red" => styled.on_bright_red(),
"on_bright_green" => styled.on_bright_green(),
"on_bright_yellow" => styled.on_bright_yellow(),
"on_bright_blue" => styled.on_bright_blue(),
"on_bright_magenta" => styled.on_bright_magenta(),
"on_bright_cyan" => styled.on_bright_cyan(),
"on_bright_white" => styled.on_bright_white(),
"bold" => styled.bold(),
"dim" => styled.dim(),
"italic" => styled.italic(),
"underline" => styled.underline(),
"blink" => styled.blink(),
"reverse" => styled.reverse(),
"hidden" => styled.hidden(),
"strikethrough" => styled.strikethrough(),
_ => styled,
};
}
segments.push(styled);
}
}
segments
}
pub fn render_styled_components(styled: Vec<StyledString>) -> String {
styled.iter().render()
}
pub fn parse_styled(input: &str) -> String {
render_styled_components(parse_styled_components(input))
}
pub trait Renderable {
fn render(self) -> String;
}
impl<'a, I> Renderable for I
where
I: Iterator<Item = &'a StyledString>,
{
fn render(self) -> String {
self.map(ToString::to_string).collect()
}
}
#[macro_export]
macro_rules! style {
($($arg:tt)*) => {
$crate::style::parse_styled(&format!($($arg)*))
};
}
#[macro_export]
macro_rules! paint {
($($arg:tt)*) => {
print!("\x1b[0m{}", $crate::style!($($arg)*))
};
}
#[macro_export]
macro_rules! paintln {
($($arg:tt)*) => {
{
$crate::paint!($($arg)*);
std::println!("")
}
};
}