Raise from generate-help on an undeclared subcommand - #24
Conversation
When the name did not match a declared command, generate-help fell back to the app's own description and specs but still printed the unknown name in the header and usage line, so the page looked like a real subcommand page for a command that does not exist. run-cli never passes an undeclared name, but generate-help is exported and an app author calling it with a typo got a confident wrong page instead of a fault. The command is now looked up once and a miss raises, before anything is printed, in line with the other caller-bug paths in run-cli. Closes #15 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 branch (Kaappi v0.27.1, same as the PR): suite passes 207/0, git diff --check is clean, DCO sign-off present, subject matches house style, and the greeter example still runs.
The main thing I verified beyond the tests: the new raise is unreachable through run-cli. parsed-command is only ever set from a found-cmd produced by find-command during parsing; an unknown bare word takes the unknown command error branch instead; and myapp nope --help takes the help-wins arm, which builds the result with no command attached, so it prints the app page. The raise therefore fires only when the exported generate-help is called directly with an undeclared name — exactly the caller-bug case targeted here. A non-string name also fails cleanly: find-command compares with equal?, so it misses and raises with the irritant intact.
Framing as Fixed rather than Changed looks right: the old output (the app's own specs rendered under a fabricated name) had no legitimate use, so no working code can have depended on it.
Two inline nitpicks, both optional — otherwise this looks good to merge from my side.
| sub-name))) | ||
| (if c (cmd-desc c) (cli-desc app))) | ||
| (cli-desc app))) | ||
| (sub (and sub-name |
There was a problem hiding this comment.
Verified the integration concern this change hinges on: run-cli can never reach this raise. parsed-command is only set for a command found via find-command in the parse loop (the found-cmd arm), unknown bare words are reported as usage errors, and --help after an unknown word resets to the app page. So the raise is reachable only by calling the exported generate-help directly with a bad name — the caller bug this targets. Collapsing the two duplicated lookups into one binding is a nice side effect.
Nitpick, feel free to skip: the (and sub-name (or (find-command …) (error …))) chain is correct (error never returns) but the raise is easy to miss as the second arm of or nested inside and inside let*. An if puts the two outcomes at the same level:
(sub (if sub-name
(or (find-command
(filter (lambda (s) (eq? (spec-type s) 'command))
(cli-specs app))
sub-name)
(error "generate-help: no such command" sub-name))
#f))There was a problem hiding this comment.
Restructured in 532f67a as suggested: if with the or lookup-or-raise in the consequent and #f in the alternative, so both outcomes sit at the same level.
| "generate-help: no such command" | ||
| (guard (e ((error-object? e) (error-object-message e))) | ||
| (capture-output (lambda () (generate-help app "nope"))) | ||
| 'returned)) |
There was a problem hiding this comment.
Nice trick: if generate-help ever stops raising, the guard body evaluates to 'returned and the check fails instead of passing vacuously. Worth a one-line comment so a future reader doesn't take 'returned for dead code:
'returned)) ; reached only if generate-help returns normally — must failRelated nitpick on line 779: (#t #f) swallows any escape, not just error objects — a hypothetical future exit inside generate-help would pass silently. ((error-object? e) #f) keeps the assertion honest. Both optional; the coverage is correct as written.
There was a problem hiding this comment.
Both taken in 532f67a: the 'returned sentinel carries a comment saying it is reached only if generate-help returns normally and must fail, and the nothing-printed check now guards with (error-object? e) instead of #t.
Review nits. The raise was the second arm of an or nested inside an and inside a let*, easy to miss; an if puts the found and not-found outcomes side by side. In the tests, the sentinel that makes the raise check fail rather than pass vacuously now says so, and the nothing-printed check catches only error objects so a future non-error escape cannot slip through it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Baiju Muthukadan <baiju.m.mail@gmail.com>
|
Review addressed in 532f67a:
Suite: 207 passed, 0 failed. |
Closes #15.
generate-help app "nope"fell back to the app's description and specs when the name matched no declared command, but kept the unknown name in the header and usage line, so it printed a confident page for a command that does not exist:run-clionly ever passes real command names, butgenerate-helpis exported, and an app author calling it with a typo deserves a fault, not a fabricated page.Change
The command is looked up once, and a miss raises
generate-help: no such commandwith the name as irritant, before anything is printed. That matches the existing treatment of caller bugs inrun-cli(a declared command with no handler, no default handler), which also raise rather than print. The two duplicatedfind-commandlookups in thelet*collapse into one binding as a side effect.Tests
Three checks: the error message, that nothing is printed before raising, and that a declared name still renders. Suite: 207 passed, 0 failed on Kaappi v0.27.1.
Docs
README: one sentence under Help. CHANGELOG: Fixed entry.
🤖 Generated with Claude Code