1use std::{
2 cell::RefCell,
3 error::Error as ErrorTrait,
4 fmt, io,
5 num::{ParseFloatError, ParseIntError},
6 result,
7};
8
9use once_cell::sync::Lazy;
10
11use crate::common::arena::SymHashMap;
12
13#[derive(Debug, Clone, Default)]
14pub struct LogState {
15 pub undefined: SymHashMap<usize>,
16 pub missing: SymHashMap<usize>,
17 pub debug: usize,
18 pub info: usize,
19 pub warning: usize,
20 pub error: usize,
21 pub fatal: bool,
22 pub status_code: usize,
23}
24pub enum LogStatus {
25 Debug,
26 Info,
27 Warning,
28 Error,
29 Fatal,
30 Undefined,
31 Missing,
32}
33
34#[thread_local]
35pub static REPORT: Lazy<RefCell<LogState>> = Lazy::new(|| RefCell::new(LogState::default()));
36
37#[thread_local]
41static MACRO_DIAG_DEPTH: std::cell::Cell<u32> = std::cell::Cell::new(0);
42
43pub struct MacroDiagGuard(());
54impl Drop for MacroDiagGuard {
55 fn drop(&mut self) { MACRO_DIAG_DEPTH.set(MACRO_DIAG_DEPTH.get().saturating_sub(1)); }
56}
57#[must_use = "the guard must live across the log emission it marks"]
58pub fn macro_diag_guard() -> MacroDiagGuard {
59 MACRO_DIAG_DEPTH.set(MACRO_DIAG_DEPTH.get() + 1);
60 MacroDiagGuard(())
61}
62
63pub fn note_status_from_logger(status: LogStatus) {
69 if MACRO_DIAG_DEPTH.get() > 0 || REPORT.try_borrow_mut().is_err() {
70 return;
71 }
72 note_status(status, None);
73}
74
75#[thread_local]
81static SUPPRESS_LOG_OUTPUT: std::cell::Cell<bool> = std::cell::Cell::new(false);
82
83pub fn set_suppress_log_output(suppress: bool) -> bool {
85 let prev = SUPPRESS_LOG_OUTPUT.get();
86 SUPPRESS_LOG_OUTPUT.set(suppress);
87 prev
88}
89
90thread_local! {
100 static DEMOTE_FATALS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
101}
102
103pub fn set_demote_fatals(demote: bool) -> bool {
105 DEMOTE_FATALS.with(|c| {
106 let prev = c.get();
107 c.set(demote);
108 prev
109 })
110}
111
112pub fn is_demote_fatals() -> bool { DEMOTE_FATALS.with(|c| c.get()) }
114
115pub fn is_log_output_suppressed() -> bool { SUPPRESS_LOG_OUTPUT.get() }
117
118#[thread_local]
126static LAST_ERROR_KEY: RefCell<Option<String>> = RefCell::new(None);
127#[thread_local]
128static CONSECUTIVE_ERROR_COUNT: std::cell::Cell<usize> = std::cell::Cell::new(0);
129
130pub const MAX_CONSECUTIVE_ERRORS: usize = 500;
140
141pub fn note_consecutive_error(key: &str) -> usize {
145 let mut last = LAST_ERROR_KEY.borrow_mut();
146 if last.as_deref() == Some(key) {
147 let c = CONSECUTIVE_ERROR_COUNT.get() + 1;
148 CONSECUTIVE_ERROR_COUNT.set(c);
149 c
150 } else {
151 *last = Some(key.to_string());
152 CONSECUTIVE_ERROR_COUNT.set(1);
153 1
154 }
155}
156
157pub fn emit_record(status: LogStatus, target: &str, message: &str) {
167 let _diag_guard = macro_diag_guard();
168 let level = match status {
169 LogStatus::Debug => log::Level::Debug,
170 LogStatus::Info => log::Level::Info,
171 LogStatus::Warning => log::Level::Warn,
172 _ => log::Level::Error,
173 };
174 let unconditional = matches!(status, LogStatus::Error | LogStatus::Fatal);
175 note_status(status, None);
176 if unconditional || !is_log_output_suppressed() {
177 log::log!(target: target, level, "{message}");
178 }
179}
180
181pub fn emit_info(category: &str, object: &str, message: &str) {
198 emit_record(LogStatus::Info, &format!("{category}:{object}"), message);
199}
200
201pub fn emit_warn(category: &str, object: &str, message: &str) {
203 emit_record(LogStatus::Warning, &format!("{category}:{object}"), message);
204}
205
206pub fn emit_error(category: &str, object: &str, message: &str) {
213 emit_record(LogStatus::Error, &format!("{category}:{object}"), message);
214 if is_demote_fatals() {
215 return;
216 }
217 let maxerrors = match crate::state::try_lookup_int("MAX_ERRORS") {
218 None => usize::MAX, Some(v) if v > 0 => v as usize,
220 Some(_) => 100,
221 };
222 let consec = note_consecutive_error(&format!("{category}:{object}"));
223 let over_total = get_status(LogStatus::Error) > maxerrors;
224 let over_consec = consec > MAX_CONSECUTIVE_ERRORS;
225 if (over_total && get_status(LogStatus::Error) == maxerrors + 1)
227 || (over_consec && consec == MAX_CONSECUTIVE_ERRORS + 1)
228 {
229 emit_fatal(
230 "TooManyErrors",
231 "MaxLimit",
232 &format!(
233 "Too many errors (> {})!",
234 if over_total {
235 maxerrors
236 } else {
237 MAX_CONSECUTIVE_ERRORS
238 }
239 ),
240 );
241 }
242}
243
244pub fn emit_fatal(category: &str, object: &str, message: &str) {
250 emit_record(
251 LogStatus::Fatal,
252 &format!("Fatal:{category}:{object} "),
253 message,
254 );
255}
256
257fn reset_consecutive_error_tracker() {
259 *LAST_ERROR_KEY.borrow_mut() = None;
260 CONSECUTIVE_ERROR_COUNT.set(0);
261}
262#[macro_export]
263macro_rules! report {
264 () => {
265 (*$crate::common::error::REPORT).borrow()
266 };
267}
268#[macro_export]
269macro_rules! report_mut {
270 () => {
271 (*$crate::common::error::REPORT).borrow_mut()
272 };
273}
274
275pub fn clear_fatal_flag() {
281 let mut report = REPORT.borrow_mut();
282 report.fatal = false;
283}
284
285pub fn note_status(status: LogStatus, what: Option<&str>) {
286 let mut report = REPORT.borrow_mut();
287 use LogStatus::*;
288 match status {
289 Debug => report.debug += 1,
290 Info => report.info += 1,
291 Warning => report.warning += 1,
292 Error => report.error += 1,
293 Fatal => {
294 if !report.fatal && debug_fatal_enabled() {
299 eprintln!("[debug-fatal] LogStatus::Fatal first noted here:");
300 eprintln!("{}", std::backtrace::Backtrace::force_capture());
301 }
302 report.fatal = true;
303 },
304 Undefined => {
305 let key = what.unwrap_or_default().to_string();
310 let entry = report.undefined.entry(&key).or_insert(0);
311 *entry += 1;
312 },
313 Missing => {
314 let key = what.unwrap_or_default().to_string();
315 let entry = report.missing.entry(&key).or_insert(0);
316 *entry += 1;
317 },
318 }
319}
320
321pub fn get_status(status: LogStatus) -> usize {
322 let report = REPORT.borrow();
323 use LogStatus::*;
324 match status {
325 Debug => report.debug,
326 Info => report.info,
327 Warning => report.warning,
328 Error => report.error,
329 Fatal => {
330 if report.fatal {
331 1
332 } else {
333 0
334 }
335 },
336 Undefined => report.undefined.0.values().sum(),
337 Missing => report.missing.0.values().sum(),
338 }
339}
340
341pub fn debug_fatal_enabled() -> bool {
346 use std::sync::OnceLock;
347 static FLAG: OnceLock<bool> = OnceLock::new();
348 *FLAG.get_or_init(|| std::env::var_os("LATEXML_DEBUG_FATAL").is_some())
349}
350
351pub fn initialize_report() {
352 let mut report = REPORT.borrow_mut();
353 *report = LogState::default();
354 reset_consecutive_error_tracker();
355 LAST_RESOURCE_FATAL.with(|c| *c.borrow_mut() = None);
356}
357
358pub fn reset_arena_keyed_reports() {
364 let mut report = REPORT.borrow_mut();
365 report.undefined = Default::default();
366 report.missing = Default::default();
367}
368
369thread_local! {
370 static LAST_RESOURCE_FATAL: RefCell<Option<Error>> = const { RefCell::new(None) };
380}
381
382pub fn record_last_fatal(e: &Error) {
387 use ErrorCategory as C;
388 if !matches!(e.target, ErrorTarget::Timeout) {
389 return;
390 }
391 let category = match &e.category {
392 C::TokenLimit => C::TokenLimit,
393 C::PushbackLimit => C::PushbackLimit,
394 C::Recursion => C::Recursion,
395 C::IfLimit => C::IfLimit,
396 C::MemoryBudget => C::MemoryBudget,
397 C::Convert => C::Convert,
398 _ => return,
399 };
400 LAST_RESOURCE_FATAL.with(|c| {
401 *c.borrow_mut() = Some(Error {
402 target: ErrorTarget::Timeout,
403 category,
404 message: e.message.clone(),
405 });
406 });
407}
408
409pub fn take_last_resource_fatal() -> Option<Error> {
413 LAST_RESOURCE_FATAL.with(|c| c.borrow_mut().take())
414}
415
416pub fn conversion_status_line(code: usize) -> String { format!("Status:conversion:{code}") }
427
428pub fn conversion_verdict(code: usize) -> String {
434 format!(
435 "Conversion {}: {}",
436 if code >= 3 { "failed" } else { "complete" },
437 get_status_message()
438 )
439}
440
441pub fn get_status_message() -> String {
442 let report = REPORT.borrow();
443 let mut parts = Vec::new();
444 if report.warning > 0 {
445 parts.push(format!(
446 "{} warning{}",
447 report.warning,
448 if report.warning > 1 { "s" } else { "" }
449 ));
450 }
451 if report.error > 0 {
452 parts.push(format!(
453 "{} error{}",
454 report.error,
455 if report.error > 1 { "s" } else { "" }
456 ));
457 }
458 if report.fatal {
459 parts.push("1 fatal error".to_string());
460 }
461 let undef_keys: Vec<String> = report
462 .undefined
463 .keys()
464 .map(|k| crate::common::arena::to_string(*k))
465 .collect();
466 if !undef_keys.is_empty() {
467 parts.push(format!(
468 "{} undefined macro{}[{}]",
469 undef_keys.len(),
470 if undef_keys.len() > 1 { "s" } else { "" },
471 undef_keys.join(", ")
472 ));
473 }
474 let miss_keys: Vec<String> = report
475 .missing
476 .keys()
477 .map(|k| crate::common::arena::to_string(*k))
478 .collect();
479 if !miss_keys.is_empty() {
480 parts.push(format!(
481 "{} missing file{}[{}]",
482 miss_keys.len(),
483 if miss_keys.len() > 1 { "s" } else { "" },
484 miss_keys.join(", ")
485 ));
486 }
487 if parts.is_empty() {
488 "No obvious problems".to_string()
489 } else {
490 parts.join("; ")
491 }
492}
493
494pub fn get_status_code() -> usize {
497 let report = REPORT.borrow();
498 if report.fatal {
499 3
500 } else if report.error > 0 {
501 2
502 } else if report.warning > 0 {
503 1
504 } else {
505 0
506 }
507}
508
509#[derive(Default, Clone, Copy)]
518pub struct ReportCounts {
519 pub debug: usize,
520 pub info: usize,
521 pub warning: usize,
522 pub error: usize,
523 pub fatal: bool,
524}
525
526pub fn snapshot_report_counts() -> ReportCounts {
528 let r = REPORT.borrow();
529 ReportCounts {
530 debug: r.debug,
531 info: r.info,
532 warning: r.warning,
533 error: r.error,
534 fatal: r.fatal,
535 }
536}
537
538pub fn restore_report_counts(c: ReportCounts) {
545 let mut r = REPORT.borrow_mut();
546 r.debug = c.debug;
547 r.info = c.info;
548 r.warning = c.warning;
549 r.error = c.error;
550 r.fatal = c.fatal;
551}
552
553pub fn merge_report_counts(c: ReportCounts) {
558 let mut r = REPORT.borrow_mut();
559 r.debug += c.debug;
560 r.info += c.info;
561 r.warning += c.warning;
562 r.error += c.error;
563 r.fatal |= c.fatal;
564}
565
566static KNOWN_DEBUG_FEATURES: Lazy<std::sync::RwLock<std::collections::BTreeSet<String>>> =
575 Lazy::new(|| std::sync::RwLock::new(std::collections::BTreeSet::new()));
576static ENABLED_DEBUG_FEATURES: Lazy<std::sync::RwLock<rustc_hash::FxHashSet<String>>> =
577 Lazy::new(|| std::sync::RwLock::new(rustc_hash::FxHashSet::default()));
578
579pub fn debuggable_feature(name: &str) {
582 if let Ok(mut known) = KNOWN_DEBUG_FEATURES.write() {
583 known.insert(name.to_string());
584 }
585}
586
587pub fn known_debug_features() -> Vec<String> {
589 KNOWN_DEBUG_FEATURES
590 .read()
591 .map(|k| k.iter().cloned().collect())
592 .unwrap_or_default()
593}
594
595pub fn enable_debug_feature(name: &str) {
597 if let Ok(mut enabled) = ENABLED_DEBUG_FEATURES.write() {
598 enabled.insert(name.to_string());
599 }
600}
601
602pub fn debug_enabled(name: &str) -> bool {
604 ENABLED_DEBUG_FEATURES
605 .read()
606 .map(|enabled| enabled.contains(name))
607 .unwrap_or(false)
608}
609
610#[macro_export]
618macro_rules! DebugFeature {
619 ($feature:literal, $($arg:tt)*) => {{
620 if $crate::common::error::debug_enabled($feature) {
621 let __diag_guard = $crate::common::error::macro_diag_guard();
622 $crate::common::error::note_status(
623 $crate::common::error::LogStatus::Debug, None);
624 use log::debug;
625 debug!(target: $feature, $($arg)*);
626 }
627 }};
628}
629
630#[macro_export]
631macro_rules! Debug {
632 ($category:expr_2021, $object:expr_2021, $message:expr_2021) => {{
633 $crate::common::error::emit_record(
634 $crate::common::error::LogStatus::Debug,
635 &format!("{}:{}", $category, $object),
636 &$crate::generate_message!($message))
637 }};
638 ($category:expr_2021, $object:expr_2021, $message:expr_2021, $($details:expr_2021),*) => {{
639 $crate::common::error::emit_record(
640 $crate::common::error::LogStatus::Debug,
641 &format!("{}:{}", $category, $object),
642 &$crate::generate_message!($message, $($details),*))
643 }};
644 ($($simple:expr_2021),*) => {{
645 let __diag_guard = $crate::common::error::macro_diag_guard();
646 $crate::common::error::note_status(
647 $crate::common::error::LogStatus::Debug, None);
648 use log::debug;
649 debug!($($simple),*);
650 }};
651
652}
653
654#[macro_export]
655macro_rules! Info {
656 ($category:expr_2021, $object:expr_2021, $message:expr_2021) => {{
657 $crate::common::error::emit_info(
658 &format!("{}", $category), &format!("{}", $object),
659 &$crate::generate_message!($message))
660 }};
661 ($category:expr_2021, $object:expr_2021, $message:expr_2021, $($details:expr_2021),*) => {{
662 $crate::common::error::emit_info(
663 &format!("{}", $category), &format!("{}", $object),
664 &$crate::generate_message!($message, $($details),*))
665 }};
666 ($($simple:expr_2021),*) => {{
667 let __diag_guard = $crate::common::error::macro_diag_guard();
668 $crate::common::error::note_status(
669 $crate::common::error::LogStatus::Info, None);
670 use log::info;
671 info!($($simple),*);
672 }};
673
674}
675
676#[macro_export]
677macro_rules! Warn {
678 ($category:expr_2021, $object:expr_2021, $message:expr_2021) => {{
679 $crate::common::error::emit_warn(
680 &format!("{}", $category), &format!("{}", $object),
681 &$crate::generate_message!($message))
682 }};
683 ($category:expr_2021, $object:expr_2021, $message:expr_2021, $($details:expr_2021),*) => {{
684 $crate::common::error::emit_warn(
685 &format!("{}", $category), &format!("{}", $object),
686 &$crate::generate_message!($message, $($details),*))
687 }}
688}
689
690#[macro_export]
691macro_rules! Error {
692 ($category:expr_2021, $object:expr_2021, $message:expr_2021) => {{
693 $crate::Error!($category,$object,$message,"")
694 }};
695 ($category:expr_2021, $object:expr_2021, $message:expr_2021, $($details:expr_2021),*) => {{
696 $crate::common::error::emit_record(
697 $crate::common::error::LogStatus::Error,
698 &format!("{}:{}", $category, $object),
699 &$crate::generate_message!($message, $($details),*),
700 );
701 if !$crate::common::error::is_demote_fatals() {
708 let max_from_state = $crate::state::try_lookup_int("MAX_ERRORS");
713 let maxerrors = match max_from_state {
719 None => usize::MAX,
723 Some(v) if v > 0 => v as usize,
724 Some(_) => 100,
725 };
726 if $crate::common::error::get_status($crate::common::error::LogStatus::Error) > maxerrors {
727 Fatal!(TooManyErrors, MaxLimit(maxerrors), format!("Too many errors (> {maxerrors})!"));
728 }
729 let __consec_key = format!("{}:{}", $category, $object);
738 let __consec = $crate::common::error::note_consecutive_error(&__consec_key);
739 if __consec > $crate::common::error::MAX_CONSECUTIVE_ERRORS {
740 Fatal!(
741 TooManyErrors,
742 MaxLimit($crate::common::error::MAX_CONSECUTIVE_ERRORS),
743 format!(
744 "Runaway: same error '{}' fired {} times in a row (cap = {})",
745 __consec_key, __consec, $crate::common::error::MAX_CONSECUTIVE_ERRORS
746 )
747 );
748 }
749 }
750 }}
751}
752
753#[macro_export]
755macro_rules! Fatal {
756 ($target:expr_2021, $category:expr_2021, $message:expr_2021) => {{
757 if $crate::common::error::is_demote_fatals() {
758 $crate::common::error::emit_record(
764 $crate::common::error::LogStatus::Error,
765 "demoted_fatal",
766 &format!("{}", $message),
767 );
768 } else {
769 $crate::common::error::note_status($crate::common::error::LogStatus::Fatal, None);
770 }
771 {
772 use $crate::common::error::{Error as LatexmlError, ErrorCategory::*, ErrorTarget::*};
773 let __fatal_err = LatexmlError {
774 target: $target,
775 category: $category,
776 message: $message.to_string(),
777 };
778 $crate::common::error::record_last_fatal(&__fatal_err);
782 return Err(__fatal_err);
783 }
784 }};
785}
786
787#[macro_export]
788macro_rules! fatal {
789 ($target:expr_2021, $category:expr_2021, $message:expr_2021) => {{
790 use $crate::common::error::{Error as LatexmlError, ErrorCategory::*, ErrorTarget::*};
791 return Err(LatexmlError {
792 target: $target,
793 category: $category,
794 message: $message.to_string(),
795 });
796 }};
797}
798
799#[macro_export]
800macro_rules! generate_message {
801 ($message:expr_2021) => {
802 format!(
803 "{}\n\t{}\n\tIn {}:{}:{}\n",
804 $message,
805 $crate::gullet::get_location(),
806 file!(),
807 line!(),
808 column!()
809 )
810 };
811 ($message:expr_2021, $detail:expr_2021) => {
812 format!(
813 "{}\n\t{}\n\t{}\n\tIn {}:{}:{}\n",
814 $message,
815 $crate::gullet::get_location(),
816 $detail,
817 file!(),
818 line!(),
819 column!()
820 )
821 };
822 ($message:expr_2021, $detail:expr_2021, $detail2:expr_2021) => {
823 format!(
824 "{}\n\t{}\n\t{}\n\t{}\n\tIn {}:{}:{}\n",
825 $message,
826 $crate::gullet::get_location(),
827 $detail,
828 $detail2,
829 file!(),
830 line!(),
831 column!()
832 )
833 };
834 ($message:expr_2021, $detail:expr_2021, $detail2:expr_2021) => {
835 format!(
836 "{}\n\t{}\n\t{}\n\t{}\n\tIn {}:{}:{}\n",
837 $message,
838 $crate::gullet::get_location(),
839 $detail,
840 $detail2,
841 file!(),
842 line!(),
843 column!()
844 )
845 };
846 ($message:expr_2021, $detail:expr_2021, $detail2:expr_2021, $location:expr_2021) => {
847 format!(
848 "{}\n\t{}\n\t{}\n\t{}\n\tIn {}:{}:{}\n",
849 $message,
850 $location,
851 $detail,
852 $detail2,
853 file!(),
854 line!(),
855 column!()
856 )
857 };
858}
859
860#[macro_export]
864macro_rules! Note {
865 ($input:expr_2021) => {{
866 let msg = $input;
867 $crate::util::logger::note_to_log(&msg.to_string());
868 if !$crate::common::error::is_log_output_suppressed()
869 && log::max_level() >= log::LevelFilter::Info
870 {
871 $crate::println_stderr!("{msg}");
872 $crate::util::logger::mark_stderr_at_line_start();
873 }
874 }};
875}
876
877#[macro_export]
880macro_rules! NoteLog {
881 ($input:expr_2021) => {
882 $crate::util::logger::note_to_log(&($input).to_string());
883 };
884}
885
886#[macro_export]
889macro_rules! NoteSTDERR {
890 ($input:expr_2021) => {
891 if !$crate::common::error::is_log_output_suppressed()
892 && log::max_level() >= log::LevelFilter::Info
893 {
894 let msg = $input;
895 $crate::println_stderr!("{msg}");
896 $crate::util::logger::mark_stderr_at_line_start();
897 }
898 };
899}
900
901pub type Result<T> = result::Result<T, Error>;
902
903#[derive(Debug)]
904pub struct Error {
905 pub target: ErrorTarget,
906 pub category: ErrorCategory,
907 pub message: String,
908}
909impl ErrorTrait for Error {}
910unsafe impl Send for Error {}
916unsafe impl Sync for Error {}
917
918#[derive(Debug)]
919pub enum ErrorCategory {
920 Init,
921 Io(io::Error),
922 NotFound,
923 Unexpected,
924 Expected,
925 Misdefined,
926 Unknown,
927 MissingFile,
928 Malformed,
929 Libxml,
930 Convert,
931 Recursion,
932 EoF,
933 Endgroup,
934 FailedParse,
935 MaxLimit(usize),
936 Generic(Box<dyn ErrorTrait>),
937 Filename(String),
938 ToDo,
939 TokenLimit,
940 PushbackLimit,
941 IfLimit,
942 MemoryBudget,
943}
944
945#[derive(Debug)]
946pub enum ErrorTarget {
947 Package,
948 Parameter,
949 ParamSpec,
950 Prototype,
951 Converter,
952 Mouth,
953 Core,
954 State,
955 Stomach,
956 Codegen,
957 Macro,
958 XMath,
959 MathParser,
960 Document,
961 Definition,
962 TexPool,
963 Internal,
964 TargetUnexpected,
965 TooManyErrors,
966 Timeout,
967}
968
969impl fmt::Display for ErrorCategory {
970 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
971 use ErrorCategory::*;
972 match self {
973 Init => write!(f, "Init"),
974 Io(err) => err.fmt(f),
975 NotFound => write!(f, "No matching cities with a population were found."),
976 MissingFile => write!(f, "missing file"),
977 Misdefined => write!(f, "misdefined"),
978 Unknown => write!(f, "unknown"),
979 Malformed => write!(f, "malformed"),
980 Expected => write!(f, "expected"),
981 Unexpected => write!(f, "unexpected"),
982 Libxml => write!(f, "libxml error"),
983 Recursion => write!(f, "<recursion>"),
984 EoF => write!(f, "<EOF>"),
985 ToDo => write!(f, "TODO"),
986 Convert => write!(f, "conversion"),
987 Endgroup => write!(f, "<endgroup>"),
988 FailedParse => write!(f, "failed to parse"),
989 MaxLimit(num) => write!(f, "{}", num),
990 Generic(err) => err.fmt(f),
991 Filename(name) => write!(f, "file:{name}"),
992 TokenLimit => write!(f, "token_limit"),
993 PushbackLimit => write!(f, "pushback_limit"),
994 IfLimit => write!(f, "if_limit"),
995 MemoryBudget => write!(f, "memory_budget"),
996 }
997 }
998}
999impl fmt::Display for Error {
1000 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1001 write!(
1002 f,
1003 "Error:{}:{:?} {}",
1004 self.category, self.target, self.message
1005 )
1006 }
1007}
1008
1009impl Error {
1010 pub fn log_fatal(&self) {
1011 emit_record(
1017 LogStatus::Fatal,
1018 &s!("Fatal:{:?}:{:?} ", self.target, self.category),
1019 &self.message,
1020 );
1021 }
1022 pub fn todo() -> Self {
1023 Error {
1024 target: ErrorTarget::Internal,
1025 category: ErrorCategory::ToDo,
1026 message: String::from(
1027 "This section of the code is not yet implemented / ported over from Perl.",
1028 ),
1029 }
1030 }
1031}
1032
1033#[macro_export]
1034macro_rules! unported {
1035 () => {{ ::latexml_core::common::error::Error::todo() }};
1036}
1037
1038impl From<io::Error> for Error {
1039 fn from(err: io::Error) -> Error {
1040 Error {
1041 target: ErrorTarget::Mouth,
1042 category: ErrorCategory::Io(err),
1043 message: s!("IO error"),
1044 }
1045 }
1046}
1047
1048impl From<Box<dyn ErrorTrait>> for Error {
1049 fn from(err: Box<dyn ErrorTrait>) -> Error {
1050 Error {
1051 target: ErrorTarget::Document,
1052 message: err.to_string(),
1053 category: ErrorCategory::Generic(err),
1054 }
1055 }
1056}
1057impl From<Box<dyn ErrorTrait + Send + Sync>> for Error {
1058 fn from(err: Box<dyn ErrorTrait + Send + Sync>) -> Error {
1059 Error {
1060 target: ErrorTarget::Document,
1061 message: err.to_string(),
1062 category: ErrorCategory::Generic(err),
1063 }
1064 }
1065}
1066
1067impl From<String> for Error {
1068 fn from(err: String) -> Error {
1069 Error {
1070 target: ErrorTarget::Document,
1071 category: ErrorCategory::Generic(From::from(err.clone())),
1072 message: err,
1073 }
1074 }
1075}
1076
1077impl<'a> From<&'a str> for Error {
1078 fn from(err: &'a str) -> Error {
1079 Error {
1080 target: ErrorTarget::Document,
1081 category: ErrorCategory::Generic(From::from(err.to_owned())),
1082 message: err.to_owned(),
1083 }
1084 }
1085}
1086
1087impl From<()> for Error {
1088 fn from(_e: ()) -> Error {
1089 Error {
1090 target: ErrorTarget::Document,
1091 category: ErrorCategory::Libxml,
1092 message: s!("LibXML error"),
1093 }
1094 }
1095}
1096
1097impl From<ParseIntError> for Error {
1098 fn from(err: ParseIntError) -> Error {
1099 Error {
1100 target: ErrorTarget::Document,
1101 message: err.to_string(),
1102 category: ErrorCategory::Generic(Box::new(err)),
1103 }
1104 }
1105}
1106
1107impl From<ParseFloatError> for Error {
1108 fn from(err: ParseFloatError) -> Error {
1109 Error {
1110 target: ErrorTarget::Document,
1111 message: err.to_string(),
1112 category: ErrorCategory::Generic(Box::new(err)),
1113 }
1114 }
1115}
1116
1117impl From<marpa::error::Error> for Error {
1118 fn from(err: marpa::error::Error) -> Error {
1119 Error {
1120 target: ErrorTarget::MathParser,
1121 category: ErrorCategory::FailedParse,
1122 message: err.to_string(),
1123 }
1124 }
1125}
1126
1127pub fn progress_step(_note: &str) {
1140 }
1143
1144pub fn note_progress(stuff: &str) {
1145 use log::info;
1146 info!(target: "note", "{}", stuff);
1147}
1148
1149pub fn note_progress_detailed(stuff: &str) {
1151 use log::debug;
1152 debug!(target: "note", "{}", stuff);
1153}
1154
1155pub fn note_begin(stage: &str) {
1164 use log::info;
1166 info!(target: "note", "\n({}...", stage);
1167}
1168
1169pub fn note_end(_stage: &str) {
1175 use log::info;
1181 info!(target: "note", " )");
1182}
1183
1184#[cfg(test)]
1185mod tests {
1186 use super::*;
1187
1188 #[test]
1195 fn initialize_report_clears_state() {
1196 note_status(LogStatus::Warning, None);
1197 initialize_report();
1198 assert_eq!(get_status(LogStatus::Warning), 0);
1199 }
1200
1201 #[test]
1202 fn note_status_increments_counters() {
1203 initialize_report();
1204 note_status(LogStatus::Warning, None);
1205 note_status(LogStatus::Warning, None);
1206 note_status(LogStatus::Error, None);
1207 assert_eq!(get_status(LogStatus::Warning), 2);
1208 assert_eq!(get_status(LogStatus::Error), 1);
1209 assert_eq!(get_status(LogStatus::Fatal), 0);
1210 }
1211
1212 #[test]
1213 fn fatal_macro_latches_resource_fatals() {
1214 initialize_report();
1218 fn raise() -> Result<()> {
1219 Fatal!(
1220 Timeout,
1221 TokenLimit,
1222 "Token limit of 5 exceeded, infinite loop?"
1223 );
1224 }
1225 let err = raise().unwrap_err();
1226 assert!(matches!(err.target, ErrorTarget::Timeout));
1227 let latched = take_last_resource_fatal().expect("latch must hold the fatal");
1228 assert!(matches!(latched.target, ErrorTarget::Timeout));
1229 assert!(matches!(latched.category, ErrorCategory::TokenLimit));
1230 assert_eq!(latched.message, "Token limit of 5 exceeded, infinite loop?");
1231 assert!(take_last_resource_fatal().is_none());
1233 fn raise_other() -> Result<()> {
1235 Fatal!(Internal, EoF, "fell off the end");
1236 }
1237 let _ = raise_other().unwrap_err();
1238 assert!(take_last_resource_fatal().is_none());
1239 let _ = raise().unwrap_err();
1241 initialize_report();
1242 assert!(take_last_resource_fatal().is_none());
1243 }
1244
1245 #[test]
1246 fn fatal_status_is_sticky_and_returns_1() {
1247 initialize_report();
1248 note_status(LogStatus::Fatal, None);
1249 note_status(LogStatus::Fatal, None);
1250 assert_eq!(get_status(LogStatus::Fatal), 1);
1252 }
1253
1254 #[test]
1255 fn get_status_code_priority_order() {
1256 initialize_report();
1257 assert_eq!(get_status_code(), 0, "clean state → 0");
1258 note_status(LogStatus::Warning, None);
1259 assert_eq!(get_status_code(), 1, "warning → 1");
1260 note_status(LogStatus::Error, None);
1261 assert_eq!(get_status_code(), 2, "error wins over warning → 2");
1262 note_status(LogStatus::Fatal, None);
1263 assert_eq!(get_status_code(), 3, "fatal wins over error → 3");
1264 }
1265
1266 #[test]
1267 fn status_message_clean_is_no_obvious_problems() {
1268 initialize_report();
1269 assert_eq!(get_status_message(), "No obvious problems");
1270 }
1271
1272 #[test]
1273 fn status_message_plural_warnings() {
1274 initialize_report();
1275 note_status(LogStatus::Warning, None);
1276 let m = get_status_message();
1277 assert_eq!(m, "1 warning", "singular form");
1278
1279 note_status(LogStatus::Warning, None);
1280 let m = get_status_message();
1281 assert_eq!(m, "2 warnings", "plural form");
1282 }
1283
1284 #[test]
1285 fn status_message_multiple_categories_joined() {
1286 initialize_report();
1287 note_status(LogStatus::Warning, None);
1288 note_status(LogStatus::Warning, None);
1289 note_status(LogStatus::Error, None);
1290 let m = get_status_message();
1291 assert!(
1292 m.contains("2 warnings") && m.contains("1 error") && m.contains("; "),
1293 "got {m:?}"
1294 );
1295 }
1296
1297 #[test]
1298 fn suppress_log_output_returns_prior_value() {
1299 let prior = set_suppress_log_output(true);
1300 assert!(is_log_output_suppressed());
1301 let prior2 = set_suppress_log_output(false);
1302 assert!(prior2, "round-trip prior value");
1303 assert!(!is_log_output_suppressed());
1304 set_suppress_log_output(prior);
1306 }
1307}