latexml_post/diag.rs
1//! Diagnostic emission for `latexml_post`.
2//!
3//! Five capitalized macros — `Note!`, `Info!`, `Warn!`, `Error!`,
4//! `Fatal!` — mirror the LaTeXML Perl `Note()`/`Info()`/`Warn()`/
5//! `Error()`/`Fatal()` reporting conventions. Local to this crate so
6//! `latexml_post` does not need to import `latexml_core::common::error`
7//! just to emit diagnostics. The macro names deliberately shadow the
8//! identically-named macros from `latexml_core` — same shape (`(category,
9//! object, …)`), no location trace appended. Since the single-vehicle
10//! rework, post's `Error!` DOES participate in the MAX_ERRORS /
11//! consecutive-error caps via `emit_error` — the cap latches the sticky
12//! fatal and CONTINUES (no unwind channel here), where Perl's Post neither
13//! counts nor caps ($STATE-gated) and its Fatal dies. Deliberate
14//! divergence, recorded in OXIDIZED_DESIGN. They DO bump the shared `latexml_core`
15//! `REPORT` status counters via `note_status`, so a post-processing
16//! `Warn!`/`Error!`/`Fatal!` raises the conversion's `status_code` exactly
17//! like a core-phase one — the run's severity is the combined worst of the
18//! core and post phases (`cortex_worker` folds them as `max(core, post)`).
19//! Without this a post-only failure (e.g. an image that fails every
20//! converter) logged its line but left `status_code` at 0.
21//!
22//! For diagnostics raised on a post-processing WORKER THREAD (the graphics
23//! conversion pool), both the log text AND these counter bumps are
24//! `#[thread_local]`, so they are captured per-worker and replayed on the
25//! main thread via `latexml_core::util::logger::capture`/`replay_captured`.
26//!
27//! `Info!`/`Warn!`/`Error!` forward to the SINGLE diagnostic vehicle
28//! (`latexml_core::common::error::emit_*`), which counts, respects output
29//! suppression, participates in the runaway circuit-breakers, and emits with
30//! `target = "<category>:<object>"`, yielding the canonical
31//! `{Severity}:{category}:{object} {message}` line the harness
32//! aggregates from every other stage (engine, package, contrib).
33//!
34//! `Note!` is the lone exception: it bypasses the logger formatter
35//! entirely and writes the bare message to stderr, matching the
36//! prefix-less `Note(…)` output style from Perl LaTeXML.
37//!
38//! `Fatal!` additionally `return`s `Err(PostError::Processing(…))`
39//! so the calling function can early-exit via `?`, mirroring the way
40//! Perl `Fatal()` early-exits via `die`. The crate's `PostError` type
41//! differs from `latexml_core::common::error::Error`, which is why we
42//! cannot reuse the upstream `Fatal!`.
43//!
44//! Convention notes carried over from Perl `LaTeXML::Post::*`:
45//! * `Error('expected', 'source', …)` — Graphics.pm:216 (missing source)
46//! * `Error('imageprocessing', $source, …)` — Graphics.pm:274 (conversion fail)
47//! * `Error('expected', 'stylesheet', …)` — XSLT.pm:36/47 (XSLT setup)
48//! * `Error('missing-file', $stylesheet, …)` — XSLT.pm:42 (missing XSLT)
49//! * `Error('expected', 'Image::Magick', …)` — LaTeXImages.pm:128 (env)
50//! * `Error('I/O', $path, …)` — LaTeXImages.pm:259 (I/O)
51//! * `Error('shell', $cmd, …)` — LaTeXImages.pm:293/328 (subprocess)
52//! * `Fatal('misdefined', (ref $self), …)` — Post.pm:177/434 (no-process / abstract)
53//! * `Fatal('unexpected', $dir, …)` — Post.pm:701 (bad destdir)
54
55#[macro_export]
56macro_rules! Note {
57 ($input:expr_2021) => {{
58 if log::max_level() >= log::LevelFilter::Info {
59 eprintln!("{}", $input);
60 }
61 }};
62}
63
64// The single-message arms delegate to the format arms (`"{}", $msg`) so the
65// `note_status` count + the `log::*!` emission live in exactly ONE place per
66// macro — mirroring `latexml_core`'s own `Error!` self-delegation.
67#[macro_export]
68macro_rules! Info {
69 ($category:expr_2021, $object:expr_2021, $msg:expr_2021) => {
70 $crate::Info!($category, $object, "{}", $msg)
71 };
72 ($category:expr_2021, $object:expr_2021, $fmt:expr_2021, $($arg:tt)+) => {{
73 latexml_core::common::error::emit_info(
74 &format!("{}", $category), &format!("{}", $object), &format!($fmt, $($arg)+))
75 }};
76}
77
78#[macro_export]
79macro_rules! Warn {
80 ($category:expr_2021, $object:expr_2021, $msg:expr_2021) => {
81 $crate::Warn!($category, $object, "{}", $msg)
82 };
83 ($category:expr_2021, $object:expr_2021, $fmt:expr_2021, $($arg:tt)+) => {{
84 latexml_core::common::error::emit_warn(
85 &format!("{}", $category), &format!("{}", $object), &format!($fmt, $($arg)+))
86 }};
87}
88
89#[macro_export]
90macro_rules! Error {
91 ($category:expr_2021, $object:expr_2021, $msg:expr_2021) => {
92 $crate::Error!($category, $object, "{}", $msg)
93 };
94 ($category:expr_2021, $object:expr_2021, $fmt:expr_2021, $($arg:tt)+) => {{
95 latexml_core::common::error::emit_error(
96 &format!("{}", $category), &format!("{}", $object), &format!($fmt, $($arg)+))
97 }};
98}
99
100#[macro_export]
101macro_rules! Fatal {
102 ($category:expr_2021, $object:expr_2021, $msg:expr_2021) => {
103 $crate::Fatal!($category, $object, "{}", $msg)
104 };
105 ($category:expr_2021, $object:expr_2021, $fmt:expr_2021, $($arg:tt)+) => {{
106 let __m = format!($fmt, $($arg)+);
107 latexml_core::common::error::emit_fatal(
108 &format!("{}", $category), &format!("{}", $object), &__m);
109 return Err($crate::processor::PostError::Processing(
110 format!("{}:{}: {}", $category, $object, __m)
111 ));
112 }};
113}