tools: command records - NOTES always bullets, examples held by tests (#142)
Files changed: - CHANGES.md - VERSION - tools/chemenu/cli_contract.py - tools/chemenu/tests/test_cli.py - tools/chemenu/tests/test_cli_contract.py - tools/chemenu/tests/test_docs_verify.py
This commit is contained in:
1 parent
5bfbb49d74
commit
5aae7fed1b
6 files changed
+54
-23
No files matched your search
+10
-1
@@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse.
|
||||
|
||||
---
|
||||
|
||||
## 7.1.0-beta.20 - 2026-09-26 - Command records, Instance health group: one bullet per check, examples
|
||||
## 7.1.0-beta.21 - 2026-09-26 - Command records: NOTES is always a tuple of bullets; every record's examples are tested
|
||||
|
||||
**Author:** Torben Nehmer
|
||||
|
||||
@@ -89,6 +89,7 @@ concern - readable here, never shipped as something to parse.
|
||||
- Command records, Content migrations group: one line per cause, examples, prohibitions
|
||||
- Command records, Private instances group: one line per cause, examples, prohibitions
|
||||
- Command records, Instance health group: one bullet per check, examples
|
||||
- Command records: NOTES is always a tuple of bullets; every record's examples are tested
|
||||
<!-- /wikitool:bumps -->
|
||||
|
||||
### CalDAV task-tracker provider (Nextcloud Tasks, iOS Reminders); review reports unknown-value findings instead of skipping them
|
||||
@@ -333,6 +334,14 @@ dropped rather than moved: the record justified the conventions `FAIL` by `xref`
|
||||
out of the section headings, while the check's own docstring calls those headings cosmetic and
|
||||
grounds the `FAIL` in the file binding every page.
|
||||
|
||||
### Command records: NOTES is always a tuple of bullets; every record's examples are tested
|
||||
|
||||
With every group rewritten, `CommandRecord.notes` no longer accepts the single-paragraph string
|
||||
it carried over from the first pass; a record that passes one is refused at import. Two tests
|
||||
over the real registry hold what the rewrite established: every command has at least one
|
||||
example, and every command with a gate shows how its clearance is passed back in (`--confirm`,
|
||||
`--confirm-rebase` or `--resume`).
|
||||
|
||||
---
|
||||
|
||||
## 7.0.0 - 2026-09-22 - Task-Tracker-Anbindung: Vorhaben als Seitenart, Verpflichtungsschicht, Weekly Review als Read-Time-Join
|
||||
|
||||
@@ -11,11 +11,12 @@ the `###`-level grouping and rendering order, unchanged from what
|
||||
|
||||
Phase 1 (Gitea #121) filled every record mechanically and word-for-word from
|
||||
the two tables `tools/CONTRACT.md` used to carry. Phase 2 (Gitea #142)
|
||||
rewrites them group by group: NOTES into present-tense bullets, one
|
||||
`Failure` per cause, EXAMPLES/NEVER/SEE ALSO filled, and "why" moved out to
|
||||
a code comment where the behaviour is implemented. A sentence several
|
||||
records carry verbatim lives here once (`token_gate_reaction`, ...), so the
|
||||
output repeats it and the source does not.
|
||||
rewrote them: NOTES as present-tense bullets, one `Failure` per cause,
|
||||
EXAMPLES/NEVER/SEE ALSO filled, and "why" moved out to a code comment where
|
||||
the behaviour is implemented. How a record is written is the `CommandRecord`
|
||||
docstring's job. A sentence several records carry verbatim lives here once
|
||||
(`token_gate_reaction`, ...), so the output repeats it and the source does
|
||||
not.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -120,22 +121,22 @@ class CommandRecord:
|
||||
- `see_also`: related commands and the instruction that uses this one.
|
||||
It is the only place another command may be named for context - a
|
||||
behaviour this command shares with another is stated here in full,
|
||||
not as "same as `X`".
|
||||
|
||||
A plain `str` for `notes` is still accepted and renders as one paragraph:
|
||||
the phase-1 form, kept only until every group has been rewritten into
|
||||
bullets (Gitea #142)."""
|
||||
not as "same as `X`"."""
|
||||
path: str
|
||||
summary: str
|
||||
synopsis: tuple[Variant, ...]
|
||||
properties: Properties
|
||||
notes: tuple[str, ...] | str
|
||||
notes: tuple[str, ...]
|
||||
failures: tuple[Failure, ...]
|
||||
examples: tuple[str, ...] = ()
|
||||
never: tuple[str, ...] = ()
|
||||
see_also: tuple[str, ...] = ()
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.notes, str):
|
||||
raise ValueError(
|
||||
f"cli_contract: {self.path!r} notes must be a tuple of bullets, not one string"
|
||||
)
|
||||
if not self.properties.gates and any(f.code == 42 for f in self.failures):
|
||||
raise ValueError(
|
||||
f"cli_contract: {self.path!r} lists an exit-42 cause but declares no gate"
|
||||
@@ -294,11 +295,8 @@ def _labelled(failure: Failure, text: str) -> str:
|
||||
return f"{failure.label}: {text}" if failure.label else text
|
||||
|
||||
|
||||
def _notes_lines(notes: tuple[str, ...] | str) -> list[str]:
|
||||
"""NOTES as rendered lines: one `- ` bullet per entry, or the phase-1
|
||||
paragraph unchanged."""
|
||||
if isinstance(notes, str):
|
||||
return [notes]
|
||||
def _notes_lines(notes: tuple[str, ...]) -> list[str]:
|
||||
"""NOTES as rendered lines: one `- ` bullet per entry."""
|
||||
return [f"- {note}" for note in notes]
|
||||
|
||||
|
||||
|
||||
@@ -235,3 +235,22 @@ def test_a_usage_error_is_unframed_and_names_wikitool(monkeypatch):
|
||||
text = out + err
|
||||
assert "Usage: wikitool publish" in text
|
||||
assert not FRAME_CHARS & set(text)
|
||||
|
||||
|
||||
def test_every_record_shows_at_least_one_example():
|
||||
"""Every real command record carries a copyable example (Gitea #142)."""
|
||||
missing = [path for path, rec in cli_contract.all_records().items() if not rec.examples]
|
||||
assert missing == []
|
||||
|
||||
|
||||
def test_every_gated_record_shows_its_re_run_after_exit_42():
|
||||
"""A command with a gate shows how the clearance is passed back in -
|
||||
`--confirm`, `--confirm-rebase`, or `new project`'s `--resume`."""
|
||||
clearing_flags = ("--confirm", "--confirm-rebase", "--resume")
|
||||
missing = [
|
||||
path
|
||||
for path, rec in cli_contract.all_records().items()
|
||||
if rec.properties.gates
|
||||
and not any(flag in example for example in rec.examples for flag in clearing_flags)
|
||||
]
|
||||
assert missing == []
|
||||
@@ -23,7 +23,7 @@ def _fixture_record(**overrides) -> cc.CommandRecord:
|
||||
atomic="Yes - single file write",
|
||||
budget=cc.Budget.COUNTED,
|
||||
),
|
||||
notes="Frobnicates the named widget in place.",
|
||||
notes=("Frobnicates the named widget in place.",),
|
||||
failures=(cc.Failure(label="", cause="Widget not found", reaction="Fix the name and retry once"),),
|
||||
)
|
||||
defaults.update(overrides)
|
||||
@@ -218,7 +218,7 @@ def test_render_markdown_section_has_no_options_heading():
|
||||
section = cc.render_markdown_section(rec)
|
||||
assert "OPTIONS" not in section
|
||||
assert "#### `frobnicate`" in section
|
||||
assert rec.notes in section
|
||||
assert f"- {rec.notes[0]}" in section
|
||||
|
||||
|
||||
def test_notes_tuple_renders_one_bullet_per_entry():
|
||||
@@ -297,3 +297,8 @@ def test_exit_42_cause_without_a_gate_is_refused():
|
||||
def test_failure_code_outside_0_1_42_is_refused():
|
||||
with pytest.raises(ValueError):
|
||||
cc.Failure(cause="Crash", reaction="Report", code=2)
|
||||
|
||||
|
||||
def test_notes_as_one_string_is_refused():
|
||||
with pytest.raises(ValueError, match="tuple of bullets"):
|
||||
_fixture_record(notes="One paragraph, the phase-1 form.")
|
||||
@@ -52,7 +52,7 @@ def _fixture_record(path: str) -> cli_contract.CommandRecord:
|
||||
atomic="Yes",
|
||||
budget=cli_contract.Budget.COUNTED,
|
||||
),
|
||||
notes="Does a thing, mechanically.",
|
||||
notes=("Does a thing, mechanically.",),
|
||||
failures=(cli_contract.Failure(label="", cause="It broke", reaction="Fix and retry"),),
|
||||
)
|
||||
|
||||
@@ -150,7 +150,7 @@ def test_a_phantom_synopsis_flag_is_reported(monkeypatch):
|
||||
summary="Does a thing.",
|
||||
synopsis=(cli_contract.Variant(usage="frobnicate --flag <v> --invented <v>"),),
|
||||
properties=_fixture_record("frobnicate").properties,
|
||||
notes="Does a thing.",
|
||||
notes=("Does a thing.",),
|
||||
failures=(),
|
||||
)
|
||||
param = click.Option(["--flag"])
|
||||
@@ -188,7 +188,7 @@ def test_a_boolean_flag_pair_is_satisfied_by_either_spelling(monkeypatch):
|
||||
summary="Does a thing.",
|
||||
synopsis=(cli_contract.Variant(usage="frobnicate [--no-push]"),),
|
||||
properties=_fixture_record("frobnicate").properties,
|
||||
notes="Does a thing.",
|
||||
notes=("Does a thing.",),
|
||||
failures=(),
|
||||
)
|
||||
param = click.Option(["--push/--no-push"], default=True)
|
||||
|
||||
Reference in new issue
Block a user