use std::fmt;
macro_rules! color {
($($name:ident $fg:literal $bg:literal),* $(,)?) => {
#[derive(Debug, Clone, Copy)]
#[must_use]
#[non_exhaustive]
pub enum Color {
$(
#[allow(missing_docs)]
$name,
)*
}
impl Default for Color {
fn default() -> Self {
Self::Default
}
}
impl Color {
const fn fg(self) -> &'static str {
match self {
$(
Self::$name => stringify!($fg),
)*
}
}
const fn bg(self) -> &'static str {
match self {
$(
Self::$name => stringify!($bg),
)*
}
}
}
};
}
color! {
Default 39 49,
Black 30 40,
Red 31 41,
Green 32 42,
Yellow 33 43,
Blue 34 44,
Magenta 35 45,
Cyan 36 46,
White 37 47,
BrightBlack 90 100,
BrightRed 91 101,
BrightGreen 92 102,
BrightYellow 93 103,
BrightBlue 94 104,
BrightMagenta 95 105,
BrightCyan 96 106,
BrightWhite 97 107,
}
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
struct Colors {
fg: Option<Color>,
bg: Option<Color>,
}
impl Colors {
const fn new() -> Self {
Self { fg: None, bg: None }
}
const fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
const fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
}
macro_rules! effect {
($($name:ident $num:literal),* $(,)?) => {
#[derive(Debug, Clone, Copy)]
#[must_use]
#[non_exhaustive]
pub enum Effect {
$(
#[allow(missing_docs)]
$name = $num,
)*
}
impl Effect {
const ALL: [Self; 9] = [$(Self::$name,)*];
const fn ansi(self) -> &'static str {
match self {
$(
Self::$name => stringify!($num),
)*
}
}
}
};
}
effect! {
Bold 1,
Dimmed 2,
Italic 3,
Underline 4,
Blink 5,
BlinkFast 6,
Reversed 7,
Hidden 8,
Strikethrough 9,
}
#[derive(Debug, Clone, Copy, Default)]
struct Effects {
bytes: u16,
}
impl Effects {
const fn new() -> Self {
Self { bytes: 0 }
}
const fn set(mut self, effect: Effect) -> Self {
self.bytes |= 1 << effect as u16;
self
}
const fn unset(mut self, effect: Effect) -> Self {
self.bytes &= !(1 << effect as u16);
self
}
const fn get(self, effect: Effect) -> bool {
(self.bytes >> effect as u16) & 1 == 1
}
}
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
#[non_exhaustive]
pub struct Style {
colors: Colors,
effects: Effects,
}
impl Style {
#[inline]
pub const fn new() -> Self {
Self {
colors: Colors::new(),
effects: Effects::new(),
}
}
#[inline]
pub const fn fg(mut self, color: Color) -> Self {
self.colors = self.colors.fg(color);
self
}
#[inline]
pub const fn bg(mut self, color: Color) -> Self {
self.colors = self.colors.bg(color);
self
}
#[inline]
pub const fn set(mut self, effect: Effect) -> Self {
self.effects = self.effects.set(effect);
self
}
#[inline]
pub const fn unset(mut self, effect: Effect) -> Self {
self.effects = self.effects.unset(effect);
self
}
pub(crate) fn style<T: fmt::Display>(self, thing: T) -> Styled<T> {
Styled { style: self, thing }
}
}
pub(crate) struct Styled<T> {
style: Style,
thing: T,
}
impl<T: fmt::Display> fmt::Display for Styled<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Styled {
style: Style { colors, effects },
thing,
} = self;
let meta = colors.bg.is_some() || colors.fg.is_some() || effects.bytes != 0;
if meta {
f.write_str("\x1b[")?;
let colors = [colors.fg, colors.bg]
.into_iter()
.zip([Color::fg, Color::bg])
.filter_map(|(color, extractor)| color.map(extractor));
let effects = Effect::ALL
.into_iter()
.filter(|effect| effects.get(*effect))
.map(Effect::ansi);
for (index, code) in colors.chain(effects).enumerate() {
if index != 0 {
f.write_str(";")?;
}
f.write_str(code)?;
}
f.write_str("m")?;
}
fmt::Display::fmt(thing, f)?;
if meta {
f.write_str("\x1b[0m")?;
}
Ok(())
}
}
macro_rules! theme {
($(#[$meta:meta] $name:ident),* $(,)?) => {
#[derive(Debug, Clone, Default)]
#[must_use]
#[non_exhaustive]
pub struct Theme {
$(
#[$meta]
pub(crate) $name: Style,
)*
}
impl Theme {
$(
#[$meta]
#[inline]
pub const fn $name(mut self, style: Style) -> Self {
self.$name = style;
self
}
)*
}
};
}
theme! {
file,
line,
error,
dependency,
package,
hash,
header,
message,
hidden,
}
impl Theme {
#[inline]
pub const fn blank() -> Self {
Self {
file: Style::new(),
line: Style::new(),
error: Style::new(),
dependency: Style::new(),
package: Style::new(),
hash: Style::new(),
header: Style::new(),
message: Style::new(),
hidden: Style::new(),
}
}
#[inline]
pub const fn new() -> Self {
Self::dark()
}
#[inline]
pub const fn dark() -> Self {
Self {
file: Style::new().fg(Color::Magenta),
line: Style::new().fg(Color::Magenta),
error: Style::new().fg(Color::BrightRed),
dependency: Style::new().fg(Color::Green),
package: Style::new().fg(Color::BrightRed),
hash: Style::new().fg(Color::BrightBlack),
header: Style::new().fg(Color::Red),
message: Style::new().fg(Color::Cyan),
hidden: Style::new().fg(Color::BrightCyan),
}
}
#[inline]
pub const fn light() -> Self {
Self {
file: Style::new().fg(Color::Magenta),
line: Style::new().fg(Color::Magenta),
error: Style::new().fg(Color::Red),
dependency: Style::new().fg(Color::Green),
package: Style::new().fg(Color::Red),
hash: Style::new().fg(Color::BrightBlack),
header: Style::new().fg(Color::Red),
message: Style::new().fg(Color::Blue),
hidden: Style::new().fg(Color::Blue),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
impl Effects {
fn contains(self, effect: Effect) -> Self {
assert!(self.get(effect));
self
}
fn lacks(self, effect: Effect) -> Self {
assert!(!self.get(effect));
self
}
}
#[test]
fn effect() {
let bold = Effect::Bold;
let strike = Effect::Strikethrough;
Effects::new()
.lacks(bold)
.unset(bold)
.lacks(bold)
.set(bold)
.contains(bold)
.set(strike)
.contains(bold)
.contains(strike)
.unset(bold)
.contains(strike)
.lacks(bold);
}
}