headwater-cli 0.3.0

The headwater binary, and what CI runs. headwater --help is the verb list
Documentation
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
// SPDX-License-Identifier: Apache-2.0
//! How wide the help is, and who lays it out.
//!
//! # `clap` cannot wrap in this workspace, and `term_width` will not make it
//!
//! `StyledStr::wrap` is `pub(crate) fn wrap(&mut self, _hard_width: usize) {}`
//! under `#[cfg(not(feature = "wrap_help"))]`, and this workspace takes `clap`
//! with `derive` alone. So `Command::term_width` sets a number every renderer
//! reads and nothing acts on, and every help string reached a caller on one
//! line however long it was — 1,126 columns at the widest.
//!
//! Taking the `wrap_help` feature is the route the crate offers and it is the
//! wrong one here. It pulls `terminal_size`, which measures the terminal the
//! process is attached to, and a width that depends on the terminal makes a
//! piped run and a run under a terminal write different bytes. Every recorded
//! fixture and every test that reads this help would then be reading the
//! terminal of whoever ran it.
//!
//! So the strings are folded here, before `clap` sees them, at a width this
//! module decides. Nothing in the path reads a terminal: `dimensions()` is
//! `(None, None)` without `wrap_help`, so `clap` asks no question about the
//! stream it is writing to, and neither does this.
//!
//! # The one indent, and how it is known
//!
//! [`painted`] declares `next_line_help` on every command of the tree, which
//! puts an argument's help on the line under the argument rather than in a
//! column whose width is a function of the longest argument at that node. The
//! indent is then `TAB` plus `NEXT_LINE_INDENT` — two spaces and eight — at
//! every node, so [`INDENT`] is a constant rather than a computation, and one
//! folded string is right wherever `clap` decides to print it.
//!
//! # What `clap` appends after a help string, and why folding has to know
//!
//! `HelpTemplate::help` writes the string this module folded and then appends
//! the spec values — `[default: 0]`, `[possible values: …]`, `[aliases: …]` —
//! on the same line after a space. A fold that did not account for them would
//! be right about the text and wrong about the line. [`reserved`] measures what
//! is coming and [`fold_at`] keeps the last word of the text and that suffix on
//! one line together.
//!
//! # Color reads the terminal on purpose, and the masthead is why it must
//!
//! [`HW-DR-0045`](../../../../docs/decisions/0045-coloring-the-cli-and-where-the-banner-goes.md)
//! departs from the rule two sections up, deliberately: an escape sequence
//! leaked into a pipe or a log file actively harms whoever reads it, where a
//! column-wrap choice never did, and every fixture this corpus pins already
//! runs headless. So [`color_of`] is a pure function in exactly the shape
//! [`width_of`] already is — unit-testable with a table and no real terminal —
//! and its one live caller, [`stdout_color`] or [`stderr_color`], reads a
//! stream's own terminal state, which nothing above this line ever does.
//!
//! `--no-color`, `--no-banner` and their environment variables are read the
//! way `--wide` already is: scanned raw, before `clap` builds the tree,
//! because [`banner`] runs inside `first_screen`, which is built before
//! parsing runs.

use clap::{Arg, ArgAction, Command};

/// The width and the fill, which live in `headwater_check::fill` and are named
/// here so that a caller of this module keeps writing `paint::WIDTH`.
///
/// They moved down when the check report gained a layout: the report and the
/// help are laid out by one implementation, and `headwater-check` is the crate
/// every report composer can reach. Nothing is re-implemented here.
pub use headwater_check::fill::{fold, fold_at, WIDEST, WIDTH};

/// The column an argument's help starts at, at every node of the tree.
///
/// `clap`'s `TAB` is two spaces and its `NEXT_LINE_INDENT` is eight, and
/// [`painted`] declares `next_line_help` everywhere so that the pair is the
/// whole indent at every node.
pub const INDENT: usize = 10;

/// The width this process lays the help out at.
///
/// `COLUMNS` is read here and nowhere else in this binary, and only when the
/// command line carries `--wide`. The scan is over the raw arguments because
/// the answer is needed to build the tree that parses them: `clap` renders help
/// inside the parse, out of strings that were folded before it started.
pub fn width() -> usize {
    // The variable is read inside the `true` arm rather than beside the scan,
    // so that a run with no `--wide` on its command line makes no call at all.
    // Two interface contracts say what reaches this binary out of the
    // environment, and the honest sentence is shorter for it.
    match std::env::args_os().any(|one| one == "--wide") {
        false => WIDTH,
        true => width_of(true, std::env::var("COLUMNS").ok().as_deref()),
    }
}

