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 // STDERR-only post note, gated on the decoupled console verbosity (Perl
59 // `$VERBOSITY >= 0`), NOT `max_level` — under `--quiet` the log-file floor
60 // holds `max_level` at `Info`, but the console note must stay silent (#763).
61 if latexml_core::util::logger::stderr_shows_info() {
62 eprintln!("{}", $input);
63 }
64 }};
65}
66
67// The single-message arms delegate to the format arms (`"{}", $msg`) so the
68// `note_status` count + the `log::*!` emission live in exactly ONE place per
69// macro — mirroring `latexml_core`'s own `Error!` self-delegation.
70#[macro_export]
71macro_rules! Info {
72 ($category:expr_2021, $object:expr_2021, $msg:expr_2021) => {
73 $crate::Info!($category, $object, "{}", $msg)
74 };
75 ($category:expr_2021, $object:expr_2021, $fmt:expr_2021, $($arg:tt)+) => {{
76 latexml_core::common::error::emit_info(
77 &format!("{}", $category), &format!("{}", $object), &format!($fmt, $($arg)+))
78 }};
79}
80
81#[macro_export]
82macro_rules! Warn {
83 ($category:expr_2021, $object:expr_2021, $msg:expr_2021) => {
84 $crate::Warn!($category, $object, "{}", $msg)
85 };
86 ($category:expr_2021, $object:expr_2021, $fmt:expr_2021, $($arg:tt)+) => {{
87 latexml_core::common::error::emit_warn(
88 &format!("{}", $category), &format!("{}", $object), &format!($fmt, $($arg)+))
89 }};
90}
91
92#[macro_export]
93macro_rules! Error {
94 ($category:expr_2021, $object:expr_2021, $msg:expr_2021) => {
95 $crate::Error!($category, $object, "{}", $msg)
96 };
97 ($category:expr_2021, $object:expr_2021, $fmt:expr_2021, $($arg:tt)+) => {{
98 latexml_core::common::error::emit_error(
99 &format!("{}", $category), &format!("{}", $object), &format!($fmt, $($arg)+))
100 }};
101}
102
103#[macro_export]
104macro_rules! Fatal {
105 ($category:expr_2021, $object:expr_2021, $msg:expr_2021) => {
106 $crate::Fatal!($category, $object, "{}", $msg)
107 };
108 ($category:expr_2021, $object:expr_2021, $fmt:expr_2021, $($arg:tt)+) => {{
109 let __m = format!($fmt, $($arg)+);
110 latexml_core::common::error::emit_fatal(
111 &format!("{}", $category), &format!("{}", $object), &__m);
112 return Err($crate::processor::PostError::Processing(
113 format!("{}:{}: {}", $category, $object, __m)
114 ));
115 }};
116}