Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

completion: add zsh #653

Merged
merged 11 commits into from
Aug 13, 2022
4 changes: 3 additions & 1 deletion .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ jobs:
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b

- name: Run shellcheck
uses: ludeeus/action-shellcheck@94e0aab03ca135d11a35e5bfc14e6746dc56e7e9
uses: ludeeus/action-shellcheck@203a3fd018dfe73f8ae7e3aa8da2c149a5f41c33
with:
ignore_names: configlet.zsh
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Commands:

Options for completion:
-s, --shell <shell> Choose the shell type (required)
Allowed values: b[ash], f[ish]
Allowed values: b[ash], f[ish], z[sh]

Options for fmt:
-e, --exercise <slug> Only operate on this exercise
Expand Down
114 changes: 114 additions & 0 deletions completions/configlet.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#compdef configlet

autoload -U is-at-least

(( $+functions[_configlet_commands] )) ||
_configlet_commands() {
local commands
commands=(
# subcommands with no options
"generate:Generate concept exercise introductions" \
"lint:Check the track configuration for correctness" \
# subcommands with options
"completion:Output a completion script for a given shell" \
"fmt:Format the exercise '.meta/config.json' files" \
"info:Print track information" \
"sync:Check or update Practice Exercise docs, metadata, and tests" \
"uuid:Output a version 4 UUID" \
)
_describe -t commands 'configlet commands' commands "$@"
}

_configlet() {
typeset -a _arguments_options

if is-at-least 5.2; then
_arguments_options=(-s -S -C)
else
_arguments_options=(-s -C)
fi
Comment on lines +25 to +29
Copy link
Member Author

@ee7 ee7 Aug 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From man zshcompsys:

The options of _arguments have the following meanings:

-s
Enable option stacking for single-letter options, whereby multiple single-letter options may be combined into a single word. For example, the two options -x and -y may be combined into a single word -xy. By default, every word corresponds to a single option name (-xy is a single option named xy).
Options beginning with a single hyphen or plus sign are eligible for stacking; words beginning with two hyphens are not.

Note that -s after -- has a different meaning, which is documented in the segment entitled `Deriving spec forms from the help output'.

-C
Modify the curcontext parameter for an action of the form ->state. This is discussed in detail below.

-S
Do not complete options after a -- appearing on the line, and ignore the --. For example, with -S, in the line

foobar -x -- -y

the -x is considered an option, the -y is considered an argument, and the -- is considered to be neither.


local line
local curcontext="$curcontext"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From man zshcompsys:

The option -C tells _arguments to modify the curcontext parameter for an action of the form ->state. This is the standard parameter used to keep track of the current context. Here it (and not the context array) should be made local to the calling function to avoid passing back the modified value and should be initialised to the current value at the start of the function:

local curcontext="$curcontext"


_configlet_global_opts=(
{-h,--help}'[Show help]'
'--version[Show version information]'
'(-t --track-dir)'{-t+,--track-dir=}'[Select a track directory]:directory:_directories'
{-v,--verbosity}'[Verbosity level]: :(quiet normal detailed)'
)

_arguments "${_arguments_options[@]}" \
"$_configlet_global_opts[@]" \
":: :_configlet_commands" \
"*::: :->configlet"

words=($line[1] "${words[@]}")
(( CURRENT += 1 ))
curcontext="${curcontext%:*:*}:configlet-command-$line[1]:"

_configlet_complete_any_exercise_slug() {
local -a cmd slugs slug_paths
slug_paths=(./exercises/concept/*(/))
slugs=( ${${slug_paths#./exercises/concept/}%-*-*} )
slug_paths=(./exercises/practice/*(/))
slugs+=( ${${slug_paths#./exercises/practice/}%-*-*} )
compadd "$@" -a slugs
}

_configlet_complete_practice_exercise_slug() {
local -a cmd slugs slug_paths
slug_paths=(./exercises/practice/*(/))
slugs=( ${${slug_paths#./exercises/practice/}%-*-*} )
compadd "$@" -a slugs
}
Copy link
Member Author

@ee7 ee7 Aug 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this could use a refactor.

Note that this can suggest invalid slugs, because it:

Let's handle that later - we should fix it for the bash and fish completion scripts too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored somewhat in 833522b


case $line[1] in
# subcommands with no options
(generate)
_arguments "${_arguments_options[@]}" \
"$_configlet_global_opts[@]" \
;;
(lint)
_arguments "${_arguments_options[@]}" \
"$_configlet_global_opts[@]" \
;;
# subcommands with options
(completion)
_arguments "${_arguments_options[@]}" \
"$_configlet_global_opts[@]" \
{-s,--shell}'[Select the shell type]: :(bash fish zsh)' \
;;
(fmt)
_arguments "${_arguments_options[@]}" \
"$_configlet_global_opts[@]" \
'(-e --exercise)'{-e+,--exercise=}'[exercise slug]:slug:_configlet_complete_any_exercise_slug' \
{-u,--update}'[Write changes]' \
{-y,--yes}'[Auto-confirm update]' \
;;
(info)
_arguments "${_arguments_options[@]}" \
"$_configlet_global_opts[@]" \
{-o,--offline}'[Do not update prob-specs cache]' \
;;
(sync)
_arguments "${_arguments_options[@]}" \
"$_configlet_global_opts[@]" \
'(-e --exercise)'{-e+,--exercise=}'[exercise slug]:slug:_configlet_complete_practice_exercise_slug' \
{-o,--offline}'[Do not update prob-specs cache]' \
{-u,--update}'[Write changes]' \
{-y,--yes}'[Auto-confirm update]' \
'--docs[Sync docs only]' \
'--filepaths[Populate .meta/config.json "files" entry]' \
'--metadata[Sync metadata only]' \
'--tests[For auto-confirming]: :(choose include exclude)' \
;;
(uuid)
_arguments "${_arguments_options[@]}" \
"$_configlet_global_opts[@]" \
'(-n --num)'{-n+,--num=}'[How many UUIDs]:' \
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-optname+
The first argument may appear immediately after optname in the same word, or may appear as a separate word after the option. For example, -foo+:... specifies that the completed option and argument will look like either -fooarg or -foo arg.

-optname=
The argument may appear as the next word, or in same word as the option name provided that it is separated from it by an equals sign, for example -foo=arg or -foo arg.

;;
esac
}

_configlet "$@"
1 change: 1 addition & 0 deletions src/cli.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type
sNil = "nil"
sBash = "bash"
sFish = "fish"
sZsh = "zsh"

SyncKind* = enum
skDocs = "docs"
Expand Down
4 changes: 2 additions & 2 deletions tests/test_binary.nim
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,7 @@ proc testsForGenerate(binaryPath: string) =
proc testsForCompletion(binaryPath: string) =
suite "completion":
const completionsDir = repoRootDir / "completions"
for shell in ["bash", "fish"]:
for shell in ["bash", "fish", "zsh"]:
test shell:
let c = shell[0]
# Convert platform-specific line endings (e.g. CR+LF on Windows) to LF
Expand All @@ -943,7 +943,7 @@ proc testsForCompletion(binaryPath: string) =
execAndCheck(0, &"{binaryPath} completion -s {shell}", expected)
execAndCheck(0, &"{binaryPath} completion -s {c}", expected)
execAndCheck(0, &"{binaryPath} completion -s{c}", expected)
for shell in ["powershell", "zsh"]:
for shell in ["powershell"]:
test &"{shell} (produces an error)":
let (outp, exitCode) = execCmdEx(&"{binaryPath} completion -s {shell}")
check:
Expand Down