diff --git a/shape-doctor.py b/shape-doctor.py index 9518ef83..2358acef 100755 --- a/shape-doctor.py +++ b/shape-doctor.py @@ -222,6 +222,19 @@ reader improvises around -- which is the house rule the rest of this standard is built on. +AND THE NEXT IS THE COMMAND AND NOTHING ELSE (#277). The sentence saying why +it is the fix -- "read its output in full", "then apply --at ...", "what +`make bootstrap` runs" -- used to ride on the same line as ` # `, +because a terminal shows both at once. `sh`, bash and `zsh -c` read that `#` +as a comment. An INTERACTIVE zsh without `interactivecomments` -- zsh's +default, and macOS's login shell, the one a person pastes into -- reads it as +a word: the pasted line handed the comment to the command as arguments, ran +whatever followed a `;` as a second command, and ran whatever sat in +backticks, `make bootstrap` among them. So `next` and the `NEXT` line are the +bare command, pasted whole, and the sentence is the row's `why`: a `WHY` line +under `NEXT` in the table, `"why"` on every row of `--json` (null where there +is none), and a line after `Remediation:` when the report cannot answer. + AND ONLY A `FINDING` MOVES THE VERDICT. A row is `ok`, `note`, `FINDING`, `n/a` or -- the Triad row alone -- `advisory`, and `FINDING` means exactly "something ELSE asserts this" -- a @@ -707,10 +720,22 @@ def quote_arg(value, platform: str | None = None, *, class Row: - """One line of the report: what was asked, the answer, and the exit.""" + """One line of the report: what was asked, the answer, and the exit. + + `next_command` IS A COMMAND, WHOLE, AND `why` IS WHAT IS SAID ABOUT IT + (#277). The two were one string, ` # `, and the `#` is a + comment only to a shell that reads it as one: pasted into an interactive + zsh it was a word, the comment's text became arguments, a `;` in it ran a + second command and a pair of backticks ran a third. So a builder hands + the sentence over as `why`, a keyword of its own, and `next_command` + holds only what a reader pastes. `why` is never folded back into the + command line: `report` prints it on a `WHY` line of its own and `as_dict` + as its own key. + """ def __init__(self, check_id: str, label: str, status: str, reason: str, - next_command: str | None = None, detail: dict | None = None): + next_command: str | None = None, detail: dict | None = None, + *, why: str | None = None): self.id = check_id self.label = label self.status = status @@ -721,14 +746,18 @@ def __init__(self, check_id: str, label: str, status: str, reason: str, # ordinary job, flattening this repository's own typography, on the # literal words a caller wrote around those values. self.next_command = ascii_text(next_command) if next_command else None + #: The sentence that explains `next_command`, kept off its line. + self.why = ascii_text(why) if why else None #: Structured facts for `--json`, so a caller reads numbers rather #: than parsing the sentence a human is meant to read. self.detail = detail or {} def as_dict(self) -> dict: + # `why` ON EVERY ROW, null where there is none, so a caller reads a + # key rather than testing for its absence -- as `placement_plan` is. return {"id": self.id, "label": self.label, "status": self.status, "reason": self.reason, "next": self.next_command, - "detail": self.detail} + "why": self.why, "detail": self.detail} class Check: @@ -1192,7 +1221,7 @@ def run_validator(ctx: Context, script: Path, args: list[str], DOCTOR'S OWN IS NEVER PART OF IT (#306). `pythonpath` is the directory to put on it, and `validator_row` passes `validator_scripts`'s answer: given one, the child's `PYTHONPATH` is that directory ALONE, the way the printed - `PYTHONPATH= python3 ...` sets it; given none, the child has NO + `env PYTHONPATH= python3 ...` sets it; given none, the child has NO `PYTHONPATH`, and the variable is taken OUT of the copy of the doctor's environment this builds, not left in it. ONE PLACE DECIDES, and it is the `env` block below: it does not ask whether the script is a root's own copy @@ -1350,14 +1379,22 @@ def validator_command(shape: Path, used: Path, own_copy: bool, args: list, SO THE TEMPLATE'S LINE SETS IT, in the reader's dialect, which is the same two this file already speaks (`quote_arg`, `copy_command`, - `scaffold_command`): `PYTHONPATH=/scripts python3