1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
//! Line rendering: the verb gutter, body markers, and the deferred section
//! header that only prints once its section produces output.
use ;
use Mutex;
use Colorize;
/// A section header that has been opened ([`StageLogger::group`](crate::log::StageLogger::group)) but not yet
/// printed. The header line is deferred until the section actually emits a
/// body line, so a stage that does nothing prints nothing at all (matching
/// GoReleaser, which only prints a section header once the section has output).
pub
/// Stack of section headers awaiting their first body line. Pushed by
/// [`StageLogger::group`](crate::log::StageLogger::group), drained by [`flush_pending`] when a real line is
/// about to print, and popped (LIFO) by [`SectionGuard::drop`](crate::log::SectionGuard::drop).
///
/// A `Mutex` (not a thread-local) because sections are opened on the main
/// thread like [`SECTION_DEPTH`](super::depth::SECTION_DEPTH), but body lines may flush from a stage's
/// worker threads (e.g. `build` spawning per-target threads). The lock
/// serializes the flush state transition: each header's `flushed` flag flips
/// under the lock, so a header prints exactly once — never lost, never
/// duplicated. Header-before-body ordering holds because every emit method
/// calls [`flush_pending`] then writes its body line with no early return
/// between. It does not serialize body-line-vs-body-line ordering across
/// workers — two `build` threads may print their body lines in either order
/// under a just-flushed header, matching build's existing unordered parallel
/// output.
pub static PENDING: = new;
/// Print every still-unflushed pending section header, in ancestor-first
/// (bottom-to-top) order, then mark each flushed.
///
/// Called immediately before any method actually writes a visible body line,
/// so the deferred headers appear above their first line in correct nesting
/// order. A header renders at its own stored [`PendingHeader::depth`] — the
/// 2-space-per-level indent, the right-aligned bold-green verb in the
/// `VERB_COLUMN` gutter, then (if non-empty) one space and the message.
///
/// No-op when nothing is pending (the common case once a section has already
/// emitted its first line), so the per-body-line cost is one uncontended lock.
pub
/// Render a section/stage header line at `depth`: the 2-space-per-level
/// nesting indent, a bold-green verb right-aligned in the `VERB_COLUMN`
/// gutter, then (only for a non-empty `msg`) a single space and the message.
///
/// The single source of truth shared by both header-emitting paths — the
/// deferred section header in [`flush_pending`] and the direct
/// [`StageLogger::step`](crate::log::StageLogger::step) — so interleaved headers and steps land in
/// byte-identical columns for the same depth. A single-word phrase (empty
/// `msg`) renders the bare gutter verb with no trailing space, so headers
/// never carry stray whitespace.
pub
/// Render one gutter line: `label` right-aligned in the `VERB_COLUMN` gutter
/// (painted by `paint`) after `prefix`, then a space and `msg` — or, when `msg`
/// is empty, the bare painted label with no trailing space. The single column
/// system shared by section headers and the Warning/Error/Note status labels,
/// so a status label's message lands in the same column as a header's.
pub
/// Width of the right-aligned verb column in [`StageLogger::step`](crate::log::StageLogger::step),
/// matching Cargo's ` Compiling foo` look (3 leading spaces + 9-char
/// verb = a 12-column gutter before the message).
pub const VERB_COLUMN: usize = 12;
/// Indent (after any section nesting) of a body line — a [`StageLogger::detail`](crate::log::StageLogger::detail)
/// / [`success`] / [`failure`] / [`kv`] row, or a status label. Three spaces
/// place the marker column one stop in from the section header's text, so body
/// lines read as subordinate to the header above them.
///
/// [`success`]: crate::log::StageLogger::success
/// [`failure`]: crate::log::StageLogger::failure
/// [`kv`]: crate::log::StageLogger::kv
pub const BODY_INDENT: &str = " ";
/// Marker for an info / detail body line (`•`). Rendered cyan.
pub const MARKER_DETAIL: &str = "•";
/// Marker for a success body line (`✓`). Rendered green.
pub const MARKER_SUCCESS: &str = "✓";
/// Marker for a failure body line (`✗`). Rendered red.
pub const MARKER_FAILURE: &str = "✗";
/// Map a pipeline stage name to its full Cargo-style header phrase
/// (`"Building binaries"`, `"Signing artifacts"`, `"Publishing"`). Drives
/// [`StageLogger::group`](crate::log::StageLogger::group)'s deferred header: the leading verb is right-aligned
/// into the `VERB_COLUMN` gutter (bold-green, matching `cargo`'s
/// ` Compiling foo` look), and the remaining words form the message that
/// follows. A single-word phrase (`"Publishing"`) renders just the gutter
/// verb with no trailing message.
///
/// The phrase is a *readable description* of the work, not an echo of the
/// stage name — `group("build")` reads ` Building binaries`, not
/// ` Building build`. This keeps the continuous log scannable: a reader
/// sees what each section does, not the internal stage identifier.
///
/// Falls back to `"Running <stage>"` for any stage without a bespoke
/// phrase, so a newly-added stage still renders in the system vocabulary
/// (` Running myfancystage`) without a code change here.
/// Render the themed `Warning` line for `msg`: the label right-aligned in the
/// gutter (bold-yellow, no colon) at the enclosing section's header indent
/// (`label_indent`), so it reads as a peer of the section verbs and its
/// message aligns with theirs. The single source of truth for the warning
/// palette and label, shared by [`StageLogger::warn`](crate::log::StageLogger::warn) and the CLI's tracing
/// formatter so a library-side `warn!` looks identical to a logger warn (one
/// output authority).
/// Render the themed `Error` line for `msg` — gutter-aligned bold-red label,
/// companion to [`render_warning`]; shared so the error palette/label lives in
/// exactly one place.
/// Render the themed `Note` line for `msg`: gutter-aligned bold-green label.
/// The third status label — informational lines that are neither warnings nor
/// errors (host-target selection, auto-snapshot activation). The bold-green
/// deliberately matches the section-verb palette (not yellow/red): a note is a
/// neutral peer of the section headers, not an alert. Shared so the `Note`
/// palette/label lives in exactly one place rather than being open-coded per
/// call site.
/// Current indentation prefix (2 spaces per open section). Empty at the
/// top level. Applied identically everywhere — including under GitHub
/// Actions, where the indentation (not a collapsible `::group::` block) is
/// what conveys section nesting, matching the continuous single-stream log
/// GoReleaser emits.
///
/// This is the body-line depth (`•` detail / `success` / `failure` / `kv`
/// rows). Status labels do NOT use it — they sit one level shallower at the
/// enclosing header's depth via `label_indent`.
/// Indentation prefix for a status label (`Warning` / `Error` / `Note`).
///
/// A label is a body-level event, but unlike a `•` detail line it renders
/// through the right-aligned [`render_gutter`] system (like a section header),
/// so to land its label in the verb column and its message in the header's
/// message column it must sit at the ENCLOSING header's depth — one level
/// shallower than [`indent`] (which tracks the deeper body depth). Using the
/// body indent here would push the gutter-aligned label two columns past both
/// the sibling headers and the body bullets, leaving it floating on its own.
///
/// Floored at the inherited `base_depth` so a label emitted with no open
/// section never dedents below the ambient (e.g. GitHub Actions) indent.
pub
/// Strip ANSI CSI escape sequences (SGR color codes, cursor moves) from `s`.
///
/// Captured subprocess output (cargo, gpg, …) carries terminal color codes
/// when color is forced for the live CI log. Those bytes must never leak into
/// a propagated error message that reaches a non-terminal sink — a failure
/// email, the `on_error` hook's `$ANODIZER_ERROR`, a JSON run summary — where
/// they render as garbage around every styled token.
pub