Repository navigation
Migrate the CLI from argh to cw - #2
Merged
Merged
Conversation
18 argv-level cases recorded from the current argh-based console script: the full `--help` surface, the no-query interactive prompt (grub's headline behaviour), a search through each flag that reaches the searcher and each that reaches the Searcher constructor, both spellings argh derives for every parameter (including `--n`, the long form for a one-character name), grub's own exit-1 bad-source path, and five usage errors that must keep exit code 2. Recorded first, on purpose: this is the baseline the migration is measured against, so it has to exist in history before any source changes. `tests/fixtures/corpus/` is four one-line documents, well separated by topic, so a search case has a single unambiguous top hit. `misc/README.md` documents the harness: how to re-record, and the four rules for adding a case. One case was written and then deliberately removed: `--extensions .md` over a .txt-only corpus raises `ValueError: empty vocabulary` out of scikit-learn and the uncaught traceback names 17 absolute paths -- unreplayable on another machine, and it names the dispatcher's own frames, so it could never have survived this migration regardless. Worth knowing that path exists; it is a grub rough edge for another day, not something to pin. Claude-Session: https://claude.ai/code/session_01K6LB3AwUmKDxaFNZ2NqPGr
`from cw import compat as argh` -- nothing else changes, including the `argv=`
keyword `dispatch_command` is called with here. Replayed against the golden
recorded in the previous commit:
18/18 identical (--strict-help, so the --help body is asserted too)
Kept as its own commit because it is the evidence for the claim: the compat shim
is a drop-in, and any behaviour difference in the next commit is attributable to
the move to cw's own API rather than to leaving argh.
Claude-Session: https://claude.ai/code/session_01K6LB3AwUmKDxaFNZ2NqPGr
`main` now returns `cw.dispatch(run, argv)` directly rather than going through the
compat shim, and `argh` is replaced by `cw>=0.1,<0.2` in `dependencies`.
Note the argv handoff: argh took it as the keyword `argv=`, cw takes it as the
second positional. `main` still returns an int and still returns 0 on success, so
`test_cli_main_entry_point`'s `main([...]) == 0` holds, and the console script's
exit code is unchanged.
Replay against the pre-migration goldens, with the --help body asserted:
18/18 identical on CPython 3.12 (recorded from argh)
18/18 identical on CPython 3.10 (recorded from argh)
no --help changes
38 passed
New `tests/test_cli_parity.py` replays the golden in the ordinary test run, so
this cannot rot: a refactor here, or a future cw release, that changes any exit
code, any byte of stdout/stderr, or the usage line fails the suite. Verified it
can fail -- renaming `snippets` to `snippet` turned it red naming exactly that
flag. (A first attempt at a negative test, forcing `convention=cw.MODERN`, did
NOT fail, and that is correct rather than alarming: for a single command whose
options are all keyword defaults, the two conventions produce identical grammar.)
`misc/cli_golden_py310.json` is recorded from *argh* too, on a throwaway 3.10 venv
over a worktree pinned at the pre-migration commit -- a migration proof on both
matrix versions, not a baseline taken after the fact. The only cases that differ
between the two recordings are the two usage errors, where CPython 3.12 stopped
listing `nargs='*'` positionals among "the following arguments are required".
Claude-Session: https://claude.ai/code/session_01K6LB3AwUmKDxaFNZ2NqPGr
argparse takes its `prog` from `basename(sys.argv[0])`, and on Windows the console
script is installed as `grub.EXE`, so every recorded usage line and error prefix
differed on the runner:
- usage: grub [-h] [-m METHOD] ... source [query ...]
+ usage: grub.EXE [-h] [-m METHOD] ... source [query ...]
That is a fact about packaging, not about the command line. cw 0.1.1 scrubs it
(i2mint/cw#34), which is also where the stdin fix this repo's no-query REPL case
depends on landed. Both were found by this migration, so the floor is raised rather
than left at 0.1: with `cw>=0.1` a resolver could pick 0.1.0 and this repo's own
parity test would fail on Windows for a reason nobody could act on.
Claude-Session: https://claude.ai/code/session_01K6LB3AwUmKDxaFNZ2NqPGr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces
argh(LGPL-3.0-or-later) withcw(MIT, zero runtime dependencies). Nothing a user sees changes.The proof
misc/cli_golden_py310.jsonandmisc/cli_golden_py312.jsonwere recorded from the argh CLI before any source edit (their own commit, first in the branch) and replayed after each wave:--helpfrom cw import compat as arghcw.dispatch(run, argv)--helpdiff: none — asserted, not eyeballed:strict_help=Truecompares the full help body, andpython -m cw.testing diff-helpprintsno --help changes. Noexpect_diffentries.38 passed(37 pre-existing + the new parity test).The corpus — 18 cases, in
misc/cli_cases.txt--help. grub is one command, so this is its whole grammar;--method/-m,--chunk/-c,--extensions/-e,--snippets, and--n, the long form argh makes for a one-character parameter name;*querycatches;The argv handoff, which is the one thing that could have gone wrong
argh took it as a keyword (
argh.dispatch_command(run, argv=argv)); cw takes it as the second positional (cw.dispatch(run, argv)).mainstill returns an int and still returns 0 on success, sotest_cli_main_entry_point'smain([...]) == 0holds and the console script's exit code is unchanged.One golden per CPython version
argparseis stdlib and rewrites its own text between versions: 3.12 stopped listingnargs='*'positionals among "the following arguments are required". That is the only difference between the two recordings, it predates this migration, and argh produces it identically — verified against plain argparse on 3.10/3.11/3.12/3.13 with neither argh nor cw in the picture. A single golden asserted across a matrix would fail for something nobody caused, which is the fastest way to teach a team to ignore a red parity test.The 3.10 golden is recorded from argh too — a throwaway 3.10 venv over a worktree pinned at the pre-migration commit — so it is a migration proof on both matrix versions, not a baseline taken after the fact. An unrecorded version fails loudly with instructions rather than skipping.
It cannot rot
tests/test_cli_parity.pyreplays in the ordinary test run, so a futurecwrelease cannot silently change this CLI. Verified it can fail: renamingsnippetstosnippetturned it red naming exactly that flag.Worth recording that the first attempt at a negative test — forcing
convention=cw.MODERN— did not fail, and that is correct rather than alarming: for a single command whose options are all keyword defaults, cw's two conventions produce identical grammar. A negative test that cannot distinguish anything is worth catching before it is trusted.Two cw defects this migration found
Both fixed in i2mint/cw#34, both found here:
cw.testinginherited the recorder's stdin. grub's no-query REPL therefore recorded a timeout at a terminal and an EOF under CI — the same case, two different "facts". That made goldens non-reproducible between a developer's machine and CI, which is the exact failure modeRECORDING_ENVexists to prevent..exewas not scrubbed.argparsetakesprogfrombasename(sys.argv[0]), so the runner reportedusage: grub.EXE. (This one bit the siblingopswardmigration first — 22 of 31 cases red.)Also
dependencies:argh→cw>=0.1,<0.2(the fleet-wide spelling).rg -n 'argh' grub/returns nothing.tests/fixtures/corpus/— four one-line documents, well separated by topic, so a search case has a single unambiguous top hit.misc/README.mddocuments the harness: how to read a red result (you changed the CLI / scikit-learn moved a score / you changed the CLI by accident), how to re-record, and the four rules for adding a case.One case was written and deliberately removed:
--extensions .mdover a.txt-only corpus raisesValueError: empty vocabularyout of scikit-learn, and the uncaught traceback names 17 absolute paths — unreplayable on another machine, and it names the dispatcher's own frames, so it could never have survived this migration regardless. Worth knowing that rough edge exists; it is not this PR's to fix.Checks
Validation 3.10 · Validation 3.12 · Windows Tests — all pass.
Windows deserves a note: it was red before cw 0.1.1 (
usage: grub.EXE ...on every case that prints a usage line) and is green after, with noexpect_diffand nothing skipped. The whole corpus — exit codes, stdout, stderr, usage lines and the full--helpbody — is asserted on all three.