Skip to content

Commit

Permalink
fix(completions): add quoting of command names (#247)
Browse files Browse the repository at this point in the history
  • Loading branch information
Secrus committed Sep 6, 2022
1 parent 6a5afa4 commit 011de8d
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 11 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -23,3 +23,4 @@ test.py
/test
.pytest_cache
.vscode
*.patch
20 changes: 12 additions & 8 deletions src/cleo/commands/completions_command.py
Expand Up @@ -8,6 +8,7 @@
import subprocess

from cleo import helpers
from cleo._compat import shell_quote
from cleo.commands.command import Command
from cleo.commands.completions.templates import TEMPLATES

Expand Down Expand Up @@ -156,13 +157,14 @@ def render_bash(self) -> str:
for cmd in sorted(self.application.all().values(), key=lambda c: c.name or ""):
if cmd.hidden or not cmd.enabled or not cmd.name:
continue
cmds.append(cmd.name)
command_name = shell_quote(cmd.name) if " " in cmd.name else cmd.name
cmds.append(command_name)
options = " ".join(
f"--{opt.name}".replace(":", "\\:")
for opt in sorted(cmd.definition.options, key=lambda o: o.name)
)
cmds_opts += [
f" ({cmd.name})",
f" ({command_name})",
f' opts="${{opts}} {options}"',
" ;;",
"", # newline
Expand Down Expand Up @@ -200,13 +202,14 @@ def sanitize(s: str) -> str:
for cmd in sorted(self.application.all().values(), key=lambda c: c.name or ""):
if cmd.hidden or not cmd.enabled or not cmd.name:
continue
cmds.append(self._zsh_describe(cmd.name, sanitize(cmd.description)))
command_name = shell_quote(cmd.name) if " " in cmd.name else cmd.name
cmds.append(self._zsh_describe(command_name, sanitize(cmd.description)))
options = " ".join(
self._zsh_describe(f"--{opt.name}", sanitize(opt.description))
for opt in sorted(cmd.definition.options, key=lambda o: o.name)
)
cmds_opts += [
f" ({cmd.name})",
f" ({command_name})",
f" opts+=({options})",
" ;;",
"", # newline
Expand Down Expand Up @@ -243,21 +246,22 @@ def sanitize(s: str) -> str:
for cmd in sorted(self.application.all().values(), key=lambda c: c.name or ""):
if cmd.hidden or not cmd.enabled or not cmd.name:
continue
command_name = shell_quote(cmd.name) if " " in cmd.name else cmd.name
cmds.append(
f"complete -c {script_name} -f -n '__fish{function}_no_subcommand' "
f"-a {cmd.name} -d '{sanitize(cmd.description)}'"
f"-a {command_name} -d '{sanitize(cmd.description)}'"
)
cmds_opts += [
f"# {cmd.name}",
f"# {command_name}",
*[
f"complete -c {script_name} -A "
f"-n '__fish_seen_subcommand_from {cmd.name}' "
f"-n '__fish_seen_subcommand_from {command_name}' "
f"-l {opt.name} -d '{sanitize(opt.description)}'"
for opt in sorted(cmd.definition.options, key=lambda o: o.name)
],
"", # newline
]
cmds_names.append(cmd.name)
cmds_names.append(command_name)

return TEMPLATES["fish"] % {
"script_name": script_name,
Expand Down
6 changes: 5 additions & 1 deletion tests/commands/completion/fixtures/bash.txt
Expand Up @@ -41,6 +41,10 @@ _my_function()
opts="${opts} "
;;

('spaced command')
opts="${opts} "
;;

esac

COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
Expand All @@ -51,7 +55,7 @@ _my_function()

# completing for a command
if [[ $cur == $com ]]; then
coms="command:with:colons hello help list"
coms="command:with:colons hello help list 'spaced command'"

COMPREPLY=($(compgen -W "${coms}" -- ${cur}))
__ltrim_colon_completions "$cur"
Expand Down
10 changes: 10 additions & 0 deletions tests/commands/completion/fixtures/command_with_space_in_name.py
@@ -0,0 +1,10 @@
from __future__ import annotations

from cleo.commands.command import Command
from cleo.helpers import argument


class SpacedCommand(Command):
name = "spaced command"
description = "Command with space in name."
arguments = [argument("test", description="test argument")]
5 changes: 4 additions & 1 deletion tests/commands/completion/fixtures/fish.txt
@@ -1,6 +1,6 @@
function __fish_my_function_no_subcommand
for i in (commandline -opc)
if contains -- $i command:with:colons hello help list
if contains -- $i command:with:colons hello help list 'spaced command'
return 1
end
end
Expand All @@ -21,6 +21,7 @@ complete -c script -f -n '__fish_my_function_no_subcommand' -a command:with:colo
complete -c script -f -n '__fish_my_function_no_subcommand' -a hello -d 'Complete me please.'
complete -c script -f -n '__fish_my_function_no_subcommand' -a help -d 'Displays help for a command.'
complete -c script -f -n '__fish_my_function_no_subcommand' -a list -d 'Lists commands.'
complete -c script -f -n '__fish_my_function_no_subcommand' -a 'spaced command' -d 'Command with space in name.'

# command options

Expand All @@ -34,3 +35,5 @@ complete -c script -A -n '__fish_seen_subcommand_from hello' -l option-without-d
# help

# list

# 'spaced command'
6 changes: 5 additions & 1 deletion tests/commands/completion/fixtures/zsh.txt
Expand Up @@ -21,7 +21,7 @@ _my_function()
opts+=("--ansi:Force ANSI output." "--help:Display help for the given command. When no command is given display help for the list command." "--no-ansi:Disable ANSI output." "--no-interaction:Do not ask any interactive question." "--quiet:Do not output any message." "--verbose:Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug." "--version:Display this application version.")
elif [[ $cur == $com ]]; then
state="command"
coms+=("command\:with\:colons:Test." "hello:Complete me please." "help:Displays help for a command." "list:Lists commands.")
coms+=("command\:with\:colons:Test." "hello:Complete me please." "help:Displays help for a command." "list:Lists commands." "'spaced command':Command with space in name.")
fi

case $state in
Expand All @@ -47,6 +47,10 @@ _my_function()
opts+=()
;;

('spaced command')
opts+=()
;;

esac

_describe 'option' opts
Expand Down
6 changes: 6 additions & 0 deletions tests/commands/completion/test_completions_command.py
Expand Up @@ -6,9 +6,11 @@

import pytest

from cleo._compat import WINDOWS
from cleo.application import Application
from cleo.testers.command_tester import CommandTester
from tests.commands.completion.fixtures.command_with_colons import CommandWithColons
from tests.commands.completion.fixtures.command_with_space_in_name import SpacedCommand
from tests.commands.completion.fixtures.hello_command import HelloCommand


Expand All @@ -19,6 +21,7 @@
app = Application()
app.add(HelloCommand())
app.add(CommandWithColons())
app.add(SpacedCommand())


def test_invalid_shell() -> None:
Expand All @@ -29,6 +32,7 @@ def test_invalid_shell() -> None:
tester.execute("pomodoro")


@pytest.mark.skipif(WINDOWS, reason="Only test linux shells")
def test_bash(mocker: MockerFixture) -> None:
mocker.patch(
"cleo.io.inputs.string_input.StringInput.script_name",
Expand All @@ -50,6 +54,7 @@ def test_bash(mocker: MockerFixture) -> None:
assert expected == tester.io.fetch_output().replace("\r\n", "\n")


@pytest.mark.skipif(WINDOWS, reason="Only test linux shells")
def test_zsh(mocker: MockerFixture) -> None:
mocker.patch(
"cleo.io.inputs.string_input.StringInput.script_name",
Expand All @@ -71,6 +76,7 @@ def test_zsh(mocker: MockerFixture) -> None:
assert expected == tester.io.fetch_output().replace("\r\n", "\n")


@pytest.mark.skipif(WINDOWS, reason="Only test linux shells")
def test_fish(mocker: MockerFixture) -> None:
mocker.patch(
"cleo.io.inputs.string_input.StringInput.script_name",
Expand Down

0 comments on commit 011de8d

Please sign in to comment.