Skip to content

Commit 011de8d

Browse files
authored
fix(completions): add quoting of command names (#247)
1 parent 6a5afa4 commit 011de8d

File tree

7 files changed

+43
-11
lines changed

7 files changed

+43
-11
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ test.py
2323
/test
2424
.pytest_cache
2525
.vscode
26+
*.patch

src/cleo/commands/completions_command.py

+12-8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import subprocess
99

1010
from cleo import helpers
11+
from cleo._compat import shell_quote
1112
from cleo.commands.command import Command
1213
from cleo.commands.completions.templates import TEMPLATES
1314

@@ -156,13 +157,14 @@ def render_bash(self) -> str:
156157
for cmd in sorted(self.application.all().values(), key=lambda c: c.name or ""):
157158
if cmd.hidden or not cmd.enabled or not cmd.name:
158159
continue
159-
cmds.append(cmd.name)
160+
command_name = shell_quote(cmd.name) if " " in cmd.name else cmd.name
161+
cmds.append(command_name)
160162
options = " ".join(
161163
f"--{opt.name}".replace(":", "\\:")
162164
for opt in sorted(cmd.definition.options, key=lambda o: o.name)
163165
)
164166
cmds_opts += [
165-
f" ({cmd.name})",
167+
f" ({command_name})",
166168
f' opts="${{opts}} {options}"',
167169
" ;;",
168170
"", # newline
@@ -200,13 +202,14 @@ def sanitize(s: str) -> str:
200202
for cmd in sorted(self.application.all().values(), key=lambda c: c.name or ""):
201203
if cmd.hidden or not cmd.enabled or not cmd.name:
202204
continue
203-
cmds.append(self._zsh_describe(cmd.name, sanitize(cmd.description)))
205+
command_name = shell_quote(cmd.name) if " " in cmd.name else cmd.name
206+
cmds.append(self._zsh_describe(command_name, sanitize(cmd.description)))
204207
options = " ".join(
205208
self._zsh_describe(f"--{opt.name}", sanitize(opt.description))
206209
for opt in sorted(cmd.definition.options, key=lambda o: o.name)
207210
)
208211
cmds_opts += [
209-
f" ({cmd.name})",
212+
f" ({command_name})",
210213
f" opts+=({options})",
211214
" ;;",
212215
"", # newline
@@ -243,21 +246,22 @@ def sanitize(s: str) -> str:
243246
for cmd in sorted(self.application.all().values(), key=lambda c: c.name or ""):
244247
if cmd.hidden or not cmd.enabled or not cmd.name:
245248
continue
249+
command_name = shell_quote(cmd.name) if " " in cmd.name else cmd.name
246250
cmds.append(
247251
f"complete -c {script_name} -f -n '__fish{function}_no_subcommand' "
248-
f"-a {cmd.name} -d '{sanitize(cmd.description)}'"
252+
f"-a {command_name} -d '{sanitize(cmd.description)}'"
249253
)
250254
cmds_opts += [
251-
f"# {cmd.name}",
255+
f"# {command_name}",
252256
*[
253257
f"complete -c {script_name} -A "
254-
f"-n '__fish_seen_subcommand_from {cmd.name}' "
258+
f"-n '__fish_seen_subcommand_from {command_name}' "
255259
f"-l {opt.name} -d '{sanitize(opt.description)}'"
256260
for opt in sorted(cmd.definition.options, key=lambda o: o.name)
257261
],
258262
"", # newline
259263
]
260-
cmds_names.append(cmd.name)
264+
cmds_names.append(command_name)
261265

262266
return TEMPLATES["fish"] % {
263267
"script_name": script_name,

tests/commands/completion/fixtures/bash.txt

+5-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ _my_function()
4141
opts="${opts} "
4242
;;
4343

44+
('spaced command')
45+
opts="${opts} "
46+
;;
47+
4448
esac
4549

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

5256
# completing for a command
5357
if [[ $cur == $com ]]; then
54-
coms="command:with:colons hello help list"
58+
coms="command:with:colons hello help list 'spaced command'"
5559

5660
COMPREPLY=($(compgen -W "${coms}" -- ${cur}))
5761
__ltrim_colon_completions "$cur"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from __future__ import annotations
2+
3+
from cleo.commands.command import Command
4+
from cleo.helpers import argument
5+
6+
7+
class SpacedCommand(Command):
8+
name = "spaced command"
9+
description = "Command with space in name."
10+
arguments = [argument("test", description="test argument")]

tests/commands/completion/fixtures/fish.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
function __fish_my_function_no_subcommand
22
for i in (commandline -opc)
3-
if contains -- $i command:with:colons hello help list
3+
if contains -- $i command:with:colons hello help list 'spaced command'
44
return 1
55
end
66
end
@@ -21,6 +21,7 @@ complete -c script -f -n '__fish_my_function_no_subcommand' -a command:with:colo
2121
complete -c script -f -n '__fish_my_function_no_subcommand' -a hello -d 'Complete me please.'
2222
complete -c script -f -n '__fish_my_function_no_subcommand' -a help -d 'Displays help for a command.'
2323
complete -c script -f -n '__fish_my_function_no_subcommand' -a list -d 'Lists commands.'
24+
complete -c script -f -n '__fish_my_function_no_subcommand' -a 'spaced command' -d 'Command with space in name.'
2425

2526
# command options
2627

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

3637
# list
38+
39+
# 'spaced command'

tests/commands/completion/fixtures/zsh.txt

+5-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ _my_function()
2121
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.")
2222
elif [[ $cur == $com ]]; then
2323
state="command"
24-
coms+=("command\:with\:colons:Test." "hello:Complete me please." "help:Displays help for a command." "list:Lists commands.")
24+
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.")
2525
fi
2626

2727
case $state in
@@ -47,6 +47,10 @@ _my_function()
4747
opts+=()
4848
;;
4949

50+
('spaced command')
51+
opts+=()
52+
;;
53+
5054
esac
5155

5256
_describe 'option' opts

tests/commands/completion/test_completions_command.py

+6
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66

77
import pytest
88

9+
from cleo._compat import WINDOWS
910
from cleo.application import Application
1011
from cleo.testers.command_tester import CommandTester
1112
from tests.commands.completion.fixtures.command_with_colons import CommandWithColons
13+
from tests.commands.completion.fixtures.command_with_space_in_name import SpacedCommand
1214
from tests.commands.completion.fixtures.hello_command import HelloCommand
1315

1416

@@ -19,6 +21,7 @@
1921
app = Application()
2022
app.add(HelloCommand())
2123
app.add(CommandWithColons())
24+
app.add(SpacedCommand())
2225

2326

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

3134

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

5256

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

7378

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

0 commit comments

Comments
 (0)