Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/unknown-command-help.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Report a usage error and exit with code 2 for unknown CLI commands even when help or version is requested.
15 changes: 12 additions & 3 deletions rewatch-ocaml/cli.ml
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,8 @@ exception Version
Cmdliner otherwise treats them as unknown commands. Move global options
behind the selected command and expand short help/version clusters because
Cmdliner's standard display options only provide long names. A root display
request takes precedence over otherwise invalid implicit-build arguments. *)
request takes precedence over otherwise invalid implicit-build options, but
a positional command must be valid even when help or version is requested. *)
let normalize_argv argv =
let is_short_global_cluster argument =
let length = String.length argument in
Expand Down Expand Up @@ -365,12 +366,20 @@ let normalize_argv argv =
executable :: command :: (globals @ rest)
| None ->
let globals, others = partition_implicit arguments in
if
let has_command_topic =
match others with
| topic :: _ -> not (String.starts_with ~prefix:"-" topic)
| [] -> false
in
let requests_display =
List.exists
(fun argument ->
requests_help argument || requests_version argument)
globals
then executable :: globals
in
if requests_display && has_command_topic then
executable :: "help" :: others
else if requests_display then executable :: globals
else executable :: "build" :: (globals @ others)
in
Array.of_list (normalize_display_options routed)
Expand Down
9 changes: 9 additions & 0 deletions rewatch-ocaml/tests/check_command_validation.sh
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,15 @@ run_build_output_case quiet-warning 0 \
"$root/rewatch-ocaml/tests/warning-replay" quiet

run_case build-subcommand-version accept build --version
run_case unknown-command-help exit2 invalid --help
require_error_contains unknown-command-help 'unknown command "invalid"'
run_case unknown-command-short-help exit2 invalid -h
run_case unknown-command-formatted-help exit2 --help=plain invalid
run_case unknown-command-version exit2 unkdnow --version
require_error_contains unknown-command-version 'unknown command "unkdnow"'
run_case unknown-command-short-version exit2 unkdnow -V
run_case unknown-command-leading-version exit2 --version unkdnow
run_case unknown-command-clustered-version exit2 -vV unkdnow
run_case clustered-global-version accept -vV build
run_case clustered-global-verbosity accept -vvvvv build "$project"
run_case clustered-global-version-before-help accept -Vh build
Expand Down
28 changes: 22 additions & 6 deletions tests/rewatch_ounit_tests/cli_tests.ml
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,18 @@ let tests =
((build_options ["--"; "-v"]).folder = "-v")
"double dash preserves an option-looking folder";
check
(shows_help ["some-folder"; "--help"])
"implicit command folder help uses global help";
(rejects ["invalid"; "--help"])
"an unknown command with help is rejected";
check
(shows_help ["some-folder"; "-h"])
"short implicit command help uses global help";
(rejects ["invalid"; "-h"])
"an unknown command with short help is rejected";
check
(rejects ["--help=plain"; "invalid"])
"leading formatted help rejects unknown commands";
check
(rejects ["invalid"; "-Vh"])
"help in a version cluster rejects unknown commands";
check (shows_help ["--help"]) "root help remains accepted";
check (shows_help ["build"; "--help"]) "explicit build displays command help";
check (shows_help ["build"; "-h"]) "explicit build accepts short command help";
check
Expand Down Expand Up @@ -88,8 +95,17 @@ let tests =
(shows_version ["-V"; "build"])
"a leading short version flag has global precedence";
check
(shows_version ["some-folder"; "-V"])
"implicit build extracts a trailing global version flag";
(rejects ["unkdnow"; "--version"])
"an unknown command with version is rejected";
check
(rejects ["unkdnow"; "-V"])
"an unknown command with short version is rejected";
check
(rejects ["--version"; "unkdnow"])
"leading version rejects unknown commands";
check
(rejects ["-vV"; "unkdnow"])
"version in a verbosity cluster rejects unknown commands";
check (shows_version ["--version"]) "the long global version flag is accepted";
check
(shows_version ["build"; "-V"])
Expand Down
Loading