/// The width a `--wide` and a `COLUMNS` reading come to.
///
/// Without `--wide` the answer is [`WIDTH`] and the reading is not consulted, so
/// a run in a 40-column terminal and a run piped into a file write the same
/// bytes. With it the reading is held to `[WIDTH, WIDEST]`: a narrower terminal
/// gets 80 because the text was written to be read at 80, and a wider one gets
/// 120 because a line of prose past that is harder to read rather than easier.
/// A `COLUMNS` that is absent or is not a number is the same answer as no
/// `--wide` at all.
pub fn width_of(wide: bool, columns: Option<&str>) -> usize {
    if !wide {
        return WIDTH;
    }
    match columns.and_then(|text| text.trim().parse::<usize>().ok()) {
        Some(number) => number.clamp(WIDTH, WIDEST),
        None => WIDTH,
    }
}

/// The tree, with every string folded and every node laid out the same way.
///
/// It walks the whole tree rather than the verbs, so a second word and an
/// argument `clap` propagated are folded by the same code as a verb, and an
/// argument added later is folded without anybody remembering to.
pub fn painted(command: Command, width: usize) -> Command {
    let mut one = command.next_line_help(true);
    if let Some(about) = one.get_about().map(ToString::to_string) {
        one = one.about(fold(&about, width));
    }
    one = one.mut_args(|arg| {
        let Some(help) = arg.get_help().map(ToString::to_string) else {
            return arg;
        };
        let room = width.saturating_sub(INDENT);
        let tail = reserved(&arg);
        let folded = fold_at(&help, room, tail);
        arg.help(folded)
    });
    let names: Vec<String> = one
        .get_subcommands()
        .map(|inner| inner.get_name().to_string())
        .collect();
    for name in names {
        one = one.mut_subcommand(name, |inner| painted(inner, width));
    }
    one
}

/// The same tree with every string put back on one line.
///
/// # What this undoes, and for whom
///
/// [`painted`] folds every `about` and every `help` in the tree, and a help
/// screen is what that is for. A completion script is the other reader of the
/// same tree and it wants the opposite: a shell shows a description in a
/// listing it lays out itself, so a fold this engine chose arrives as a break
/// at a width the shell did not pick. `main`'s `completions` runs this over the
/// painted tree before handing it to `clap_complete`.
///
/// Folding and then flattening returns the source string, because [`fold_at`]
/// breaks only at a space and rejoins words with one space. So the flag and
/// subcommand descriptions do not move a byte, and only the positionals change
/// — which is where the whole defect was.
///
/// # Why here rather than a tree built at no width
///
/// `command_at(usize::MAX)` folds nothing and produces the same bytes today,
/// and it is the wrong answer. [`fold_at`]'s own contract is that "a newline in
/// the source is a break the author asked for and survives", so that route
/// leaves a completion script one authored newline in one help string away
/// from the defect returning. This one removes the newline whatever put it
/// there.
///
/// # Why the tree rather than the `zsh` writer
///
/// `clap_complete` flattens a flag and a subcommand description and does not
/// flatten a positional one: its `zsh` generator escapes a positional by hand
/// rather than through the `escape_help` its other two paths use, and that
/// hand-written chain omits the newline. Its `bash`, `fish` and PowerShell
/// generators write no positional description at all, so those three are clean
/// for a reason this repository does not control. Flattening the tree is
/// therefore the fix that survives a dependency bump, and
/// `engine/crates/cli/tests/completions.rs` reads all four scripts for the same
/// reason.
pub fn flattened(command: Command) -> Command {
    let mut one = command;
    if let Some(about) = one.get_about().map(ToString::to_string) {
        one = one.about(one_line(&about));
    }
    one = one.mut_args(|arg| {
        let Some(help) = arg.get_help().map(ToString::to_string) else {
            return arg;
        };
        arg.help(one_line(&help))
    });
    let names: Vec<String> = one
        .get_subcommands()
        .map(|inner| inner.get_name().to_string())
        .collect();
    for name in names {
        one = one.mut_subcommand(name, flattened);
    }
    one
}

