1use std::{
2 borrow::Cow,
3 fmt::{self, Display},
4 io::{Write, stderr, stdout},
5 sync::Mutex,
6 u8,
7};
8
9use crate::bait::{MutexExt, OptionExt};
10
11use super::{Bg, BogFmter, BogLevel, Fg};
12
13#[allow(non_camel_case_types)]
15struct GLOBAL_BOGGER_STRUCT {
16 formatter: Box<dyn BogFmter + Send + Sync>,
17 writer: Box<dyn Write + Send + Sync>,
18 min_level: (u8, BogLevel),
19 downcast_to: (u8, BogLevel),
20 pub prefix: String,
21 pub suffix: String,
22 pub tag_override: Option<String>,
23 pub log: Option<bool>,
25}
26
27static GLOBAL_BOGGER: Mutex<Option<GLOBAL_BOGGER_STRUCT>> = Mutex::new(None);
29
30fn init_(logger: Box<dyn BogFmter + Send + Sync>, write: Box<dyn Write + Send + Sync>) {
31 let bogger = GLOBAL_BOGGER_STRUCT {
32 formatter: logger,
33 writer: write,
34 downcast_to: (255, BogLevel::ERROR),
35 min_level: (0, BogLevel::DEBUG),
36 prefix: String::new(),
37 suffix: String::new(),
38 tag_override: None,
39 log: None,
40 };
41
42 *GLOBAL_BOGGER.lock().unwrap() = Some(bogger);
43}
44impl GLOBAL_BOGGER_STRUCT {
46 fn bog(&mut self, mut level: BogLevel, tag: &str, msg: &str) {
47 let pri = self.formatter.priority(&level);
49 if pri < self.min_level.0 {
50 return;
51 }
52 if pri > self.downcast_to.0 {
53 level = self.downcast_to.1;
54 }
55
56 if self.log != Some(false) {
57 let level = match level {
58 BogLevel::ERROR => Some(log::Level::Error),
59 BogLevel::WARN => Some(log::Level::Warn),
60 BogLevel::INFO => Some(log::Level::Info),
61 BogLevel::DEBUG => Some(log::Level::Debug),
62 BogLevel::___ => Some(log::Level::Trace),
63 _ => None,
64 };
65 if let Some(lvl) = level {
66 log::log!(lvl, "{}{}{}", self.prefix, msg, self.suffix);
67 }
68 }
69 if self.log != Some(true) {
70 let effective_tag = self.tag_override.as_deref().unwrap_or(tag);
72
73 let mut formatted = if !self.prefix.is_empty() {
75 let mut prefixed_msg = self.prefix.clone();
76 prefixed_msg.push_str(&msg);
77 self.formatter.format(level, effective_tag, &prefixed_msg)
78 } else {
79 self.formatter.format(level, effective_tag, msg)
80 };
81
82 if !self.suffix.is_empty() {
83 formatted.push_str(&self.suffix);
84 }
85 formatted.push('\n');
86
87 let _ = self.writer.write_all(formatted.as_bytes());
89 }
90 }
91
92 fn pause(&mut self) {
93 self.min_level.0 = u8::MAX;
94 }
95
96 fn resume(&mut self) {
97 self.min_level.0 = self.formatter.priority(&self.min_level.1)
98 }
99
100 fn filter_below(&mut self, lvl: BogLevel) {
101 self.min_level = (self.formatter.priority(&lvl), lvl);
102 }
103
104 fn filter_below_priority(&mut self, v: u8) {
107 self.min_level = (v, BogLevel::___);
108 }
109
110 fn downcast_above(&mut self, lvl: BogLevel) {
111 self.downcast_to = (self.formatter.priority(&lvl), lvl);
112 }
113
114 fn bounds(&self) -> ((u8, BogLevel), (u8, BogLevel)) {
115 (self.min_level, self.downcast_to)
116 }
117
118 pub fn set_bounds(&mut self, bounds: ((u8, BogLevel), (u8, BogLevel))) {
119 self.min_level = bounds.0;
120 self.downcast_to = bounds.1;
121 }
122}
123
124pub struct BogContext {
127 bounds: [Option<BogLevel>; 2],
129 pause: bool,
131 prefix: Option<String>,
133 suffix: Option<String>,
135 tag_override: Option<String>,
137 log: Option<bool>,
139}
140
141impl BogContext {
142 pub fn new() -> Self {
143 Self {
144 bounds: [None, None],
145 pause: false,
146 prefix: None,
147 suffix: None,
148 tag_override: None,
149 log: None,
150 }
151 }
152
153 pub fn upper(mut self, level: BogLevel) -> Self {
154 self.bounds[1] = Some(level);
155 self
156 }
157
158 pub fn lower(mut self, level: BogLevel) -> Self {
159 self.bounds[0] = Some(level);
160 self
161 }
162
163 pub fn pause(mut self, pause: bool) -> Self {
164 self.pause = pause;
165 self
166 }
167
168 pub fn prefix<S: Into<String>>(mut self, prefix: S) -> Self {
169 self.prefix = Some(prefix.into());
170 self
171 }
172
173 pub fn suffix<S: Into<String>>(mut self, suffix: S) -> Self {
174 self.suffix = Some(suffix.into());
175 self
176 }
177
178 pub fn tag<S: Into<String>>(mut self, tag: S) -> Self {
179 self.tag_override = Some(tag.into());
180 self
181 }
182
183 pub fn log(mut self, log: Option<bool>) -> Self {
184 self.log = log;
185 self
186 }
187}
188
189pub struct BOGGER {}
193impl BOGGER {
195 #[inline]
198 pub fn bog(level: BogLevel, tag: &str, msg: &str) {
199 if let Some(b) = GLOBAL_BOGGER._lock().as_mut() {
200 b.bog(level, tag, msg);
201 }
202 }
203
204 #[inline]
206 pub fn filter_below(lvl: BogLevel) {
207 if let Some(b) = GLOBAL_BOGGER._lock().as_mut() {
208 b.filter_below(lvl);
209 }
210 }
211
212 #[inline]
214 pub fn downcast_above(lvl: BogLevel) {
215 if let Some(b) = GLOBAL_BOGGER._lock().as_mut() {
216 b.downcast_above(lvl);
217 }
218 }
219
220 #[inline]
222 pub fn with<T>(context: BogContext, f: impl FnOnce() -> T) -> T {
223 let (prev_bounds, prev_paused, prev_prefix, prev_suffix, prev_tag) = {
224 if let Some(b) = GLOBAL_BOGGER._lock().as_mut() {
225 let prev_bounds = b.bounds();
227 let prev_paused = prev_bounds.0.0 == u8::MAX;
228 let prev_prefix = b.prefix.clone();
229 let prev_suffix = b.suffix.clone();
230 let prev_tag = b.tag_override.clone();
231
232 if let Some(level) = context.bounds[0] {
234 b.filter_below(level);
235 }
236 if let Some(level) = context.bounds[1] {
237 b.downcast_above(level);
238 }
239 if let Some(ref prefix) = context.prefix {
240 b.prefix = prefix.clone();
241 }
242 if let Some(ref suffix) = context.suffix {
243 b.suffix = suffix.clone();
244 }
245 if let Some(ref tag) = context.tag_override {
246 b.tag_override = Some(tag.clone());
247 }
248 if context.pause {
249 b.pause();
250 }
251
252 (
253 Some(prev_bounds),
254 Some(prev_paused),
255 Some(prev_prefix),
256 Some(prev_suffix),
257 prev_tag,
258 )
259 } else {
260 (None, None, None, None, None)
261 }
262 };
263
264 let result = f();
266
267 if let Some(b) = GLOBAL_BOGGER._lock().as_mut() {
269 if let Some(bounds) = prev_bounds {
270 b.set_bounds(bounds);
271 }
272 if let Some(paused) = prev_paused {
273 if paused {
274 b.pause();
275 } else {
276 b.resume();
277 }
278 }
279 if let Some(prefix) = prev_prefix {
280 b.prefix = prefix;
281 }
282 if let Some(suffix) = prev_suffix {
283 b.suffix = suffix;
284 }
285 if let Some(tag) = prev_tag {
286 b.tag_override = Some(tag);
287 } else if context.tag_override.is_some() {
288 b.tag_override = None
289 }
290 }
291
292 result
293 }
294
295 #[inline]
297 pub fn paused<T>(f: impl FnOnce() -> T) -> T {
298 BOGGER::pause();
299 let ret = f();
300 BOGGER::resume();
301 ret
302 }
303
304 #[inline]
306 pub fn pause() {
307 if let Some(b) = GLOBAL_BOGGER._lock().as_mut() {
308 b.pause();
309 }
310 }
311
312 #[inline]
314 pub fn resume() {
315 if let Some(b) = GLOBAL_BOGGER._lock().as_mut() {
316 b.resume();
317 }
318 }
319}
320
321pub fn init_bogger(fg: bool, output_stderr: bool) {
323 let writer: Box<dyn Write + Send + Sync> = if output_stderr {
324 Box::new(stderr())
325 } else {
326 Box::new(stdout())
327 };
328
329 if fg {
330 init_(Box::new(Fg {}), writer);
331 } else {
332 init_(Box::new(Bg {}), writer);
333 }
334}
335
336pub fn init_filter(verbosity: u8) {
351 let level = match verbosity {
352 0 => {
353 GLOBAL_BOGGER
354 ._lock()
355 .as_mut()
356 .unwrap()
357 .filter_below_priority(u8::MAX);
358 return;
359 }
360 1 => BogLevel::NOTE,
361 2 => BogLevel::ERROR,
362 3 => BogLevel::WARN,
363 4 => BogLevel::INFO,
364 5 => BogLevel::_WRN,
365 6 => BogLevel::DEBUG,
366 _ => BogLevel::___,
367 };
368 log::debug!("Bogging level initialized at {level:?}");
369 BOGGER::filter_below(level);
370}
371
372#[macro_export]
374macro_rules! ibog {
375 ($($harg:expr),* ; $($arg:expr),*) => {{
377 $crate::BOGGER::bog(
378 $crate::bog::BogLevel::INFO,
379 &format!($($harg),*),
380 &format!($($arg),*),
381 );
382 }};
383 ($($arg:expr),*) => {{
385 $crate::BOGGER::bog(
386 $crate::bog::BogLevel::INFO,
387 "",
388 &format!($($arg),*),
389 );
390 }};
391}
392
393#[macro_export]
394macro_rules! dbog {
395 ($($harg:expr),* ; $($arg:expr),*) => {{
396 $crate::BOGGER::bog(
397 $crate::bog::BogLevel::DEBUG,
398 &format!($($harg),*),
399 &format!($($arg),*),
400 );
401 }};
402 ($($arg:expr),*) => {{
403 $crate::BOGGER::bog(
404 $crate::bog::BogLevel::DEBUG,
405 "",
406 &format!($($arg),*),
407 );
408 }};
409}
410
411#[macro_export]
412macro_rules! ebog {
413 ($($harg:expr),* ; $($arg:expr),*) => {{
414 $crate::BOGGER::bog(
415 $crate::bog::BogLevel::ERROR,
416 &format!($($harg),*),
417 &format!($($arg),*),
418 );
419 }};
420 ($($arg:expr),*) => {{
421 $crate::BOGGER::bog(
422 $crate::bog::BogLevel::ERROR,
423 "",
424 &format!($($arg),*),
425 );
426 }};
427}
428
429#[macro_export]
430macro_rules! wbog {
431 ($($harg:expr),* ; $($arg:expr),*) => {{
432 $crate::BOGGER::bog(
433 $crate::bog::BogLevel::WARN,
434 &format!($($harg),*),
435 &format!($($arg),*),
436 );
437 }};
438 ($($arg:expr),*) => {{
439 $crate::BOGGER::bog(
440 $crate::bog::BogLevel::WARN,
441 "",
442 &format!($($arg),*),
443 );
444 }};
445}
446
447#[macro_export]
448macro_rules! nbog {
449 ($($harg:expr),* ; $($arg:expr),*) => {{
450 $crate::BOGGER::bog(
451 $crate::bog::BogLevel::NOTE,
452 &format!($($harg),*),
453 &format!($($arg),*),
454 );
455 }};
456 ($($arg:expr),*) => {{
457 $crate::BOGGER::bog(
458 $crate::bog::BogLevel::NOTE,
459 "",
460 &format!($($arg),*),
461 );
462 }};
463}
464
465#[macro_export]
466macro_rules! mbog {
467 ($($harg:expr),* ; $($arg:expr),*) => {{
468 $crate::BOGGER::bog(
469 $crate::bog::BogLevel::EMPTY,
470 &format!($($harg),*),
471 &format!($($arg),*),
472 );
473 }};
474 ($($arg:expr),*) => {{
475 $crate::BOGGER::bog(
476 $crate::bog::BogLevel::EMPTY,
477 "",
478 &format!($($arg),*),
479 );
480 }};
481}
482
483#[macro_export]
484macro_rules! cbog {
485 ($discriminant:literal ; $($harg:expr),* ; $($arg:expr),*) => {{
486 $crate::BOGGER::bog(
487 $crate::bog::BogLevel::CUSTOM($discriminant),
488 &format!($($harg),*),
489 &format!($($arg),*),
490 );
491 }};
492 ($discriminant:literal ; $($arg:expr),*) => {{
493 $crate::BOGGER::bog(
494 $crate::bog::BogLevel::CUSTOM($discriminant),
495 "",
496 &format!($($arg),*),
497 );
498 }};
499}
500
501#[macro_export]
502macro_rules! _wbog {
503 ($($harg:expr),* ; $($arg:expr),*) => {{
504 $crate::BOGGER::bog(
505 $crate::bog::BogLevel::_WRN,
506 &format!($($harg),*),
507 &format!($($arg),*),
508 );
509 }};
510 ($($arg:expr),*) => {{
511 $crate::BOGGER::bog(
512 $crate::bog::BogLevel::_WRN,
513 "",
514 &format!($($arg),*),
515 );
516 }};
517}
518
519#[macro_export]
520macro_rules! _ibog {
521 ($($harg:expr),* ; $($arg:expr),*) => {{
522 $crate::BOGGER::bog(
523 $crate::bog::BogLevel::_NFO,
524 &format!($($harg),*),
525 &format!($($arg),*),
526 );
527 }};
528 ($($arg:expr),*) => {{
529 $crate::BOGGER::bog(
530 $crate::bog::BogLevel::_NFO,
531 "",
532 &format!($($arg),*),
533 );
534 }};
535}
536
537#[easy_ext::ext(BogOkExt)]
557pub impl<T, E: Display> Result<T, E> {
558 fn _bog_<'a>(self, level: BogLevel, tag: impl Into<Cow<'a, str>>) -> Option<T> {
559 match self {
560 Ok(val) => Some(val),
561 Err(e) => {
562 BOGGER::bog(level, &tag.into(), &e.to_string());
563 None
564 }
565 }
566 }
567
568 fn _ebog_<'a>(self, tag: impl Into<Cow<'a, str>>) -> Option<T> {
569 self._bog_(BogLevel::ERROR, tag)
570 }
571
572 fn _ibog_<'a>(self, tag: impl Into<Cow<'a, str>>) -> Option<T> {
573 self._bog_(BogLevel::INFO, tag)
574 }
575
576 fn _dbog_<'a>(self, tag: impl Into<Cow<'a, str>>) -> Option<T> {
577 self._bog_(BogLevel::DEBUG, tag)
578 }
579
580 fn _wbog_<'a>(self, tag: impl Into<Cow<'a, str>>) -> Option<T> {
581 self._bog_(BogLevel::WARN, tag)
582 }
583
584 fn _bog(self, level: BogLevel) -> Option<T> {
585 self._bog_(level, "")
586 }
587
588 fn _ebog(self) -> Option<T> {
589 self._ebog_("")
590 }
591
592 fn __ebog(self) -> T {
593 self._ebog_("").or_exit()
594 }
595
596 fn _wbog(self) -> Option<T> {
597 self._wbog_("")
598 }
599
600 fn _dbog(self) -> Option<T> {
601 self._dbog_("")
602 }
603 fn _ibog(self) -> Option<T> {
604 self._ibog_("")
605 }
606}
607
608#[easy_ext::ext(BogUnwrapExt)]
609pub impl<T> Option<T> {
610 fn _bog_<'a>(
612 self,
613 level: BogLevel,
614 tag: impl Into<Cow<'a, str>>,
615 msg: impl Into<Cow<'a, str>>,
616 ) -> T {
617 match self {
618 Some(val) => val,
619 None => {
620 BOGGER::bog(level, &tag.into(), &msg.into());
621 std::process::exit(1);
622 }
623 }
624 }
625
626 fn _bog<'a>(self, level: BogLevel, msg: impl Into<Cow<'a, str>>) -> T {
628 self._bog_(level, "", msg)
629 }
630
631 fn _ebog<'a>(self, msg: impl Into<Cow<'a, str>>) -> T {
633 self._bog(BogLevel::ERROR, msg)
634 }
635
636 fn _ebog_<'a>(self, tag: impl Into<Cow<'a, str>>, msg: impl Into<Cow<'a, str>>) -> T {
638 self._bog_(BogLevel::ERROR, tag, msg)
639 }
640
641 fn bog_<'a>(
642 self,
643 level: BogLevel,
644 tag: impl Into<Cow<'a, str>>,
645 msg: impl Into<Cow<'a, str>>,
646 ) -> Option<T> {
647 match self {
648 Some(val) => Some(val),
649 None => {
650 BOGGER::bog(level, &tag.into(), &msg.into());
651 None
652 }
653 }
654 }
655 fn bog<'a>(self, level: BogLevel, msg: impl Into<Cow<'a, str>>) -> Option<T> {
656 self.bog_(level, "", msg)
657 }
658 fn ebog<'a>(self, msg: impl Into<Cow<'a, str>>) -> Option<T> {
659 self.bog(BogLevel::ERROR, msg)
660 }
661 fn ebog_<'a>(self, msg: impl Into<Cow<'a, str>>) -> Option<T> {
662 self.bog(BogLevel::ERROR, msg)
663 }
664}
665
666#[cfg(test)]
667mod test {
668 use super::*;
669
670 #[test]
671 fn show_fg_bogger() {
672 init_bogger(true, false);
673 dbog!("DEBUG message: {}", 3.14159);
675 dbog!("val"; "DEBUG values: x={}, y={}", 10, 20);
676
677 ibog!("INFO message: {}", 42);
679 ibog!("Created Directory"; "~/archr/Desktop");
680
681 wbog!("WARN message: {}", "disk almost full");
683 wbog!("NoSpace"; "WARN message: {} attempts left", 3);
684
685 ebog!("ERROR message: {}", "file not found");
687 ebog!("404"; "Not found");
688
689 nbog!("justification");
691 nbog!("NOTE"; "ancillary");
692 mbog!("justification");
693 mbog!("FULL TAG"; "ancillary");
694
695 cbog!("NOTE"; "Custom note message: {}", "all good");
697 cbog!("NOTE"; ""; "Custom note with tag: {}", 123);
698 cbog!("CUSTOM"; "Custom discriminant"; "Message with both tag and content");
699 }
700
701 #[test]
702 fn show_bg_bogger() {
703 init_bogger(false, true);
704 dbog!("DEBUG message: {}", 3.14159);
706 dbog!("val"; "DEBUG values: x={}, y={}", 10, 20);
707
708 ibog!("INFO message: {}", 42);
710 ibog!("Urgent"; "INFO message number {}", 7);
711
712 wbog!("WARN message: {}", "disk almost full");
714 wbog!("NoSpace"; "WARN message: {} attempts left", 3);
715
716 ebog!("ERROR message: {}", "file not found");
718 ebog!("404"; "Not found");
719
720 nbog!("justification");
722 nbog!("NOTE"; "ancillary");
723 mbog!("justification");
724 mbog!("FULL"; "ancillary");
725
726 cbog!("NOTE"; "Custom note message: {}", "all good");
728 cbog!("NOTE"; ""; "Custom note with tag: {}", 123);
729 cbog!("CUSTOM"; "Custom discriminant"; "Message with both tag and content");
730 }
731
732 #[test]
733 fn min_level_and_downcast_combined() {
734 init_bogger(true, false);
735
736 BOGGER::filter_below(BogLevel::INFO);
738 BOGGER::downcast_above(BogLevel::WARN);
740
741 dbog!("debug filtered");
742 ibog!("info normal");
743 ebog!("error shown as warn");
744 }
745}
746
747impl fmt::Debug for GLOBAL_BOGGER_STRUCT {
749 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
750 let mut ds = f.debug_struct("GLOBAL_BOGGER_STRUCT");
751
752 ds.field("min_level", &self.min_level)
753 .field("downcast_to", &self.downcast_to)
754 .field("prefix", &self.prefix)
755 .field("suffix", &self.suffix)
756 .field("tag_override", &self.tag_override)
757 .field("log", &self.log);
758
759 ds.field("formatter", &"dyn BogFmter")
761 .field("writer", &"dyn Write");
762
763 ds.finish()
764 }
765}