use crate::args::Args;
use colored::{Color, Colorize};
use env_logger::Builder;
use log::Level;
use log::Level::{Debug, Error, Info, Trace, Warn};
use log::LevelFilter;
use std::cmp::Reverse;
use std::io::Write;
use std::path::{Path, PathBuf};
use web_time::Instant;
#[derive(Debug)]
pub struct Log {
pub level: Level,
pub time: Instant,
pub file: PathBuf,
pub linum_new: Option<usize>,
pub linum_old: Option<usize>,
pub line: Option<String>,
pub message: String,
}
fn record_log(
logs: &mut Vec<Log>,
level: Level,
file: &Path,
linum_new: Option<usize>,
linum_old: Option<usize>,
line: Option<String>,
message: &str,
) {
let log = Log {
level,
time: Instant::now(),
file: file.to_path_buf(),
linum_new,
linum_old,
line,
message: message.to_string(),
};
logs.push(log);
}
pub fn record_file_log(
logs: &mut Vec<Log>,
level: Level,
file: &Path,
message: &str,
) {
record_log(logs, level, file, None, None, None, message);
}
pub fn record_line_log(
logs: &mut Vec<Log>,
level: Level,
file: &Path,
linum_new: usize,
linum_old: usize,
line: &str,
message: &str,
) {
record_log(
logs,
level,
file,
Some(linum_new),
Some(linum_old),
Some(line.to_string()),
message,
);
}
const fn get_log_color(log_level: Level) -> Color {
match log_level {
Info => Color::Cyan,
Warn => Color::Yellow,
Error => Color::Red,
Trace => Color::Green,
Debug => panic!(),
}
}
pub fn init_logger(level_filter: LevelFilter) {
Builder::new()
.filter_level(level_filter)
.format(|buf, record| {
writeln!(
buf,
"{}: {}",
record
.level()
.to_string()
.color(get_log_color(record.level()))
.bold(),
record.args()
)
})
.init();
}
fn preprocess_logs(logs: &mut Vec<Log>) {
logs.sort_by_key(|l| {
(
l.level,
l.linum_new,
l.linum_old,
l.message.clone(),
Reverse(l.time),
)
});
logs.dedup_by(|a, b| {
(
a.level,
&a.file,
a.linum_new,
a.linum_old,
&a.line,
&a.message,
) == (
b.level,
&b.file,
b.linum_new,
b.linum_old,
&b.line,
&b.message,
)
});
logs.sort_by_key(|l| l.time);
}
fn format_log(log: &Log) -> String {
let linum_new = log
.linum_new
.map_or_else(String::new, |i| format!("Line {i} "));
let linum_old = log
.linum_old
.map_or_else(String::new, |i| format!("({i}). "));
let line = log
.line
.as_ref()
.map_or_else(String::new, |l| l.trim_start().to_string());
let log_string = format!(
"{}{}{} {}",
linum_new.white().bold(),
linum_old.white().bold(),
log.message.yellow().bold(),
line,
);
log_string
}
#[allow(clippy::similar_names)]
pub fn format_logs(logs: &mut Vec<Log>, args: &Args) -> String {
preprocess_logs(logs);
let mut logs_string = String::new();
for log in logs {
if log.level <= args.verbosity {
let log_string = format_log(log);
logs_string.push_str(&log_string);
logs_string.push('\n');
}
}
logs_string
}
pub fn print_logs(logs: &mut Vec<Log>) {
preprocess_logs(logs);
for log in logs {
let log_string = format!(
"{} {}: {}",
"tex-fmt".magenta().bold(),
log.file.to_str().unwrap().blue().bold(),
format_log(log),
);
match log.level {
Error => log::error!("{log_string}"),
Warn => log::warn!("{log_string}"),
Info => log::info!("{log_string}"),
Trace => log::trace!("{log_string}"),
Debug => panic!(),
}
}
}