1use std::{
4 fmt::Display,
5 io::{BufWriter, StderrLock, Write, stderr},
6 str::FromStr,
7 sync::atomic::{AtomicU8, Ordering},
8};
9
10use anyhow::{Result, bail};
11use strum::VariantNames;
12use strum_macros::EnumVariantNames;
13
14use crate::serde_types::date_and_time::DateTimeWithOffset;
15
16pub fn write_time(file: &str, line: u32, column: u32) -> BufWriter<StderrLock<'static>> {
17 let t_str = DateTimeWithOffset::now(None);
19 let mut lock = BufWriter::new(stderr().lock());
20 _ = write!(&mut lock, "{t_str}\t{file}:{line}:{column}\t");
21 lock
22}
23
24#[macro_export]
27macro_rules! info_if {
28 { $verbose:expr, $($arg:tt)* } => {
29 if $verbose {
30 use std::io::Write;
31 let mut lock = $crate::utillib::logging::write_time(file!(), line!(), column!());
32 _ = writeln!(&mut lock, $($arg)*);
33 }
34 }
35}
36
37#[derive(Debug, clap::Args)]
41pub struct LogLevelOpts {
42 #[clap(short, long)]
46 quiet: bool,
47
48 #[clap(short, long)]
51 verbose: bool,
52
53 #[clap(short, long)]
56 debug: bool,
57}
58
59impl LogLevelOpts {
60 pub fn xor_log_level(self, opt_log_level: Option<LogLevel>) -> Result<Option<LogLevel>> {
64 if let Some(level) = TryInto::<Option<LogLevel>>::try_into(self)? {
65 if let Some(expected_log_level) = opt_log_level {
66 if level != expected_log_level {
67 bail!(
68 "both the {} option and log-level {} were given, please \
69 only either give one of the options --quiet / --verbose / --debug \
70 or a log-level",
71 level
72 .option_name()
73 .expect("if TryInto gave a value then option_name will give one, too"),
74 expected_log_level
75 )
76 }
77 }
78 Ok(Some(level))
79 } else {
80 Ok(opt_log_level)
81 }
82 }
83}
84
85impl TryFrom<LogLevelOpts> for LogLevel {
86 type Error = anyhow::Error;
87
88 fn try_from(value: LogLevelOpts) -> Result<Self> {
89 match value {
90 LogLevelOpts {
91 verbose: false,
92 debug: false,
93 quiet: false,
94 } => Ok(LogLevel::Warn),
95 LogLevelOpts {
96 verbose: true,
97 debug: false,
98 quiet: false,
99 } => Ok(LogLevel::Info),
100 LogLevelOpts {
101 verbose: _,
102 debug: true,
103 quiet: false,
104 } => Ok(LogLevel::Debug),
105 LogLevelOpts {
106 verbose: false,
107 debug: false,
108 quiet: true,
109 } => Ok(LogLevel::Quiet),
110 LogLevelOpts {
111 verbose: _,
112 debug: _,
113 quiet: true,
114 } => bail!("option `--quiet` conflicts with the options `--verbose` and `--debug`"),
115 }
116 }
117}
118
119impl TryFrom<LogLevelOpts> for Option<LogLevel> {
122 type Error = anyhow::Error;
123
124 fn try_from(value: LogLevelOpts) -> std::result::Result<Self, Self::Error> {
125 match value {
126 LogLevelOpts {
127 verbose: false,
128 debug: false,
129 quiet: false,
130 } => Ok(None),
131 _ => Ok(Some(value.try_into()?)),
132 }
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumVariantNames)]
137#[strum(serialize_all = "snake_case")]
138pub enum LogLevel {
139 Quiet,
141 Warn,
143 Info,
146 Debug,
148}
149
150impl LogLevel {
151 pub const MAX: LogLevel = LogLevel::Debug;
152
153 fn level(self) -> u8 {
157 self as u8
158 }
159
160 fn from_level(level: u8) -> Option<Self> {
161 {
162 match LogLevel::Quiet {
164 LogLevel::Quiet => (),
165 LogLevel::Warn => (),
166 LogLevel::Info => (),
167 LogLevel::Debug => (),
168 }
169 }
170 match level {
171 0 => Some(LogLevel::Quiet),
172 1 => Some(LogLevel::Warn),
173 2 => Some(LogLevel::Info),
174 3 => Some(LogLevel::Debug),
175 _ => None,
176 }
177 }
178
179 fn to_str(self) -> &'static str {
182 match self {
183 LogLevel::Quiet => "quiet",
184 LogLevel::Warn => "warn",
185 LogLevel::Info => "info",
186 LogLevel::Debug => "debug",
187 }
188 }
189
190 fn option_name(self) -> Option<&'static str> {
193 match self {
194 LogLevel::Quiet => Some("--quiet"),
195 LogLevel::Warn => None,
196 LogLevel::Info => Some("--verbose"),
197 LogLevel::Debug => Some("--debug"),
198 }
199 }
200}
201
202impl Default for LogLevel {
203 fn default() -> Self {
204 Self::Warn
205 }
206}
207
208#[test]
209fn t_default() {
210 assert_eq!(
211 LogLevel::default(),
212 LogLevelOpts {
213 verbose: false,
214 debug: false,
215 quiet: false
216 }
217 .try_into()
218 .expect("no conflicts")
219 );
220 assert_eq!(LogLevel::default().option_name(), None);
221}
222
223impl Display for LogLevel {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 f.write_str(self.to_str())
226 }
227}
228
229#[test]
230fn t_display() -> Result<()> {
231 for level_str in LogLevel::VARIANTS {
232 let level = LogLevel::from_str(level_str)?;
233 assert_eq!(level.to_str(), *level_str);
234 }
235 Ok(())
236}
237
238impl FromStr for LogLevel {
241 type Err = anyhow::Error;
242
243 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
244 {
245 match LogLevel::Quiet {
247 LogLevel::Quiet => (),
248 LogLevel::Warn => (),
249 LogLevel::Info => (),
250 LogLevel::Debug => (),
251 }
252 }
253 match s {
254 "quiet" => Ok(LogLevel::Quiet),
255 "warn" => Ok(LogLevel::Warn),
256 "info" => Ok(LogLevel::Info),
257 "debug" => Ok(LogLevel::Debug),
258 _ => bail!(
259 "invalid log level name {s:?}, valid are: {}",
260 LogLevel::VARIANTS.join(", ")
261 ),
262 }
263 }
264}
265
266#[test]
267fn t_levels() -> Result<()> {
268 use std::str::FromStr;
269
270 for i in 0..=LogLevel::MAX.level() {
271 let lvl = LogLevel::from_level(i).expect("valid");
272 assert_eq!(lvl.level(), i);
273 let s = lvl.to_string();
274 assert_eq!(LogLevel::from_str(&s).unwrap(), lvl);
275 }
276 assert_eq!(LogLevel::from_level(LogLevel::MAX.level() + 1), None);
277 let lvl = LogLevel::from_str("info")?;
278 assert_eq!(lvl.level(), 2);
279 assert!(LogLevel::from_str("Info").is_err());
280 assert_eq!(lvl.to_string(), "info");
281 Ok(())
282}
283
284impl PartialOrd for LogLevel {
285 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
286 Some(self.cmp(other))
287 }
288}
289
290impl Ord for LogLevel {
291 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
292 self.level().cmp(&other.level())
293 }
294}
295
296pub static LOG_LEVEL: AtomicU8 = AtomicU8::new(1);
297
298pub fn set_log_level(val: LogLevel) {
301 LOG_LEVEL.store(val.level(), Ordering::SeqCst);
302}
303
304#[inline]
306pub fn log_level() -> LogLevel {
307 let level = LOG_LEVEL.load(Ordering::Relaxed);
308 LogLevel::from_level(level).expect("no possibility to store invalid u8")
309}
310
311#[macro_export]
313macro_rules! warn {
314 { $($arg:tt)* } => {
315 if $crate::utillib::logging::log_level() >= $crate::utillib::logging::LogLevel::Warn {
316 use std::io::Write;
317 let mut lock = $crate::utillib::logging::write_time(file!(), line!(), column!());
318 _ = writeln!(&mut lock, $($arg)*);
319 }
320 }
321}
322
323#[macro_export]
325macro_rules! info {
326 { $($arg:tt)* } => {
327 if $crate::utillib::logging::log_level() >= $crate::utillib::logging::LogLevel::Info {
328 use std::io::Write;
329 let mut lock = $crate::utillib::logging::write_time(file!(), line!(), column!());
330 _ = writeln!(&mut lock, $($arg)*);
331 }
332 }
333}
334
335#[macro_export]
337macro_rules! debug {
338 { $($arg:tt)* } => {
339 if $crate::utillib::logging::log_level() >= $crate::utillib::logging::LogLevel::Debug {
340 use std::io::Write;
341 let mut lock = $crate::utillib::logging::write_time(file!(), line!(), column!());
342 _ = writeln!(&mut lock, $($arg)*);
343 }
344 }
345}
346
347#[macro_export]
351macro_rules! unfinished {
352 { } => {
353 if $crate::utillib::logging::log_level() >= $crate::utillib::logging::LogLevel::Warn {
354 use std::io::Write;
355 let mut lock = $crate::utillib::logging::write_time(file!(), line!(), column!());
356 _ = writeln!(&mut lock, "WARNING: unfinished!");
357 }
358 };
359 { $fmt:tt $($arg:tt)* } => {
360 if $crate::utillib::logging::log_level() >= $crate::utillib::logging::LogLevel::Warn {
361 use std::io::Write;
362 let mut lock = $crate::utillib::logging::write_time(file!(), line!(), column!());
363 _ = lock.write_all("WARNING: unfinished!: ".as_bytes());
364 _ = writeln!(&mut lock, $fmt $($arg)*);
365 }
366 }
367}
368
369#[macro_export]
371macro_rules! untested {
372 { } => {
373 if $crate::utillib::logging::log_level() >= $crate::utillib::logging::LogLevel::Warn {
374 use std::io::Write;
375 let mut lock = $crate::utillib::logging::write_time(file!(), line!(), column!());
376 _ = writeln!(&mut lock, "WARNING: untested!");
377 }
378 };
379 { $fmt:tt $($arg:tt)* } => {
380 if $crate::utillib::logging::log_level() >= $crate::utillib::logging::LogLevel::Warn {
381 use std::io::Write;
382 let mut lock = $crate::utillib::logging::write_time(file!(), line!(), column!());
383 _ = lock.write_all("WARNING: untested!: ".as_bytes());
384 _ = writeln!(&mut lock, $fmt $($arg)*);
385 }
386 }
387}