1use crate::args::Args;
4use colored::{Color, Colorize};
5use env_logger::Builder;
6use log::Level;
7use log::Level::{Debug, Error, Info, Trace, Warn};
8use log::LevelFilter;
9use std::cmp::Reverse;
10use std::io::Write;
11use std::path::{Path, PathBuf};
12use web_time::Instant;
13
14#[derive(Debug)]
16pub struct Log {
17 pub level: Level,
19 pub time: Instant,
21 pub file: PathBuf,
23 pub linum_new: Option<usize>,
25 pub linum_old: Option<usize>,
27 pub line: Option<String>,
29 pub message: String,
31}
32
33fn record_log(
35 logs: &mut Vec<Log>,
36 level: Level,
37 file: &Path,
38 linum_new: Option<usize>,
39 linum_old: Option<usize>,
40 line: Option<String>,
41 message: &str,
42) {
43 let log = Log {
44 level,
45 time: Instant::now(),
46 file: file.to_path_buf(),
47 linum_new,
48 linum_old,
49 line,
50 message: message.to_string(),
51 };
52 logs.push(log);
53}
54
55pub fn record_file_log(
57 logs: &mut Vec<Log>,
58 level: Level,
59 file: &Path,
60 message: &str,
61) {
62 record_log(logs, level, file, None, None, None, message);
63}
64
65pub fn record_line_log(
67 logs: &mut Vec<Log>,
68 level: Level,
69 file: &Path,
70 linum_new: usize,
71 linum_old: usize,
72 line: &str,
73 message: &str,
74) {
75 record_log(
76 logs,
77 level,
78 file,
79 Some(linum_new),
80 Some(linum_old),
81 Some(line.to_string()),
82 message,
83 );
84}
85
86const fn get_log_color(log_level: Level) -> Color {
88 match log_level {
89 Info => Color::Cyan,
90 Warn => Color::Yellow,
91 Error => Color::Red,
92 Trace => Color::Green,
93 Debug => panic!(),
94 }
95}
96
97pub fn init_logger(level_filter: LevelFilter) {
99 Builder::new()
100 .filter_level(level_filter)
101 .format(|buf, record| {
102 writeln!(
103 buf,
104 "{}: {}",
105 record
106 .level()
107 .to_string()
108 .color(get_log_color(record.level()))
109 .bold(),
110 record.args()
111 )
112 })
113 .init();
114}
115
116fn preprocess_logs(logs: &mut Vec<Log>) {
118 logs.sort_by_key(|l| {
119 (
120 l.level,
121 l.linum_new,
122 l.linum_old,
123 l.message.clone(),
124 Reverse(l.time),
125 )
126 });
127 logs.dedup_by(|a, b| {
128 (
129 a.level,
130 &a.file,
131 a.linum_new,
132 a.linum_old,
133 &a.line,
134 &a.message,
135 ) == (
136 b.level,
137 &b.file,
138 b.linum_new,
139 b.linum_old,
140 &b.line,
141 &b.message,
142 )
143 });
144 logs.sort_by_key(|l| l.time);
145}
146
147fn format_log(log: &Log) -> String {
149 let linum_new = log
150 .linum_new
151 .map_or_else(String::new, |i| format!("Line {i} "));
152
153 let linum_old = log
154 .linum_old
155 .map_or_else(String::new, |i| format!("({i}). "));
156
157 let line = log
158 .line
159 .as_ref()
160 .map_or_else(String::new, |l| l.trim_start().to_string());
161
162 let log_string = format!(
163 "{}{}{} {}",
164 linum_new.white().bold(),
165 linum_old.white().bold(),
166 log.message.yellow().bold(),
167 line,
168 );
169 log_string
170}
171
172#[allow(clippy::similar_names)]
174pub fn format_logs(logs: &mut Vec<Log>, args: &Args) -> String {
175 preprocess_logs(logs);
176 let mut logs_string = String::new();
177 for log in logs {
178 if log.level <= args.verbosity {
179 let log_string = format_log(log);
180 logs_string.push_str(&log_string);
181 logs_string.push('\n');
182 }
183 }
184 logs_string
185}
186
187pub fn print_logs(logs: &mut Vec<Log>) {
193 preprocess_logs(logs);
194 for log in logs {
195 let log_string = format!(
196 "{} {}: {}",
197 "tex-fmt".magenta().bold(),
198 log.file.to_str().unwrap().blue().bold(),
199 format_log(log),
200 );
201
202 match log.level {
203 Error => log::error!("{log_string}"),
204 Warn => log::warn!("{log_string}"),
205 Info => log::info!("{log_string}"),
206 Trace => log::trace!("{log_string}"),
207 Debug => panic!(),
208 }
209 }
210}