use ansi_term::{Colour, Style};
use std::borrow::Cow;
use std::fmt::Write;
use tracing_core::Event;
use tracing_subscriber::fmt::format::Writer;
use tracing_subscriber::fmt::{FormatEvent, FormatFields};
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
struct CustomFormatter;
impl<S, N> FormatEvent<S, N> for CustomFormatter
where
S: tracing_core::Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
_: &tracing_subscriber::fmt::FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> std::fmt::Result {
let level = event.metadata().level();
let msg_style = match *level {
tracing::Level::ERROR => Colour::Red.bold(),
tracing::Level::WARN => Colour::Yellow.bold(),
tracing::Level::INFO => Colour::Green.bold(),
tracing::Level::DEBUG => Colour::Blue.normal(),
tracing::Level::TRACE => Colour::Purple.normal(),
};
match *level {
tracing::Level::INFO => {}
tracing::Level::ERROR => {
write!(writer, "❌ ")?;
}
tracing::Level::WARN => {
write!(writer, "⚠️ ")?;
}
tracing::Level::DEBUG => {
write!(
writer,
"{} ",
Style::new().bold().fg(Colour::Blue).paint("[DEBUG]")
)?;
}
tracing::Level::TRACE => {
write!(
writer,
"{} ",
Style::new().bold().fg(Colour::Purple).paint("[TRACE]")
)?;
}
}
let mut visitor = MessageVisitor::default();
event.record(&mut visitor);
if let Some(message) = visitor.message {
write!(writer, "{}", msg_style.paint(message))?;
}
writeln!(writer)
}
}
#[derive(Default)]
struct MessageVisitor {
message: Option<String>,
}
impl tracing::field::Visit for MessageVisitor {
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
if field.name() == "message" {
self.message = Some(value.to_string());
}
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" {
self.message = Some(format!("{:?}", value));
}
}
}
pub fn init_logging(verbose_level: u8) {
let filter = match verbose_level {
0 => "INFO",
1 => "DEBUG",
_ => "TRACE",
};
let formatting_layer = tracing_subscriber::fmt::layer()
.event_format(CustomFormatter)
.with_ansi(true);
tracing_subscriber::registry()
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(filter)))
.with(formatting_layer)
.init();
}
#[derive(Debug, Clone)]
enum StylePart<'a> {
Text(Cow<'a, str>),
Colored { text: Cow<'a, str>, color: Colour },
Styled { text: Cow<'a, str>, style: Style },
ColoredStyled {
text: Cow<'a, str>,
color: Colour,
style: Style,
},
}
pub struct StyledText<'a> {
parts: Vec<StylePart<'a>>,
sep: &'a str,
}
macro_rules! color_method {
($name:ident, $color:expr) => {
pub fn $name(&mut self, text: impl Into<Cow<'a, str>>) -> &mut Self {
self.parts.push(StylePart::Colored {
text: text.into(),
color: $color,
});
self
}
};
}
macro_rules! style_method {
($name:ident, $style:expr) => {
pub fn $name(&mut self, text: impl Into<Cow<'a, str>>) -> &mut Self {
self.parts.push(StylePart::Styled {
text: text.into(),
style: $style,
});
self
}
};
}
macro_rules! color_style_method {
($name:ident, $color:expr, $style:expr) => {
pub fn $name(&mut self, text: impl Into<Cow<'a, str>>) -> &mut Self {
self.parts.push(StylePart::ColoredStyled {
text: text.into(),
color: $color,
style: $style,
});
self
}
};
}
macro_rules! print_style_method {
($fmt:ident, $print:ident, $style:ident) => {
pub fn $print(&self) {
let msg = self.$fmt().expect("Failed to format styled text");
println!("{}", msg);
}
pub fn $fmt(&self) -> Result<String, std::fmt::Error> {
let mut result = String::new();
let mut iter = self.parts.iter().peekable();
while let Some(part) = iter.next() {
let styled_text = match part {
StylePart::Text(text) => Style::new().$style().paint(text.as_ref()),
StylePart::Colored { text, color } => {
Style::new().$style().fg(*color).paint(text.as_ref())
}
StylePart::Styled { text, style } => style.$style().paint(text.as_ref()),
StylePart::ColoredStyled { text, color, style } => {
style.$style().fg(*color).paint(text.as_ref())
}
};
write!(result, "{}", styled_text)?;
if iter.peek().is_some() {
write!(result, "{}", self.sep)?;
}
}
Ok(result)
}
};
}
impl<'a> std::fmt::Display for StyledText<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut iter = self.parts.iter().peekable();
while let Some(part) = iter.next() {
match part {
StylePart::Text(text) => write!(f, "{}", text)?,
StylePart::Colored { text, color } => write!(f, "{}", color.paint(text.as_ref()))?,
StylePart::Styled { text, style } => write!(f, "{}", style.paint(text.as_ref()))?,
StylePart::ColoredStyled { text, color, style } => {
write!(f, "{}", style.fg(*color).paint(text.as_ref()))?
}
}
if iter.peek().is_some() {
write!(f, "{}", self.sep)?;
}
}
Ok(())
}
}
impl<'a> StyledText<'a> {
pub fn new(sep: &'a str) -> Self {
let parts = Vec::new();
Self { parts, sep }
}
pub fn println(&self) {
println!("{self}");
}
print_style_method!(fmt_bold, println_bold, bold);
print_style_method!(fmt_blink, println_blink, blink);
print_style_method!(fmt_italic, println_italic, italic);
print_style_method!(fmt_hidden, println_hidden, hidden);
print_style_method!(fmt_reverse, println_reverse, reverse);
print_style_method!(fmt_underline, println_underline, underline);
print_style_method!(fmt_strikethrough, println_strikethrough, strikethrough);
pub fn with(&mut self, closure: impl FnOnce(&mut Self)) -> &mut Self {
closure(self);
self
}
pub fn text(&mut self, text: impl Into<Cow<'a, str>>) -> &mut Self {
self.parts.push(StylePart::Text(text.into()));
self
}
color_method!(white, Colour::White);
color_method!(red, Colour::Red);
color_method!(green, Colour::Green);
color_method!(blue, Colour::Blue);
color_method!(purple, Colour::Purple);
color_method!(yellow, Colour::Yellow);
color_method!(cyan, Colour::Cyan);
color_method!(black, Colour::Black);
style_method!(bold, Style::new().bold());
style_method!(dimmed, Style::new().dimmed());
style_method!(italic, Style::new().italic());
style_method!(underline, Style::new().underline());
style_method!(blink, Style::new().blink());
style_method!(reverse, Style::new().reverse());
style_method!(hidden, Style::new().hidden());
style_method!(strikethrough, Style::new().strikethrough());
color_style_method!(white_bold, Colour::White, Style::new().bold());
color_style_method!(red_bold, Colour::Red, Style::new().bold());
color_style_method!(green_bold, Colour::Green, Style::new().bold());
color_style_method!(blue_bold, Colour::Blue, Style::new().bold());
color_style_method!(purple_bold, Colour::Purple, Style::new().bold());
color_style_method!(yellow_bold, Colour::Yellow, Style::new().bold());
color_style_method!(cyan_bold, Colour::Cyan, Style::new().bold());
color_style_method!(black_bold, Colour::Black, Style::new().bold());
color_style_method!(white_underline, Colour::White, Style::new().underline());
color_style_method!(red_underline, Colour::Red, Style::new().underline());
color_style_method!(green_underline, Colour::Green, Style::new().underline());
color_style_method!(blue_underline, Colour::Blue, Style::new().underline());
color_style_method!(purple_underline, Colour::Purple, Style::new().underline());
color_style_method!(yellow_underline, Colour::Yellow, Style::new().underline());
color_style_method!(cyan_underline, Colour::Cyan, Style::new().underline());
color_style_method!(black_underline, Colour::Black, Style::new().underline());
pub fn rgb(&mut self, r: u8, g: u8, b: u8, text: impl Into<Cow<'a, str>>) -> &mut Self {
self.parts.push(StylePart::Colored {
text: text.into(),
color: Colour::RGB(r, g, b),
});
self
}
pub fn rgb_bold(&mut self, r: u8, g: u8, b: u8, text: impl Into<Cow<'a, str>>) -> &mut Self {
self.parts.push(StylePart::ColoredStyled {
text: text.into(),
color: Colour::RGB(r, g, b),
style: Style::new().bold(),
});
self
}
pub fn fixed(&mut self, color_num: u8, text: impl Into<Cow<'a, str>>) -> &mut Self {
self.parts.push(StylePart::Colored {
text: text.into(),
color: Colour::Fixed(color_num),
});
self
}
pub fn fixed_bold(&mut self, color_num: u8, text: impl Into<Cow<'a, str>>) -> &mut Self {
self.parts.push(StylePart::ColoredStyled {
text: text.into(),
color: Colour::Fixed(color_num),
style: Style::new().bold(),
});
self
}
pub fn len(&self) -> usize {
self.parts.len()
}
pub fn is_empty(&self) -> bool {
self.parts.is_empty()
}
pub fn clear(&mut self) {
self.parts.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log() {
init_logging(1);
tracing::info!(
"test {} {}",
Colour::Yellow.paint("info"),
Colour::Cyan.paint("info")
);
tracing::debug!("test {}", 123);
tracing::trace!("test {}", 123);
tracing::warn!("test {}", 123);
tracing::error!("test {}", 123);
}
#[test]
fn test_styled_text() {
let mut styled_text = StyledText::new(" ");
styled_text
.text("plain")
.text("owned".to_string()) .white("white")
.red("red")
.green("green")
.blue("blue")
.purple("purple")
.yellow("yellow")
.cyan("cyan")
.black("black")
.bold("bold")
.dimmed("dimmed")
.italic("italic")
.underline("underline")
.blink("blink")
.reverse("reverse")
.hidden("hidden")
.strikethrough("strikethrough")
.white_bold("white_bold")
.red_bold("red_bold")
.green_bold("green_bold")
.blue_bold("blue_bold")
.purple_bold("purple_bold")
.yellow_bold("yellow_bold")
.cyan_bold("cyan_bold")
.black_bold("black_bold")
.white_underline("white_underline")
.red_underline("red_underline")
.green_underline("green_underline")
.blue_underline("blue_underline")
.purple_underline("purple_underline")
.yellow_underline("yellow_underline")
.cyan_underline("cyan_underline")
.black_underline("black_underline")
.rgb(255, 100, 100, "rgb_pink")
.rgb_bold(100, 255, 100, "rgb_green_bold")
.fixed(202, "fixed_orange")
.fixed_bold(45, "fixed_blue_bold")
.with(|t| {
t.green("with_closure");
});
assert!(!styled_text.is_empty());
styled_text.println();
println!();
styled_text.println_bold();
styled_text.clear();
assert!(styled_text.is_empty());
assert_eq!(styled_text.len(), 0);
}
}