use crate::{AnsiColors, Color, DynColor, DynColors};
use core::fmt;
#[cfg(doc)]
use crate::OwoColorize;
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone)]
pub enum Effect {
Bold,
Dimmed,
Italic,
Underline,
Blink,
BlinkFast,
Reversed,
Hidden,
Strikethrough,
}
macro_rules! color_methods {
($(
#[$fg_meta:meta] #[$bg_meta:meta] $color:ident $fg_method:ident $bg_method:ident
),* $(,)?) => {
$(
#[$fg_meta]
#[must_use]
pub const fn $fg_method(mut self) -> Self {
self.fg = Some(DynColors::Ansi(AnsiColors::$color));
self
}
#[$fg_meta]
#[must_use]
pub const fn $bg_method(mut self) -> Self {
self.bg = Some(DynColors::Ansi(AnsiColors::$color));
self
}
)*
};
}
macro_rules! style_methods {
($(#[$meta:meta] ($name:ident, $set_name:ident)),* $(,)?) => {
$(
#[$meta]
#[must_use]
pub const fn $name(mut self) -> Self {
self.style_flags.$set_name(true);
self
}
)*
};
}
const _: () = ();
pub struct Styled<T> {
pub(crate) target: T,
pub style: Style,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Style {
pub(crate) fg: Option<DynColors>,
pub(crate) bg: Option<DynColors>,
pub(crate) bold: bool,
pub(crate) style_flags: StyleFlags,
}
#[repr(transparent)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) struct StyleFlags(pub(crate) u8);
impl StyleFlags {
#[must_use]
#[inline]
const fn is_plain(&self) -> bool {
self.0 == 0
}
}
const DIMMED_SHIFT: u8 = 0;
const ITALIC_SHIFT: u8 = 1;
const UNDERLINE_SHIFT: u8 = 2;
const BLINK_SHIFT: u8 = 3;
const BLINK_FAST_SHIFT: u8 = 4;
const REVERSED_SHIFT: u8 = 5;
const HIDDEN_SHIFT: u8 = 6;
const STRIKETHROUGH_SHIFT: u8 = 7;
macro_rules! style_flags_methods {
($(($shift:ident, $name:ident, $set_name:ident)),* $(,)?) => {
$(
#[must_use]
const fn $name(&self) -> bool {
((self.0 >> $shift) & 1) != 0
}
const fn $set_name(&mut self, $name: bool) {
self.0 = (self.0 & !(1 << $shift)) | (($name as u8) << $shift);
}
)*
};
}
impl StyleFlags {
const fn new() -> Self {
Self(0)
}
style_flags_methods! {
(DIMMED_SHIFT, dimmed, set_dimmed),
(ITALIC_SHIFT, italic, set_italic),
(UNDERLINE_SHIFT, underline, set_underline),
(BLINK_SHIFT, blink, set_blink),
(BLINK_FAST_SHIFT, blink_fast, set_blink_fast),
(REVERSED_SHIFT, reversed, set_reversed),
(HIDDEN_SHIFT, hidden, set_hidden),
(STRIKETHROUGH_SHIFT, strikethrough, set_strikethrough),
}
}
impl Default for StyleFlags {
fn default() -> Self {
Self::new()
}
}
impl Style {
#[must_use]
pub const fn new() -> Self {
Self {
fg: None,
bg: None,
bold: false,
style_flags: StyleFlags::new(),
}
}
pub const fn style<T>(&self, target: T) -> Styled<T> {
Styled {
target,
style: *self,
}
}
#[must_use]
pub const fn fg<C: Color>(mut self) -> Self {
self.fg = Some(C::DYN_COLORS_EQUIVALENT);
self
}
#[must_use]
pub const fn bg<C: Color>(mut self) -> Self {
self.bg = Some(C::DYN_COLORS_EQUIVALENT);
self
}
#[must_use]
pub const fn remove_fg(mut self) -> Self {
self.fg = None;
self
}
#[must_use]
pub const fn remove_bg(mut self) -> Self {
self.bg = None;
self
}
color_methods! {
Black black on_black,
Red red on_red,
Green green on_green,
Yellow yellow on_yellow,
Blue blue on_blue,
Magenta magenta on_magenta,
Magenta purple on_purple,
Cyan cyan on_cyan,
White white on_white,
Default default_color on_default_color,
BrightBlack bright_black on_bright_black,
BrightRed bright_red on_bright_red,
BrightGreen bright_green on_bright_green,
BrightYellow bright_yellow on_bright_yellow,
BrightBlue bright_blue on_bright_blue,
BrightMagenta bright_magenta on_bright_magenta,
BrightMagenta bright_purple on_bright_purple,
BrightCyan bright_cyan on_bright_cyan,
BrightWhite bright_white on_bright_white,
}
#[must_use]
pub const fn bold(mut self) -> Self {
self.bold = true;
self
}
style_methods! {
(dimmed, set_dimmed),
(italic, set_italic),
(underline, set_underline),
(blink, set_blink),
(blink_fast, set_blink_fast),
(reversed, set_reversed),
(hidden, set_hidden),
(strikethrough, set_strikethrough),
}
#[must_use]
const fn set_effect(mut self, effect: Effect, to: bool) -> Self {
use Effect::*;
match effect {
Bold => {
self.bold = to;
}
Dimmed => {
self.style_flags.set_dimmed(to);
}
Italic => {
self.style_flags.set_italic(to);
}
Underline => {
self.style_flags.set_underline(to);
}
Blink => {
self.style_flags.set_blink(to);
}
BlinkFast => {
self.style_flags.set_blink_fast(to);
}
Reversed => {
self.style_flags.set_reversed(to);
}
Hidden => {
self.style_flags.set_hidden(to);
}
Strikethrough => {
self.style_flags.set_strikethrough(to);
}
}
self
}
#[must_use]
const fn set_effects(mut self, mut effects: &[Effect], to: bool) -> Self {
while let [first, rest @ ..] = effects {
self = self.set_effect(*first, to);
effects = rest;
}
self
}
#[must_use]
pub const fn effect(self, effect: Effect) -> Self {
self.set_effect(effect, true)
}
#[must_use]
pub const fn remove_effect(self, effect: Effect) -> Self {
self.set_effect(effect, false)
}
#[must_use]
pub const fn effects(self, effects: &[Effect]) -> Self {
self.set_effects(effects, true)
}
#[must_use]
pub const fn remove_effects(self, effects: &[Effect]) -> Self {
self.set_effects(effects, false)
}
#[must_use]
pub const fn remove_all_effects(mut self) -> Self {
self.bold = false;
self.style_flags = StyleFlags::new();
self
}
#[must_use]
pub fn color<Color: DynColor>(mut self, color: Color) -> Self {
self.fg = Some(color.get_dyncolors_fg());
self
}
#[must_use]
pub fn on_color<Color: DynColor>(mut self, color: Color) -> Self {
self.bg = Some(color.get_dyncolors_bg());
self
}
#[must_use]
pub const fn fg_rgb<const R: u8, const G: u8, const B: u8>(mut self) -> Self {
self.fg = Some(DynColors::Rgb(R, G, B));
self
}
#[must_use]
pub const fn bg_rgb<const R: u8, const G: u8, const B: u8>(mut self) -> Self {
self.bg = Some(DynColors::Rgb(R, G, B));
self
}
#[must_use]
pub const fn truecolor(mut self, r: u8, g: u8, b: u8) -> Self {
self.fg = Some(DynColors::Rgb(r, g, b));
self
}
#[must_use]
pub const fn on_truecolor(mut self, r: u8, g: u8, b: u8) -> Self {
self.bg = Some(DynColors::Rgb(r, g, b));
self
}
#[must_use]
#[inline]
pub const fn is_plain(&self) -> bool {
let s = &self;
!(s.fg.is_some() || s.bg.is_some() || s.bold) && s.style_flags.is_plain()
}
pub const fn prefix_formatter(&self) -> StylePrefixFormatter {
StylePrefixFormatter(*self)
}
pub const fn suffix_formatter(&self) -> StyleSuffixFormatter {
StyleSuffixFormatter(*self)
}
#[inline]
#[allow(unused_assignments)]
pub fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = self;
let format_less_important_effects = s.style_flags != StyleFlags::default();
let format_effect = s.bold || format_less_important_effects;
let format_any = !self.is_plain();
let mut semicolon = false;
if format_any {
f.write_str("\x1b[")?;
}
if let Some(fg) = s.fg {
<DynColors as DynColor>::fmt_raw_ansi_fg(&fg, f)?;
semicolon = true;
}
if let Some(bg) = s.bg {
if s.fg.is_some() {
f.write_str(";")?;
}
<DynColors as DynColor>::fmt_raw_ansi_bg(&bg, f)?;
semicolon = true;
}
if format_effect {
if s.bold {
if semicolon {
f.write_str(";")?;
}
f.write_str("1")?;
semicolon = true;
}
macro_rules! text_effect_fmt {
($style:ident, $formatter:ident, $semicolon:ident, $(($attr:ident, $value:literal)),* $(,)?) => {
$(
if $style.style_flags.$attr() {
if $semicolon {
$formatter.write_str(";")?;
}
$formatter.write_str($value)?;
$semicolon = true;
}
)+
}
}
if format_less_important_effects {
text_effect_fmt! {
s, f, semicolon,
(dimmed, "2"),
(italic, "3"),
(underline, "4"),
(blink, "5"),
(blink_fast, "6"),
(reversed, "7"),
(hidden, "8"),
(strikethrough, "9"),
}
}
}
if format_any {
f.write_str("m")?;
}
Ok(())
}
#[inline]
pub fn fmt_suffix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.is_plain() {
f.write_str("\x1b[0m")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[must_use = "this formatter does nothing unless displayed"]
pub struct StylePrefixFormatter(Style);
impl fmt::Display for StylePrefixFormatter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt_prefix(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[must_use = "this formatter does nothing unless displayed"]
pub struct StyleSuffixFormatter(Style);
impl fmt::Display for StyleSuffixFormatter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt_suffix(f)
}
}
impl Default for Style {
fn default() -> Self {
Self::new()
}
}
pub const fn style() -> Style {
Style::new()
}
impl<T> Styled<T> {
pub const fn inner(&self) -> &T {
&self.target
}
pub const fn inner_mut(&mut self) -> &mut T {
&mut self.target
}
}
macro_rules! impl_fmt {
($($trait:path),* $(,)?) => {
$(
impl<T: $trait> $trait for Styled<T> {
#[allow(unused_assignments)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.style.fmt_prefix(f)?;
<T as $trait>::fmt(&self.target, f)?;
self.style.fmt_suffix(f)
}
}
)*
};
}
impl_fmt! {
fmt::Display,
fmt::Debug,
fmt::UpperHex,
fmt::LowerHex,
fmt::Binary,
fmt::UpperExp,
fmt::LowerExp,
fmt::Octal,
fmt::Pointer,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{AnsiColors, OwoColorize};
#[test]
fn size_of() {
let size = std::mem::size_of::<Style>();
assert_eq!(size, 10, "size of Style should be 10 bytes");
}
#[test]
fn test_it() {
let style = Style::new()
.bright_white()
.on_blue()
.bold()
.dimmed()
.italic()
.underline()
.blink()
.strikethrough();
let s = style.style("TEST");
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[97;44;1;2;3;4;5;9mTEST\u{1b}[0m");
let prefix = format!("{}", style.prefix_formatter());
assert_eq!(&prefix, "\u{1b}[97;44;1;2;3;4;5;9m");
let suffix = format!("{}", style.suffix_formatter());
assert_eq!(&suffix, "\u{1b}[0m");
}
#[test]
fn test_effects() {
use Effect::*;
let style = Style::new().effects(&[Strikethrough, Underline]);
let s = style.style("TEST");
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[4;9mTEST\u{1b}[0m");
}
#[test]
fn test_color() {
let style = Style::new()
.color(AnsiColors::White)
.on_color(AnsiColors::Black);
let s = style.style("TEST");
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[37;40mTEST\u{1b}[0m");
}
#[test]
fn test_truecolor() {
let style = Style::new().truecolor(255, 255, 255).on_truecolor(0, 0, 0);
let s = style.style("TEST");
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[38;2;255;255;255;48;2;0;0;0mTEST\u{1b}[0m");
}
#[test]
fn test_string_reference() {
let style = Style::new().truecolor(255, 255, 255).on_truecolor(0, 0, 0);
let string = String::from("TEST");
let s = style.style(&string);
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[38;2;255;255;255;48;2;0;0;0mTEST\u{1b}[0m");
}
#[test]
fn test_owocolorize() {
let style = Style::new().bright_white().on_blue();
let s = "TEST".style(style);
let s2 = format!("{}", &s);
println!("{}", &s2);
assert_eq!(&s2, "\u{1b}[97;44mTEST\u{1b}[0m");
}
#[test]
fn test_is_plain() {
let style = Style::new().bright_white().on_blue();
assert!(!style.is_plain());
assert!(Style::default().is_plain());
let string = String::from("TEST");
let s = Style::default().style(&string);
let s2 = format!("{}", &s);
assert_eq!(string, s2)
}
#[test]
fn test_inner() {
let style = Style::default();
let mut s = "TEST".style(style);
assert_eq!(&&"TEST", s.inner());
*s.inner_mut() = &"changed";
assert_eq!(&&"changed", s.inner());
assert_eq!("changed", format!("{}", s));
}
}