/// The words of a string, on one line, separated by one space each.
///
/// It splits on whitespace rather than replacing the newline, so a fold that
/// left a space before its break cannot leave two spaces behind. Every help
/// string of this binary is written as one logical line of single-spaced words,
/// so over a folded string this returns the source exactly.
fn one_line(text: &str) -> String {
    text.split_whitespace().collect::<Vec<&str>>().join(" ")
}

/// The width of what `clap` appends after an argument's help, with its space.
///
/// It is the `spec_vals` of `HelpTemplate`, measured rather than rendered. The
/// `env` feature is not compiled in, so the environment-variable clause of that
/// function cannot occur here and is not measured. Every other clause is, and
/// the whole surface is held to [`WIDTH`] by `tests/width.rs`, so a clause
/// measured wrong is reported as a wide line rather than passing quietly.
fn reserved(arg: &Arg) -> usize {
    let mut parts: Vec<usize> = Vec::new();

    // `clap` prints a default and a possible-value set for an argument that
    // takes a value and for no other. A flag declared `SetTrue` carries
    // `true`/`false` on its value parser and `false` as its default, and
    // neither reaches a caller. `get_num_args` is `None` until
    // `Command::build` and this runs before that, so the action is what
    // answers here. An alias is printed for a flag as well and is not gated.
    let takes_a_value = matches!(arg.get_action(), ArgAction::Set | ArgAction::Append);

    let defaults = arg.get_default_values();
    if takes_a_value && !defaults.is_empty() && !arg.is_hide_default_value_set() {
        let written: Vec<String> = defaults
            .iter()
            .map(|value| value.to_string_lossy().into_owned())
            .collect();
        parts.push("[default: ]".chars().count() + written.join(" ").chars().count());
    }

    let mut aliases: Vec<usize> = Vec::new();
    aliases.extend(
        arg.get_visible_short_aliases()
            .unwrap_or_default()
            .iter()
            .map(|_| 2),
    );
    aliases.extend(
        arg.get_visible_aliases()
            .unwrap_or_default()
            .iter()
            .map(|name| 2 + name.chars().count()),
    );
    if !aliases.is_empty() {
        let plural = if aliases.len() == 1 { 0 } else { 2 };
        let separators = 2 * (aliases.len() - 1);
        parts.push(
            "[alias: ]".chars().count() + plural + separators + aliases.iter().sum::<usize>(),
        );
    }

    if takes_a_value && !arg.is_hide_possible_values_set() {
        // `get_visible_quoted_name` is `clap`'s and is private, so its two
        // rules are read off it here: a hidden value is not printed, and a name
        // holding a space is printed in quotes.
        let possible: Vec<usize> = arg
            .get_possible_values()
            .iter()
            .filter(|value| !value.is_hide_set())
            .map(|value| {
                let name = value.get_name();
                let quotes = usize::from(name.contains(char::is_whitespace)) * 2;
                name.chars().count() + quotes
            })
            .collect();
        if !possible.is_empty() {
            let separators = 2 * (possible.len() - 1);
            parts.push(
                "[possible values: ]".chars().count() + separators + possible.iter().sum::<usize>(),
            );
        }
    }

    match parts.is_empty() {
        true => 0,
        // `spec_vals` joins its parts with one space, and one more space
        // separates the whole of it from the help text before it.
        false => parts.iter().sum::<usize>() + parts.len(),
    }
}

/// A block of text folded to `width` and indented, with its closing newline.
///
/// The whole block is indented, first line included, which is what separates it
/// from [`row`]: a row hangs under a name and this stands under a heading.
pub fn fold_indented(text: &str, width: usize, at: usize) -> String {
    let indent = " ".repeat(at);
    let folded = fold(text, width.saturating_sub(at));
    let body = folded.replace('\n', &format!("\n{indent}"));
    format!("{indent}{body}\n")
}

