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

feat(completion): add zsh support #609

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
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
60 changes: 57 additions & 3 deletions linodecli/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,73 @@ def get_completions(ops, help_flag, action):
return (
"linode-cli completion [SHELL]\n\n"
"Prints shell completions for the requested shell to stdout.\n"
"Currently, only completions for bash and fish are available."
"Currently, only completions for bash, fish, and zsh are available."
)
if action == "bash":
return get_bash_completions(ops)
if action == "fish":
return get_fish_completions(ops)
if action == "zsh":
return get_zsh_completions(ops)
return (
"Completions are only available for bash and fish at this time.\n\n"
"Completions are only available for bash, fish, and zsh at this time.\n\n"
"To retrieve these, please invoke as\n"
"`linode-cli completion bash` or `linode-cli completion fish`"
"`linode-cli completion bash`, `linode-cli completion fish`, or `linode-cli completion zsh`"
)


def get_zsh_completions(ops):
"""
Generates and returns Zsh shell completions based on the baked spec
"""
completion_template = Template(
"""#compdef linode-cli linode lin

# This is a generated file by Linode-CLI! Do not modify!
local -a subcommands
subcommands=(
'$subcommands --help'
)

local -a command
local -a opts

_arguments -C \\
"1: :($subcommands)" \\
'*:: :->subcmds' && return 0

if (( CURRENT == 2 )); then
case $words[1] in
$command_items
esac
fi
"""
)

command_template = Template(
"""$command)
command=(
'$actions --help'
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm seeing some weird outputs attempting to autocomplete on subcommands:

linode-cli linodes list\ create\ view\ update\ delete\ backups-list\ snapshot\ backups-cancel\ backups-enable\ backup-view\ backup-restore\ boot\ clone\ configs-list\ config-create\ config-view\ config-update\ config-delete\ config-interfaces-list\ config-interface-add\ config-interface-view\ config-interface-update\ config-interface-delete\ config-interfaces-order\ disks-list\ disk-create\ disk-view\ disk-update\ disk-delete\ disk-clone\ disk-reset-password\ disk-resize\ firewalls-list\ ips-list\ ip-add\ ip-view\ ip-update\ ip-delete\ migrate\ upgrade\ nodebalancers\ linode-reset-password\ reboot\ rebuild\ rescue\ resize\ shutdown\ transfer-view\ volumes\ types\ type-view\ --help

Dropping the quotes here seems to resolve the issue on my end 👍

Suggested change
'$actions --help'
'$actions --help'

)
_describe -t commands "$command command" command
;;"""
)

command_blocks = [
command_template.safe_substitute(
command=op, actions=" ".join(list(actions.keys()))
)
for op, actions in ops.items()
]

rendered = completion_template.safe_substitute(
subcommands=" ".join(ops.keys()),
command_items="\n".join(command_blocks),
)

return rendered


def get_fish_completions(ops):
"""
Generates and returns fish shell completions based on the baked spec
Expand Down
36 changes: 36 additions & 0 deletions tests/unit/test_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,32 @@ class TestCompletion:
complete -F _linode_cli linode-cli
complete -F _linode_cli linode
complete -F _linode_cli lin"""
zsh_expected = """#compdef linode-cli linode lin

# This is a generated file by Linode-CLI! Do not modify!
local -a subcommands
subcommands=(
'temp_key --help'
)

local -a command
local -a opts

_arguments -C \\
"1: :(temp_key)" \\
'*:: :->subcmds' && return 0

if (( CURRENT == 2 )); then
case $words[1] in
temp_key)
command=(
'temp_action --help'
)
_describe -t commands "temp_key command" command
;;
esac
fi
"""

def test_fish_completion(self, mocker):
"""
Expand All @@ -60,6 +86,13 @@ def test_bash_completion(self, mocker):
actual = completion.get_bash_completions(self.ops)
assert actual == self.bash_expected

def test_zsh_completion(self):
"""
Test if the Zsh completion renders correctly
"""
actual = completion.get_zsh_completions(self.ops)
assert actual == self.zsh_expected

def test_get_completions(self):
"""
Test get_completions for arg parse
Expand All @@ -70,6 +103,9 @@ def test_get_completions(self):
actual = completion.get_completions(self.ops, False, "fish")
assert actual == self.fish_expected

actual = completion.get_completions(self.ops, False, "zsh")
assert actual == self.zsh_expected

actual = completion.get_completions(self.ops, False, "notrealshell")
assert "invoke" in actual

Expand Down
Loading