Reserve -h/--help and list the help row on every page - #22
Conversation
Help is hardwired: the parser honours -h and --help before any spec lookup, on every level. Two things contradicted that. A user option named --help stored its value under the "help" key that dispatch tests, so every invocation printed the help page and no handler ever ran, and the page listed two --help rows. And the built-in help row lived inside the Options section, which only rendered when the spec declared options of its own, so a page for an option-less subcommand said nothing about the -h it accepted. flag and option now raise when given -h or --help, so the collision is caught when the spec is built rather than discovered at the first invocation. This also removes the divergence between a clustered -xh and a bare -h that PR #21's review noted, since -h can no longer be declared. Every help page renders the Options section with the help row and says [options] in its usage line; pages that already had options are byte-for-byte unchanged. Closes #9 Closes #10 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Baiju Muthukadan <baiju.m.mail@gmail.com>
baijum
left a comment
There was a problem hiding this comment.
Reviewed locally on the PR head (9190d44), Kaappi v0.27.1.
Verified
- Suite: 170 passed, 0 failed.
- Both README help samples (quick start and
build --help) are byte-identical to the actual output. -hon option-less specs still yields help at both levels (napp -h,myapp init -h), and a cluster like-vhexpands to help too, so the always-on Options row and[options]in the usage line are honest.- The CHANGELOG's "every invocation printed help" is accurate for the canonical repro from #10: an
optionwith a truthy default stored under"help"makesrun-cli's truthiness check fire on every parse. (With aflag, default#f, the old collision only showed via shadowing and the duplicate help row.)
Both #9 and #10 look resolved. Two non-blocking notes:
- The crossed-slot gap in
check-not-help(inline comment) —(flag "-H" "-h" ...)and(flag "--help" "--helper" ...)still build and print the reserved name on the help page. - Nit: with
-hno longer declarable, theodisjunct inexpand-cluster's(or o (string=? short "-h"))(lib/kaappi/cli.sld:355) is now dead —find-opt-shortcan never match-hagainst specs that all passedcheck-not-help. Harmless to keep as belt-and-braces, but it simplifies to(string=? short "-h")if you'd rather.
| ;; -h and --help are handled by the parser and listed on every help | ||
| ;; page, so a spec cannot claim either name. | ||
| (define (check-not-help who short long) | ||
| (when (or (equal? short "-h") (equal? long "--help")) |
There was a problem hiding this comment.
This checks each slot against only its own spelling, so the crossed forms still build. (flag "-H" "-h" "H") is accepted and renders:
Options:
-H, -h H
-h, --help Show this help
The page now claims -h for the flag in the long column, while typing -h still gives the built-in help — the same divergence this PR removes, via the other slot. (flag "--help" "--helper" "H") is symmetric (prints a --help, --helper row). The -h-as-long case is worse: opt-long-name computes (substring "-h" 2 1), past the end of the string — R7RS leaves that an error, and Kaappi clamps to "", so the value is stored under an empty key that parsed-ref can never reach.
Checking both slots against both names closes it:
(when (or (member short '("-h" "--help"))
(member long '("-h" "--help")))Non-blocking — these are pathological specs — but it's two lines and makes the "reserved" promise literal.
There was a problem hiding this comment.
Done in a64c1e1: both slots are checked against both names with member, so (flag "-H" "-h" ...) and (flag "--help" "--helper" ...) now raise with the same message. Good catch on the empty-key consequence of -h in the long slot.
| "option: -h and --help are reserved for the built-in help" | ||
| (spec-error (lambda () (option "-h" "--host" "Host" "localhost")))) | ||
|
|
||
| (check "a name that merely starts with help is fine" #f |
There was a problem hiding this comment.
If the reservation is widened to both slots (see the comment on check-not-help), the crossed cases belong next to this one: (flag "-H" "-h" ...) and (flag "--help" "--helper" ...) both rejected, --helper-style names still accepted.
There was a problem hiding this comment.
Added in a64c1e1 next to the existing reserved-name checks: both crossed forms rejected with the exact message, and --helper still accepted.
check-not-help tested each slot only against its own spelling, so the crossed forms (flag "-H" "-h" ...) and (flag "--help" "--helper" ...) still built and put the reserved name on the help page next to the built-in row. The -h-as-long case was worse: opt-long-name substrings past the end of "-h", so the value landed under an empty key that parsed-ref could never reach. Both slots are now checked against both names, which makes the reservation literal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Baiju Muthukadan <baiju.m.mail@gmail.com>
|
Review addressed in a64c1e1:
Suite: 172 passed, 0 failed. |
Closes #9 and #10, both about the hardwired
-h/--help.#10:
--helpas a user option disabled dispatchflagandoptionnow raise when given-hor--help:Reserving the names, rather than dispatching on a private key, is the honest choice: the parser honours
-h/--helpbefore any spec lookup on every level, and every help page lists the row, so a user option with either name could never have worked as declared. Catching it when the spec is built means the app fails at definition time with a message naming the offending spec, instead of at the first invocation with a help page. A name that merely starts withhelp(--helper) is fine, and the check runs insidecommandspecs too since they use the same builders.This also removes the edge noted on PR #21: a clustered
-xhand a bare-hcan no longer diverge, because-hcannot be declared.#9: help row missing on option-less pages
The
-h, --helprow was nested inside the Options section, which only rendered when the spec declared options, somyapp init --helpprinted a page that never mentioned the-hit had just accepted. The section and the row now render on every page, and the usage line always says[options]:Pages that already had options are unchanged. I diffed both README help samples (quick start and
build --help) against the actual output: identical.Tests
12 new checks: the help row, Options section and
[options]on an option-less subcommand and on an app with no options at all,-hstill honoured there, and rejection of--help/-honflag,option, and inside acommandspec, with the exact message, plus--helperaccepted. Suite: 170 passed, 0 failed on Kaappi v0.27.1.Docs
README Help section states the always-present row and the reserved names. CHANGELOG: two Changed entries, since raising on a previously accepted spec and adding a section to some help pages are both visible changes.
Closes #9
Closes #10
🤖 Generated with Claude Code