@@ -25,6 +25,46 @@ export Ui
2525export Cache FileStore DirectoryStore
2626export Config
2727
28+ /**
29+ Shells for which $Command can generate completion scripts.
30+ */
31+ COMPLETION-SHELLS_ ::= [ "bash" , "zsh" , "fish" , "powershell" ]
32+
33+ /**
34+ Returns the completion script for the given $shell.
35+
36+ The $program-path is baked into the script and used to re-invoke the
37+ binary at completion time.
38+ */
39+ completion-script-for-shell_ --shell / string --program-path / string -> string :
40+ if shell == "bash" : return bash-completion-script_ --program-path = program-path
41+ if shell == "zsh" : return zsh-completion-script_ --program-path = program-path
42+ if shell == "fish" : return fish-completion-script_ --program-path = program-path
43+ if shell == "powershell" : return powershell-completion-script_ --program-path = program-path
44+ unreachable
45+
46+ /**
47+ Scans $arguments for a "--generate-completion <shell>" or
48+ "--generate-completion=<shell>" occurrence with a known shell value.
49+
50+ Returns the shell name if found, null otherwise.
51+
52+ Unknown or missing values are ignored, so that the normal parser can
53+ report them with its standard error machinery.
54+ */
55+ find-generate-completion-arg_ arguments / List -> string? :
56+ prefix := "--generate-completion="
57+ for i := 0 ; i < arguments .size ; i ++:
58+ arg / string := arguments [ i ]
59+ value / string? := null
60+ if arg == "--generate-completion" :
61+ if i + 1 < arguments .size : value = arguments [ i + 1 ]
62+ else if arg .starts-with prefix :
63+ value = arg [ prefix .size .. ]
64+ if value and ( COMPLETION-SHELLS_ .contains value ) :
65+ return value
66+ return null
67+
2868/**
2969An object giving access to common operations for CLI programs.
3070
@@ -310,8 +350,10 @@ class Command:
310350 --cli / Cli?= null
311351 --add-ui-help / bool= ( not cli )
312352 --add-completion / bool= true :
353+ added-completion-flag := false
313354 if add-completion :
314- add-completion-command_ --program-path = invoked-command
355+ added-completion-flag = add-completion-bootstrap_
356+ --program-path = invoked-command
315357
316358 // Handle __complete requests before any other processing.
317359 if add-completion and not arguments .is-empty and arguments [ 0 ] == "__complete" :
@@ -328,6 +370,14 @@ class Command:
328370 print ":$ result.directive "
329371 return
330372
373+ // Handle --generate-completion before any other processing, so that
374+ // required rest arguments and run callbacks are skipped.
375+ if added-completion-flag :
376+ shell := find-generate-completion-arg_ arguments
377+ if shell :
378+ print ( completion-script-for-shell_ --shell = shell --program-path = invoked-command )
379+ return
380+
331381 if not cli :
332382 ui := create-ui-from-args_ arguments
333383 log .set-default ( ui .logger --name = name )
@@ -339,13 +389,38 @@ class Command:
339389 invocation := Invocation .private_ cli path .commands parameters
340390 invocation .command .run-callback_ .call invocation
341391
342- add-completion-command_ --program-path / string :
392+ /**
393+ Adds a bootstrap mechanism for shell completions.
394+
395+ If the command has no run callback, a "completion" subcommand is added.
396+ Otherwise, a "--generate-completion" option is added to the root command.
397+
398+ Returns true if the "--generate-completion" flag was added.
399+ */
400+ add-completion-bootstrap_ --program-path / string -> bool :
343401 // Don't add if the user already has a "completion" subcommand.
344- if find-subcommand_ "completion" : return
345- // Can't add subcommands to a command with rest args or a run callback
346- // that already has subcommands handled.
347- if run-callback_ : return
402+ if find-subcommand_ "completion" : return false
403+ // Don't add if the user already has a "--generate-completion" option.
404+ options_ .do : | opt / Option |
405+ if opt .name == "generate-completion" : return false
406+
407+ if not run-callback_ :
408+ add-completion-subcommand_ --program-path = program-path
409+ return false
410+
411+ // The root has a run callback, so we can't add a subcommand. Fall back
412+ // to a "--generate-completion" flag.
413+ add-completion-flag_
414+ return true
415+
416+ add-completion-flag_ :
417+ options_ = options_ .copy
418+ options_ .add
419+ OptionEnum "generate-completion" COMPLETION-SHELLS_
420+ --type = "shell"
421+ --help = "Print a shell completion script (bash, zsh, fish, powershell) to stdout and exit."
348422
423+ add-completion-subcommand_ --program-path / string :
349424 prog-name := basename_ program-path
350425 completion-command := Command "completion"
351426 --help = """
@@ -378,24 +453,13 @@ class Command:
378453 PowerShell:
379454 $ program-path completion powershell >> \$ PROFILE"""
380455 --rest = [
381- OptionEnum "shell" [ "bash" , "zsh" , "fish" , "powershell" ]
456+ OptionEnum "shell" COMPLETION-SHELLS_
382457 --help = "The shell to generate completions for."
383458 --required ,
384459 ]
385460 --run =:: | invocation / Invocation |
386461 shell := invocation [ "shell" ]
387- script / string := ?
388- if shell == "bash" :
389- script = bash-completion-script_ --program-path = program-path
390- else if shell == "zsh" :
391- script = zsh-completion-script_ --program-path = program-path
392- else if shell == "fish" :
393- script = fish-completion-script_ --program-path = program-path
394- else if shell == "powershell" :
395- script = powershell-completion-script_ --program-path = program-path
396- else:
397- unreachable
398- print script
462+ print ( completion-script-for-shell_ --shell = shell --program-path = program-path )
399463 subcommands_ .add completion-command
400464
401465 add-ui-options_ :
0 commit comments