mod macurses;
use log::{set_logger, Level, Log, Record};
pub use macurses::Ansi8;
use macurses::Ansi8::*;
use macurses::*;
use std::sync::mpsc;
use std::sync::mpsc::channel;
use std::sync::OnceLock;
use std::{thread, usize};
type LogMessage = Result<(String, Level), GLoggerSignal>;
#[derive(Clone)]
pub struct GLogger {
channel: OnceLock<mpsc::Sender<LogMessage>>,
}
#[derive(Copy, Clone)]
pub struct GLoggerOptions {
pub colors: [Ansi8; 5],
}
impl std::default::Default for GLoggerOptions {
fn default() -> Self {
Self {
colors: [Red, Yellow, Green, Blue, Default],
}
}
}
#[derive(Debug, Copy, Clone)]
enum GLoggerSignal {
Flush,
Stop,
}
impl Log for GLogger {
fn enabled(&self, _: &log::Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
let log_message = record.args().to_string(); let log_level = record.level();
if let Err(error) = self
.channel
.get()
.expect("tried to log a message to a set-up logger but the log channel was not set up")
.send(Ok((log_message, log_level)))
{
let (log_message, log_level) = error.0.clone().unwrap();
panic!(
"failed to send log {:?} at level {:?} due to {}",
log_message, log_level, error
)
}
}
fn flush(&self) {
if let Err(error) = self
.channel
.get()
.expect("tried to log a message to a logger but the log channel was not set up")
.send(Err(GLoggerSignal::Flush))
{
panic!("failed to send flush instruction due to {}", error)
}
}
}
impl GLogger {
pub fn setup() -> (thread::JoinHandle<()>, &'static GLogger) {
Self::setup_with_options(GLoggerOptions::default())
}
pub fn setup_with_options(
options: GLoggerOptions,
) -> (thread::JoinHandle<()>, &'static GLogger) {
static LOGGER: GLogger = GLogger {
channel: OnceLock::new(),
};
set_logger(&LOGGER).expect("tried to set up logger twice");
log::set_max_level(log::LevelFilter::Trace);
let (sender, receiver) = channel();
LOGGER
.channel
.set(sender)
.expect("tried to set up logger twice");
let t = thread::spawn(move || {
GWriter {
channel: receiver,
logs: vec![],
signals: vec![],
log_colors: options.colors.map(|c| c as usize),
log_counts: [0; 5],
termwidth: 0,
termlength: 0,
}
.log_loop();
});
(t, &LOGGER)
}
pub fn end(&self) {
if let Err(error) = self
.channel
.get()
.expect("tried to log a message to a logger but the log channel was not set up")
.send(Err(GLoggerSignal::Stop))
{
panic!("failed to send flush instruction due to {}", error)
}
}
}
struct GWriter {
channel: mpsc::Receiver<LogMessage>,
logs: Vec<(String, Level)>,
signals: Vec<GLoggerSignal>,
log_colors: [usize; 5],
log_counts: [usize; 5],
termwidth: usize,
termlength: usize,
}
fn nice_lines(string: &str, max_len: usize, level: Level) -> Vec<(String, Level)> {
let mut lines = vec![];
string.split('\n').for_each(|line| {
line.chars()
.collect::<Box<[char]>>()
.chunks(max_len)
.for_each(|l| lines.push((String::from_iter(l), level)))
});
lines
}
impl GWriter {
fn log_loop(&mut self) {
loop {
self.read();
self.draw();
for signal in &self.signals {
match signal {
GLoggerSignal::Flush => self.flush(),
GLoggerSignal::Stop => {
eprint!("{}\n", color!(0)); return;
}
}
}
}
}
fn flush(&self) {
todo!()
}
fn draw(&mut self) {
let length = self.termlength - 6;
for log in &self.logs {
eprintln!(
"{}{:<length$} ",
color!(self.log_colors[log.1 as usize - 1]),
log.0,
);
}
for i in 0..self.termwidth {
eprint!("{}", set_cursor!(i, length + 2));
for j in 0..5 {
eprint!(
"{}{} ",
match self.log_counts[j] >= self.termwidth - i {
true => color!(7),
false => color!(27),
},
color!(self.log_colors[j])
);
}
eprint!("{}", color!(Reset as usize))
}
}
fn read(&mut self) {
self.logs.clear();
self.signals.clear();
(self.termwidth, self.termlength) = match termsize::get() {
Some(size) => (size.rows as usize, size.cols as usize),
None => (7, 7),
};
while let Ok(message) = self.channel.try_recv() {
match message {
Ok(log) => {
self.log_counts[log.1 as usize - 1] += 1;
self.logs.append(&mut nice_lines(
&format!("{:<6}{}", log.1, log.0),
self.termlength - 6,
log.1,
));
}
Err(signal) => self.signals.push(signal),
}
}
}
}