//! ANSI-colored output renderer.
//!
//! Two modes are supported:
//!
//! * [`RenderMode::Stream`] writes the raw line, prefixed with the source
//! filename in a stable color chosen by hash.
//! * [`RenderMode::Table`] pretty-prints extracted named fields as
//! `key=value` pairs with keys dimmed.
//!
//! Color can be disabled per-instance; we also honor `NO_COLOR` by default
//! (the binary sets that through the CLI layer).
use crossterm::style::Stylize;
use tokio::io::{AsyncWrite, AsyncWriteExt};
use crate::filter::MatchOutcome;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderMode {
Stream,
Table,
}
#[derive(Debug)]
pub struct Renderer {
mode: RenderMode,
color: bool,
}
impl Renderer {
pub fn new(mode: RenderMode, color: bool) -> Self {
Self { mode, color }
}
pub async fn write<W: AsyncWrite + Unpin>(
&self,
w: &mut W,
source: &str,
line: &str,
outcome: &MatchOutcome,
) -> std::io::Result<()> {
match (self.mode, outcome) {
(RenderMode::Table, MatchOutcome::Fields(fields)) => {
let prefix = self.source_prefix(source);
let body = fields
.iter()
.map(|(k, v)| self.kv(k, v))
.collect::<Vec<_>>()
.join(" ");
w.write_all(format!("{prefix} {body}\n").as_bytes()).await
}
_ => {
let prefix = self.source_prefix(source);
w.write_all(format!("{prefix} {line}\n").as_bytes()).await
}
}
}
pub async fn notice<W: AsyncWrite + Unpin>(
&self,
w: &mut W,
source: &str,
msg: &str,
) -> std::io::Result<()> {
let prefix = self.source_prefix(source);
let marker = if self.color { "--".dark_grey().to_string() } else { "--".into() };
w.write_all(format!("{prefix} {marker} {msg}\n").as_bytes()).await
}
fn source_prefix(&self, source: &str) -> String {
if !self.color {
return format!("[{source}]");
}
let palette = [
crossterm::style::Color::Cyan,
crossterm::style::Color::Green,
crossterm::style::Color::Yellow,
crossterm::style::Color::Magenta,
crossterm::style::Color::Blue,
];
let idx = (hash(source) as usize) % palette.len();
format!("[{}]", source.with(palette[idx]))
}
fn kv(&self, k: &str, v: &str) -> String {
if self.color {
format!("{}={}", k.dark_grey(), v)
} else {
format!("{k}={v}")
}
}
}
fn hash(s: &str) -> u64 {
let mut h: u64 = 0xcbf29ce484222325;
for b in s.as_bytes() {
h ^= *b as u64;
h = h.wrapping_mul(0x100000001b3);
}
h
}