/// The `clap` color choice a [`ColorMode`] means, stated rather than sensed.
///
/// # Why never `ColorChoice::Auto`
///
/// `Auto` hands the decision to `anstream`, which senses the stream itself and
/// then reads `CLICOLOR_FORCE`. Both halves are wrong here.
/// [`HW-DR-0045`](../../../../docs/decisions/0045-coloring-the-cli-and-where-the-banner-goes.md)
/// rules that no run may force color into a pipe — there is no `--color=always`
/// for the same reason — so an environment variable that turns escape bytes on
/// in a redirected run is a promise this binary broke, and
/// `tests/width.rs`'s `CLICOLOR_FORCE` case is the one that reports it.
/// `anstream` also knows nothing of this binary's own `--no-color`, so `Auto`
/// colors a help page the caller asked to be plain.
///
/// [`stdout_color`] has already read the flag, the environment and the stream,
/// which is every input the decision has. This turns that one answer into
/// `clap`'s vocabulary and adds no input of its own.
#[must_use]
pub fn color_choice(mode: ColorMode) -> clap::ColorChoice {
    match mode {
        ColorMode::Ansi => clap::ColorChoice::Always,
        ColorMode::Plain => clap::ColorChoice::Never,
    }
}

/// The palette `clap` paints a help page with, in the roles HW-DR-0045 names.
///
/// # Why the base is `Styles::plain()` rather than `clap`'s own default
///
/// `Styles::styled()` is bold and underline over nine roles, and HW-DR-0045
/// rules on three: a section heading is default bold, a file path or a flag
/// name is cyan, a verb name is green bold. Starting from plain and setting
/// only what the decision names keeps the help screen on the same palette as
/// `headwater check`, `headwater sweep report` and `headwater explain`, which
/// is what the decision asks for in those words.
///
/// # The one judgment the palette does not settle
///
/// `clap` paints a flag name and a subcommand name with a single `literal`
/// style, where HW-DR-0045 gives them cyan and green bold. Cyan wins here,
/// because a verb page is mostly flags and a subcommand name appears on the
/// root screen and the four `taxonomy`-shaped pages alone. Those verb names are
/// painted green by hand in `first_screen`, where the layout is this crate's
/// own, so the decision is kept on the surface that shows the most of them.
///
/// # Why `error`, `invalid` and `valid` stay plain
///
/// `clap` writes a parse refusal to standard error and renders it with these
/// same styles, while the choice above is read off **standard output**. A run
/// with a terminal on one stream and a pipe on the other would then color a
/// refusal nobody asked to be colored. `err` in `main.rs` already colors every
/// refusal this binary composes, off [`stderr_color`], which is the stream that
/// answers for it.
#[must_use]
pub fn help_styles(mode: ColorMode) -> clap::builder::Styles {
    use clap::builder::styling::{AnsiColor, Style};
    let base = clap::builder::Styles::plain();
    match mode {
        ColorMode::Plain => base,
        // `usage` is set alongside `header` because `{usage-heading}` renders
        // the word `Usage:` through it, and that is a section heading beside
        // `Options:` on the same page rather than a role of its own.
        ColorMode::Ansi => base
            .header(Style::new().bold())
            .usage(Style::new().bold())
            .literal(Style::new().fg_color(Some(AnsiColor::Cyan.into()))),
    }
}

/// One row of a two-column list, folded so that no line passes `width`.
///
/// `at` is the column the second field starts at. A row whose text does not fit
/// is continued under itself rather than under the name, which is what keeps a
/// list of verbs readable when one summary is long.
pub fn row(name: &str, text: &str, at: usize, width: usize) -> String {
    let pad = at.saturating_sub(2 + name.chars().count()).max(1);
    let folded = fold(text, width.saturating_sub(at));
    let indent = " ".repeat(at);
    let body = folded.replace('\n', &format!("\n{indent}"));
    format!("  {name}{}{body}\n", " ".repeat(pad))
}

/// The same row with the name painted, and the layout decided before it is.
///
/// [`row`] runs first over the plain name, so the pad and the fold are computed
/// in characters a reader sees, and the escape sequence is substituted into the
/// finished line afterwards. That is the order `Route::render` takes and the
/// order `a_folded_pointer_breaks_where_an_unpainted_one_does` holds it to:
/// paint before fold and every break moves by the width of an escape sequence
/// nothing prints.
///
/// The substitution is anchored rather than searched. [`row`] opens the line
/// with two spaces and the name, so replacing that prefix once cannot reach a
/// second occurrence of the name inside the summary that follows it.
pub fn painted_row(
    name: &str,
    text: &str,
    at: usize,
    width: usize,
    role: Role,
    mode: ColorMode,
) -> String {
    let plain = row(name, text, at, width);
    match mode {
        ColorMode::Plain => plain,
        ColorMode::Ansi => plain.replacen(
            &format!("  {name}"),
            &format!("  {}", paint(role, name, mode)),
            1,
        ),
    }
}

