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
// SPDX-License-Identifier: Apache-2.0
//! The palette [`HW-DR-0045`](../../../../docs/decisions/0045-coloring-the-cli-and-where-the-banner-goes.md)
//! rules on, and the pure functions every renderer applies it through.
//!
//! # Why this is a leaf crate and not a module of `headwater-check`
//!
//! These primitives lived in `headwater-check` until #479, beside
//! `headwater_check::fill`, because the four crates that needed them then —
//! `headwater-check` itself, `headwater-query`, `headwater-sweep` and
//! `headwater-cli` — all sit above that crate and reach it without a cycle.
//! That placement held for as long as every renderer that wanted color sat
//! above `headwater-check`, and it stopped holding the moment one did not.
//!
//! `headwater-check` depends on `headwater-census`, `headwater-resolve` and
//! `headwater-lock`. A renderer inside any of those three cannot name
//! `headwater_check::paint` at all: cargo refuses the cycle before a line
//! compiles. Six of the eleven command lines whose interface contract still
//! promises terminal sensing render inside exactly those three crates —
//! `headwater derived` in `headwater-census`, and `taxonomy validate`,
//! `resolve`, `publish`, `vendor` and `migrate` in `headwater-resolve` and
//! `headwater-lock`. Wiring any of them was not expensive, it was impossible,
//! and nothing recorded that until the dependency graph was read against
//! [HW-OBL-0180](../../../../docs/obligations/0180-a-renderer-s-color-mode-is-wired-at-a-call-site-that-no-type-forbids-from-being-wrong.md).
//!
//! So the palette moved to the bottom of the graph, where every renderer of
//! this engine reaches it. This crate depends on nothing, for the reason
//! `headwater-hash` and `headwater-mark` depend on nothing: a rule that two
//! components on opposite sides of the engine both read must have one
//! implementation, and a second copy of a palette is two palettes.
//!
//! # What stayed in `headwater-check`, and why
//!
//! `Severity` is a type of the check layer, so `glyph`, `severity_role` and
//! `severity_word` stayed with it rather than dragging `Severity` down here
//! behind them. `headwater_check::paint` re-exports everything below
//! unchanged, so every caller that wrote `headwater_check::paint::Role` before
//! the move still compiles — the same re-export chain
//! `engine/crates/cli/src/paint.rs` already ran one level up.
//!
//! Deciding *whether* a stream renders color stays in `headwater-cli`:
//! `stdout_color` and its `stderr` twin read the terminal a process is
//! attached to, which is a fact about the binary rather than about a corpus.
//!
//! # `ColorMode` is a parameter, never a read
//!
//! Every function here is pure: given the same [`Role`] and the same
//! [`ColorMode`], it returns the same bytes. Nothing here opens a stream or
//! reads an environment variable, which is what makes every renderer that
//! takes a mode unit-testable with a case table and no real terminal.
//!
//! That purity is also the defect [HW-OBL-0180](../../../../docs/obligations/0180-a-renderer-s-color-mode-is-wired-at-a-call-site-that-no-type-forbids-from-being-wrong.md)
//! records: a renderer threaded with a mode and a call site that hands it
//! [`ColorMode::Plain`] forever passes every headless test there is. What
//! catches that is `tools/engine/color-fixtures.sh`, which attaches a
//! pseudo-terminal and counts escape bytes, and nothing else in this
//! repository can.
//!
//! # `Plain` writes no escape sequence, ever
//!
//! [`HW-DR-0045`](../../../../docs/decisions/0045-coloring-the-cli-and-where-the-banner-goes.md)
//! is explicit that the fallback is "no escape sequence at all", and
//! `NO_COLOR_TEXT` (`engine/crates/cli/src/lib.rs`) promises the same thing to
//! every caller of `--no-color`. So [`paint`] and [`dim`] write `text` back
//! unchanged under [`ColorMode::Plain`].
/// Whether a stream renders the palette below, or its plain-text fallback.
/// The mode `--no-color`, `NO_COLOR` and a stream's own terminal state come
/// to, decided once so every caller reads the same answer the same way.
///
/// `--no-color` or a set `NO_COLOR` forces [`ColorMode::Plain`] regardless of
/// `is_terminal`. There is no third state: a caller who wants color forced
/// into a pipe has no lever here.
/// One semantic role [`HW-DR-0045`](../../../../docs/decisions/0045-coloring-the-cli-and-where-the-banner-goes.md)'s
/// palette names.
///
/// `Verb` carries two rows of the decision's table at once — "a verb name or a
/// `fix:` label" is one role, green and bold, so a second variant for the
/// second noun would be a distinction the palette itself does not draw.
/// `Heading` and structural or already-stated text are the two rows with no
/// hue at all — a heading is bold in the default color and the other is dim in
/// it — so the second reaches no variant here: [`dim`] is a sibling function
/// rather than a seventh role.
/// Every [`Role`], for a case table that would otherwise hand-keep its own copy
/// of the variants.
///
/// [HW-OBL-0172](../../../../docs/obligations/0172-nine-hand-kept-constants-enumerate-an-enum-and-nothing-holds-one-against-the-variants.md)
/// records the shape this is: a hand-typed array whose length is part of its
/// type, so a new variant leaves it short and compiles. It ships discharged
/// under that record's own Discharge clause — `roles_carries_every_variant_once`
/// maps this array through an exhaustive `match` on [`Role`], so a variant
/// added to the enum and left out of here stops the crate compiling. One held
/// list replaces the two unheld ones the tests below used to type by hand, and
/// a renderer's color case table reads this rather than growing a third.
pub const ROLES: = ;
/// `text`, painted for `role` under `mode`.
///
/// `Ansi` writes the standard SGR codes the decision names, which are
/// remapped by whatever theme the caller's terminal already runs — the reason
/// the decision refuses a truecolor hex. `Plain` writes `text` back unchanged:
/// no escape sequence, on the rule the module comment states.
/// Dim weight, the one part of the `Plain` fallback that is not color.