use core::fmt::{Display, Formatter, Result};
use crate::{GetFlags, Style, StyleElement, StyleSet, ToStyleSet};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Styled<C: Display> {
content: C,
style: Style,
}
impl<C: Display> Styled<C> {
#[must_use]
pub const fn new(content: C) -> Self {
Self {
content,
style: Style::new(),
}
}
#[must_use]
pub const fn get_content(&self) -> &C {
&self.content
}
#[must_use]
pub const fn with_content<C2: Display>(&self, content: C2) -> Styled<C2> {
Styled {
content,
style: self.style,
}
}
#[must_use]
pub fn into_content(self) -> C {
self.content
}
#[must_use]
pub const fn get_style(&self) -> Style {
self.style
}
#[must_use]
pub fn with_style(self, style: Style) -> Styled<C> {
Self { style, ..self }
}
}
impl<C: Display> ToStyleSet for Styled<C> {
type StyleSet = Self;
fn add(self, element: impl StyleElement) -> Self::StyleSet {
let style = self.style.add(element);
self.with_style(style)
}
fn to_style_set(self) -> Self::StyleSet {
self
}
}
impl<C: Display> StyleSet for Styled<C> {
fn get_flags(&self) -> GetFlags<'_> {
self.style.get_flags()
}
fn set<A: crate::StyleAttribute>(self, attr: A, value: A::Value) -> Self {
let style = self.style.set(attr, value);
self.with_style(style)
}
fn get<A: crate::StyleAttribute>(&self, attr: A) -> A::Value {
self.style.get(attr)
}
}
impl<C: Display> Display for Styled<C> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
if self.style == Style::default() {
write!(f, "{}", self.content)
} else {
let start = self.style;
let end = Style::default();
write!(f, "{start}{}{end}", self.content)
}
}
}
#[cfg(test)]
mod tests {
use crate::{
ColorInAPlane, Flag, Plane, assert_display,
color::{BasicColor, Color, ColorKind as _},
};
use super::*;
#[test]
fn content_and_style() {
let stld = Styled::new("CONTENT").bold();
assert_eq!(stld.get_content(), &"CONTENT");
assert_eq!(stld.get_style(), Style::new().bold());
let stld = stld.bold().with_content("NEW CONTENT");
assert_eq!(stld.get_content(), &"NEW CONTENT");
assert_eq!(stld.get_style(), Style::new().bold());
let stld = stld.with_style(Style::new().fg(BasicColor::Red));
assert_eq!(stld.get_content(), &"NEW CONTENT");
assert_eq!(stld.get_style(), Style::new().fg(BasicColor::Red));
let content = stld.into_content();
assert_eq!(content, "NEW CONTENT");
}
#[test]
fn display_no_style() {
let stld = Styled::new("CONTENT");
assert_display!(stld, "CONTENT");
}
#[test]
fn add_flag() {
let stld = Styled::new("CONTENT");
assert_display!(stld, "CONTENT");
assert_display!(stld.bold(), "\x1b[1mCONTENT\x1b[0m");
assert_display!(stld.faint(), "\x1b[2mCONTENT\x1b[0m");
assert_display!(stld.italic(), "\x1b[3mCONTENT\x1b[0m");
assert_display!(stld.underline(), "\x1b[4mCONTENT\x1b[0m");
assert_display!(stld.slow_blink(), "\x1b[5mCONTENT\x1b[0m");
assert_display!(stld.rapid_blink(), "\x1b[6mCONTENT\x1b[0m");
assert_display!(stld.reverse(), "\x1b[7mCONTENT\x1b[0m");
assert_display!(stld.conceal(), "\x1b[8mCONTENT\x1b[0m");
assert_display!(stld.crossed_out(), "\x1b[9mCONTENT\x1b[0m");
assert_display!(stld.double_underline(), "\x1b[21mCONTENT\x1b[0m");
assert_display!(stld.overline(), "\x1b[53mCONTENT\x1b[0m");
let bold_stld = stld.bold();
assert_eq!(bold_stld.flag(Flag::Faint), stld.bold().faint());
assert_eq!(bold_stld.add(Flag::Faint), stld.bold().faint());
assert_eq!(bold_stld.set_flag(Flag::Bold, false), stld);
assert_eq!(bold_stld.set_flag(Flag::Bold, true), stld.bold());
assert_eq!(bold_stld.set_flag(Flag::Faint, false), stld.bold());
assert_eq!(bold_stld.set_flag(Flag::Faint, true), stld.bold().faint());
assert_eq!(bold_stld.get_flag(Flag::Bold), true);
assert_eq!(bold_stld.get_flag(Flag::Faint), false);
assert_eq!(bold_stld.set(Flag::Bold, false), stld);
assert_eq!(bold_stld.set(Flag::Bold, true), stld.bold());
assert_eq!(bold_stld.set(Flag::Faint, false), stld.bold());
assert_eq!(bold_stld.set(Flag::Faint, true), stld.bold().faint());
assert_eq!(bold_stld.get(Flag::Bold), true);
assert_eq!(bold_stld.get(Flag::Faint), false);
assert_eq!(bold_stld.unset(Flag::Bold), stld);
assert_eq!(bold_stld.unset(Flag::Faint), stld.bold());
}
#[test]
fn get_flags() {
let style = Style::new().bold().italic().underline();
let mut flags = style.get_flags();
assert_eq!(flags.next(), Some(Flag::Bold));
assert_eq!(flags.next(), Some(Flag::Italic));
assert_eq!(flags.next(), Some(Flag::Underline));
assert_eq!(flags.next(), None);
}
#[test]
fn fg() {
let stld = Styled::new("CONTENT");
assert_eq!(stld.get_color(Plane::Foreground), None);
let stld = stld.fg(BasicColor::Red);
assert_display!(stld, "\x1b[31mCONTENT\x1b[0m");
assert_eq!(
stld.get_color(Plane::Foreground),
Some(BasicColor::Red.to_color())
);
}
#[test]
fn bg() {
let stld = Styled::new("CONTENT");
assert_eq!(stld.get_color(Plane::Background), None);
let stld = stld.bg(BasicColor::Red);
assert_display!(stld, "\x1b[41mCONTENT\x1b[0m");
assert_eq!(
stld.get_color(Plane::Background),
Some(BasicColor::Red.to_color())
);
}
#[test]
fn add_color() {
let stld_base = Styled::new("CONTENT");
assert_eq!(stld_base.get_color(Plane::Foreground), None);
assert_eq!(stld_base.get_color(Plane::Background), None);
let stld = stld_base.fg(BasicColor::Red).bg(BasicColor::Green);
assert_eq!(
stld.get_color(Plane::Foreground),
Some(BasicColor::Red.to_color())
);
assert_eq!(
stld.get_color(Plane::Background),
Some(BasicColor::Green.to_color())
);
let stld = stld_base
.color(ColorInAPlane::new(BasicColor::Yellow, Plane::Foreground))
.color(ColorInAPlane::new(BasicColor::Blue, Plane::Background));
assert_eq!(
stld.get_color(Plane::Foreground),
Some(BasicColor::Yellow.to_color())
);
assert_eq!(
stld.get_color(Plane::Background),
Some(BasicColor::Blue.to_color())
);
let stld = stld_base
.add(ColorInAPlane::new(BasicColor::White, Plane::Foreground))
.add(ColorInAPlane::new(BasicColor::Black, Plane::Background));
assert_eq!(
stld.get_color(Plane::Foreground),
Some(BasicColor::White.to_color())
);
assert_eq!(
stld.get_color(Plane::Background),
Some(BasicColor::Black.to_color())
);
let stld = stld
.set_color(Plane::Foreground, Some(BasicColor::Magenta))
.set_color(Plane::Background, None::<Color>);
assert_eq!(
stld.get_color(Plane::Foreground),
Some(BasicColor::Magenta.to_color())
);
assert_eq!(stld.get_color(Plane::Background), None);
let stld = stld
.set_color(Plane::Foreground, None::<Color>)
.set_color(Plane::Background, Some(BasicColor::Cyan));
assert_eq!(stld.get_color(Plane::Foreground), None);
assert_eq!(
stld.get_color(Plane::Background),
Some(BasicColor::Cyan.to_color())
);
let stld = stld
.set(Plane::Foreground, Some(BasicColor::Magenta.to_color()))
.set(Plane::Background, None);
assert_eq!(
stld.get(Plane::Foreground),
Some(BasicColor::Magenta.to_color())
);
assert_eq!(stld.get(Plane::Background), None);
let stld = stld
.set(Plane::Foreground, None)
.set(Plane::Background, Some(BasicColor::Cyan.to_color()));
assert_eq!(stld.get(Plane::Foreground), None);
assert_eq!(
stld.get(Plane::Background),
Some(BasicColor::Cyan.to_color())
);
let stld = stld.unset(Plane::Background);
assert_eq!(stld.get(Plane::Foreground), None);
assert_eq!(stld.get(Plane::Background), None);
}
#[test]
fn combined() {
let stld = Styled::new("CONTENT")
.bold()
.fg(BasicColor::Red)
.underline()
.bg(BasicColor::Green);
assert_eq!(
stld.get_style(),
Style::new()
.bold()
.fg(BasicColor::Red)
.underline()
.bg(BasicColor::Green)
);
assert_display!(stld, "\x1b[1;4;31;42mCONTENT\x1b[0m");
assert_eq!(
stld.unset(Flag::Bold).unset(Plane::Background).get_style(),
Style::new().underline().fg(BasicColor::Red)
)
}
#[test]
fn to_style_set() {
let stld = Styled::new("CONTENT").bold().fg(BasicColor::Red);
assert_eq!(stld.to_style_set(), stld);
}
}