/// [`ColorMode`], [`Role`], [`color_of`], [`paint`] and [`dim`] moved to
/// `headwater_check::paint`, and are re-exported here unchanged.
///
/// `Finding::render`, `Run::render`, `explain`'s renderer and the two sweep
/// renderers all need them and none of the three crates that declare those
/// functions may depend on `headwater-cli`, so the types live where every
/// caller can reach them — see the module comment of
/// `engine/crates/check/src/paint.rs` for the full reasoning. What stays here
/// is [`stdout_color`] and [`stderr_color`]: the one fact only this crate
/// holds, which stream this process is attached to.
pub use headwater_check::paint::{color_of, dim, glyph, paint, severity_role, severity_word};
pub use headwater_check::paint::{ColorMode, Role};

/// Whether `--no-color` is on the raw command line, scanned the way
/// [`width`] scans for `--wide`.
fn no_color_flag() -> bool {
    std::env::args_os().any(|one| one == "--no-color")
}

/// `NO_COLOR`'s convention: any value at all, including an empty one, turns
/// color off. `tests/width.rs` asserts this over `NO_COLOR=1`, `NO_COLOR=` and
/// `NO_COLOR=0` alike.
fn no_color_env() -> bool {
    std::env::var_os("NO_COLOR").is_some()
}

/// The mode standard output renders in, for this run of the binary.
#[must_use]
pub fn stdout_color() -> ColorMode {
    color_of(
        no_color_flag(),
        no_color_env(),
        std::io::IsTerminal::is_terminal(&std::io::stdout()),
    )
}

/// The mode standard error renders in, for this run of the binary.
#[must_use]
pub fn stderr_color() -> ColorMode {
    color_of(
        no_color_flag(),
        no_color_env(),
        std::io::IsTerminal::is_terminal(&std::io::stderr()),
    )
}

/// Whether `--no-banner` or `HEADWATER_NO_BANNER` suppress the masthead,
/// scanned the way [`no_color_flag`] and `NO_COLOR` are.
#[must_use]
pub fn banner_suppressed() -> bool {
    std::env::args_os().any(|one| one == "--no-banner")
        || std::env::var_os("HEADWATER_NO_BANNER").is_some()
}

/// Whether the raw command line asks for the root help screen: `-h` or
/// `--help` present, and no token that names a verb.
///
/// Read the way [`no_color_flag`] is, before `clap` decides anything, because
/// the masthead is printed by plain I/O ahead of `clap`'s own help writer
/// rather than inside the template it renders — see `first_screen`'s doc
/// comment for why a template cannot carry it. A `--root <path>` whose value
/// happens to equal a verb's name is the one case this reads wrong, and it
/// costs a missing masthead rather than a wrong screen: `clap` still resolves
/// the command line the same way regardless of what this function returns.
#[must_use]
pub fn wants_root_help() -> bool {
    let mut has_help = false;
    let mut has_verb = false;
    for one in std::env::args_os().skip(1) {
        if one == "-h" || one == "--help" {
            has_help = true;
        }
        if one
            .to_str()
            .is_some_and(|text| headwater_verbs::VERBS.iter().any(|verb| verb.name == text))
        {
            has_verb = true;
        }
    }
    has_help && !has_verb
}

/// The masthead `HW-DR-0045` rules on, or today's plain name line where
/// [`banner_suppressed`] holds.
///
/// `version` is `headwater_resolve::release::ENGINE`, the same value
/// `--version` prints, so a caller never reads two numbers for one binary.
/// The blank line closing the string is the one `first_screen` used to open
/// with, folded in here so the root screen keeps the same shape either way.
#[must_use]
pub fn banner(version: &str, mode: ColorMode) -> String {
    let tagline = "a documentation corpus, governed and checked like code";
    if banner_suppressed() {
        return format!("headwater — {tagline}\n\n");
    }
    let name = paint(Role::Verb, &format!("headwater {version}"), mode);
    let rule = dim(&"─".repeat(WIDTH), mode);
    format!("{name} — {}\n{rule}\n\n", dim(tagline, mode))
}

#[cfg(test)]
mod tests {
    use super::{
        banner, color_of, fold, fold_at, fold_indented, row, width_of, ColorMode, INDENT, WIDEST,
        WIDTH,
    };

