From 775674c81720c861cd6e81494ca534992f60e74c Mon Sep 17 00:00:00 2001 From: Matt Crane Date: Fri, 13 Sep 2024 12:08:37 -0700 Subject: [PATCH 1/3] Escape colon in zsh value/description pairs --- typer/_completion_classes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/typer/_completion_classes.py b/typer/_completion_classes.py index 71ba031860..f0bb89c3cc 100644 --- a/typer/_completion_classes.py +++ b/typer/_completion_classes.py @@ -86,6 +86,7 @@ def escape(s: str) -> str: .replace("'", "''") .replace("$", "\\$") .replace("`", "\\`") + .replace(":", r"\\:") ) # TODO: Explore replicating the new behavior from Click, pay attention to From 03ac1b03ec893f1c415469656eefa63ab0259a83 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Sep 2024 17:16:49 +0200 Subject: [PATCH 2/3] Add unit tests for completing options with colons --- tests/test_completion/colon_example.py | 25 +++ .../test_completion_option_colon.py | 209 ++++++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 tests/test_completion/colon_example.py create mode 100644 tests/test_completion/test_completion_option_colon.py diff --git a/tests/test_completion/colon_example.py b/tests/test_completion/colon_example.py new file mode 100644 index 0000000000..70292385d7 --- /dev/null +++ b/tests/test_completion/colon_example.py @@ -0,0 +1,25 @@ +import typer + +image_desc = [ + ("alpine:latest", "latest alpine image"), + ("alpine:hello", "fake image: for testing"), + ("nvidia/cuda:10.0-devel-ubuntu18.04", ""), +] + + +def _complete(incomplete: str) -> str: + for image, desc in image_desc: + if image.startswith(incomplete): + yield image, desc + + +app = typer.Typer() + + +@app.command() +def image(name: str = typer.Option(autocompletion=_complete)): + typer.echo(name) + + +if __name__ == "__main__": + app() diff --git a/tests/test_completion/test_completion_option_colon.py b/tests/test_completion/test_completion_option_colon.py new file mode 100644 index 0000000000..7907380282 --- /dev/null +++ b/tests/test_completion/test_completion_option_colon.py @@ -0,0 +1,209 @@ +import os +import subprocess +import sys + +from . import colon_example as mod + + +def test_completion_colon_bash_all(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_bash", + "COMP_WORDS": "colon_example.py --name ", + "COMP_CWORD": "2", + }, + ) + assert "alpine:hello" in result.stdout + assert "alpine:latest" in result.stdout + assert "nvidia/cuda:10.0-devel-ubuntu18.04" in result.stdout + + +def test_completion_colon_bash_partial(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_bash", + "COMP_WORDS": "colon_example.py --name alpine ", + "COMP_CWORD": "2", + }, + ) + assert "alpine:hello" in result.stdout + assert "alpine:latest" in result.stdout + assert "nvidia/cuda:10.0-devel-ubuntu18.04" not in result.stdout + + +def test_completion_colon_bash_single(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_bash", + "COMP_WORDS": "colon_example.py --name alpine:hell ", + "COMP_CWORD": "2", + }, + ) + assert "alpine:hello" in result.stdout + assert "alpine:latest" not in result.stdout + assert "nvidia/cuda:10.0-devel-ubuntu18.04" not in result.stdout + + +def test_completion_colon_zsh_all(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": "colon_example.py --name ", + }, + ) + assert "alpine\\\\:hello" in result.stdout + assert "alpine\\\\:latest" in result.stdout + assert "nvidia/cuda\\\\:10.0-devel-ubuntu18.04" in result.stdout + + +def test_completion_colon_zsh_partial(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": "colon_example.py --name alpine", + }, + ) + assert "alpine\\\\:hello" in result.stdout + assert "alpine\\\\:latest" in result.stdout + assert "nvidia/cuda\\\\:10.0-devel-ubuntu18.04" not in result.stdout + + +def test_completion_colon_zsh_single(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_zsh", + "_TYPER_COMPLETE_ARGS": "colon_example.py --name alpine:hell", + }, + ) + assert "alpine\\\\:hello" in result.stdout + assert "alpine\\\\:latest" not in result.stdout + assert "nvidia/cuda\\\\:10.0-devel-ubuntu18.04" not in result.stdout + + +def test_completion_colon_powershell_all(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_powershell", + "_TYPER_COMPLETE_ARGS": "colon_example.py --name ", + "_TYPER_COMPLETE_WORD_TO_COMPLETE": "", + }, + ) + assert "alpine:hello" in result.stdout + assert "alpine:latest" in result.stdout + assert "nvidia/cuda:10.0-devel-ubuntu18.04" in result.stdout + + +def test_completion_colon_powershell_partial(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_powershell", + "_TYPER_COMPLETE_ARGS": "colon_example.py --name alpine", + "_TYPER_COMPLETE_WORD_TO_COMPLETE": "alpine", + }, + ) + assert "alpine:hello" in result.stdout + assert "alpine:latest" in result.stdout + assert "nvidia/cuda:10.0-devel-ubuntu18.04" not in result.stdout + + +def test_completion_colon_powershell_single(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_powershell", + "_TYPER_COMPLETE_ARGS": "colon_example.py --name alpine:hell", + "_TYPER_COMPLETE_WORD_TO_COMPLETE": "alpine:hell", + }, + ) + assert "alpine:hello" in result.stdout + assert "alpine:latest" not in result.stdout + assert "nvidia/cuda:10.0-devel-ubuntu18.04" not in result.stdout + + +def test_completion_colon_pwsh_all(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_pwsh", + "_TYPER_COMPLETE_ARGS": "colon_example.py --name", + }, + ) + + assert "alpine:hello" in result.stdout + assert "alpine:latest" in result.stdout + assert "nvidia/cuda:10.0-devel-ubuntu18.04" in result.stdout + + +def test_completion_colon_pwsh_partial(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_pwsh", + "_TYPER_COMPLETE_ARGS": "colon_example.py --name alpine", + "_TYPER_COMPLETE_WORD_TO_COMPLETE": "alpine", + }, + ) + assert "alpine:hello" in result.stdout + assert "alpine:latest" in result.stdout + assert "nvidia/cuda:10.0-devel-ubuntu18.04" not in result.stdout + + +def test_completion_colon_pwsh_single(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_COLON_EXAMPLE.PY_COMPLETE": "complete_pwsh", + "_TYPER_COMPLETE_ARGS": "colon_example.py --name alpine:hell", + "_TYPER_COMPLETE_WORD_TO_COMPLETE": "alpine:hell", + }, + ) + assert "alpine:hello" in result.stdout + assert "alpine:latest" not in result.stdout + assert "nvidia/cuda:10.0-devel-ubuntu18.04" not in result.stdout + + +# TODO: tests for complete_fish From 1fcc47296e5ed6ab6d4ab24ec4ebb3af3c4ec585 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Sep 2024 17:35:30 +0200 Subject: [PATCH 3/3] Fix coverage --- tests/test_completion/test_completion_option_colon.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_completion/test_completion_option_colon.py b/tests/test_completion/test_completion_option_colon.py index 7907380282..f106eca862 100644 --- a/tests/test_completion/test_completion_option_colon.py +++ b/tests/test_completion/test_completion_option_colon.py @@ -5,6 +5,16 @@ from . import colon_example as mod +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--name", "DeadPool"], + capture_output=True, + encoding="utf-8", + ) + assert result.returncode == 0 + assert "DeadPool" in result.stdout + + def test_completion_colon_bash_all(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, " "],