    #[test]
    fn color_is_plain_off_a_terminal_and_ansi_on_one_unless_overridden() {
        assert_eq!(color_of(false, false, false), ColorMode::Plain);
        assert_eq!(color_of(false, false, true), ColorMode::Ansi);
        assert_eq!(
            color_of(true, false, true),
            ColorMode::Plain,
            "--no-color wins"
        );
        assert_eq!(
            color_of(false, true, true),
            ColorMode::Plain,
            "NO_COLOR wins"
        );
        assert_eq!(color_of(true, true, false), ColorMode::Plain);
    }

    #[test]
    fn the_masthead_names_the_version_once_above_a_rule_of_the_help_width() {
        let text = banner("9.9.9", ColorMode::Plain);
        let mut lines = text.lines();
        assert_eq!(
            lines.next(),
            Some("headwater 9.9.9 — a documentation corpus, governed and checked like code")
        );
        let rule = lines.next().expect("a rule line follows");
        assert_eq!(rule.chars().count(), WIDTH);
        assert!(rule.chars().all(|c| c == '─'));
    }

    #[test]
    fn nothing_reads_columns_until_a_caller_asks_for_it() {
        assert_eq!(width_of(false, Some("500")), WIDTH);
        assert_eq!(width_of(false, Some("40")), WIDTH);
        assert_eq!(width_of(false, None), WIDTH);
    }

    #[test]
    fn a_width_a_caller_asks_for_is_held_to_the_band() {
        assert_eq!(width_of(true, Some("40")), WIDTH);
        assert_eq!(width_of(true, Some("100")), 100);
        assert_eq!(width_of(true, Some("500")), WIDEST);
        assert_eq!(width_of(true, Some("80")), WIDTH);
        assert_eq!(width_of(true, Some("120")), WIDEST);
    }

    /// A reading that is not a number is the width every other run takes.
    #[test]
    fn a_columns_that_is_not_a_number_is_the_default_width() {
        assert_eq!(width_of(true, None), WIDTH);
        assert_eq!(width_of(true, Some("")), WIDTH);
        assert_eq!(width_of(true, Some("wide")), WIDTH);
        assert_eq!(width_of(true, Some("-1")), WIDTH);
    }

    /// The fill this module re-exports is the one in `headwater-check`.
    ///
    /// Its own cases live beside it, in `crates/check/src/fill.rs`. This one
    /// holds the re-export: a second implementation appearing here would pass
    /// every case there and lay the help out differently.
    #[test]
    fn the_fold_this_module_names_is_the_one_the_check_layer_owns() {
        let text = "see docs/spec/06-engine-architecture.md#the-command-line for it";
        assert_eq!(fold(text, 20), headwater_check::fill::fold(text, 20));
        assert_eq!(
            fold_at(text, 20, 6),
            headwater_check::fill::fold_at(text, 20, 6)
        );
        assert_eq!(WIDTH, headwater_check::fill::WIDTH);
        assert_eq!(WIDEST, headwater_check::fill::WIDEST);
    }

    #[test]
    fn a_row_that_does_not_fit_is_continued_under_itself() {
        let written = row(
            "check",
            "run the pipeline over the corpus, against the lock",
            15,
            40,
        );
        let lines: Vec<&str> = written.trim_end().lines().collect();
        assert_eq!(lines[0], "  check        run the pipeline over the");
        for line in &lines[1..] {
            assert!(line.starts_with(&" ".repeat(15)), "{line:?}");
        }
        for line in &lines {
            assert!(line.chars().count() <= 40, "{line:?}");
        }
    }

    #[test]
    fn an_indented_block_holds_every_line_inside_the_width() {
        let written = fold_indented("run the checks, and fail on an error", 20, 6);
        assert!(written.ends_with('\n'));
        for line in written.lines() {
            assert!(line.starts_with("      "), "{line:?}");
            assert!(line.chars().count() <= 20, "{line:?}");
        }
    }

    /// The indent is `clap`'s two-space `TAB` and its eight-space next-line
    /// indent, and the whole point of declaring `next_line_help` is that it is
    /// the same number at every node.
    #[test]
    fn the_indent_is_the_pair_clap_writes() {
        assert_eq!(INDENT, "  ".len() + "        ".len());
    }
}