123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- import pytest
- import click.shell_completion
- from click.core import Argument
- from click.core import Command
- from click.core import Group
- from click.core import Option
- from click.shell_completion import add_completion_class
- from click.shell_completion import CompletionItem
- from click.shell_completion import ShellComplete
- from click.types import Choice
- from click.types import File
- from click.types import Path
- def _get_completions(cli, args, incomplete):
- comp = ShellComplete(cli, {}, cli.name, "_CLICK_COMPLETE")
- return comp.get_completions(args, incomplete)
- def _get_words(cli, args, incomplete):
- return [c.value for c in _get_completions(cli, args, incomplete)]
- def test_command():
- cli = Command("cli", params=[Option(["-t", "--test"])])
- assert _get_words(cli, [], "") == []
- assert _get_words(cli, [], "-") == ["-t", "--test", "--help"]
- assert _get_words(cli, [], "--") == ["--test", "--help"]
- assert _get_words(cli, [], "--t") == ["--test"]
- # -t has been seen, so --test isn't suggested
- assert _get_words(cli, ["-t", "a"], "-") == ["--help"]
- def test_group():
- cli = Group("cli", params=[Option(["-a"])], commands=[Command("x"), Command("y")])
- assert _get_words(cli, [], "") == ["x", "y"]
- assert _get_words(cli, [], "-") == ["-a", "--help"]
- def test_group_command_same_option():
- cli = Group(
- "cli", params=[Option(["-a"])], commands=[Command("x", params=[Option(["-a"])])]
- )
- assert _get_words(cli, [], "-") == ["-a", "--help"]
- assert _get_words(cli, ["-a", "a"], "-") == ["--help"]
- assert _get_words(cli, ["-a", "a", "x"], "-") == ["-a", "--help"]
- assert _get_words(cli, ["-a", "a", "x", "-a", "a"], "-") == ["--help"]
- def test_chained():
- cli = Group(
- "cli",
- chain=True,
- commands=[
- Command("set", params=[Option(["-y"])]),
- Command("start"),
- Group("get", commands=[Command("full")]),
- ],
- )
- assert _get_words(cli, [], "") == ["get", "set", "start"]
- assert _get_words(cli, [], "s") == ["set", "start"]
- assert _get_words(cli, ["set", "start"], "") == ["get"]
- # subcommands and parent subcommands
- assert _get_words(cli, ["get"], "") == ["full", "set", "start"]
- assert _get_words(cli, ["get", "full"], "") == ["set", "start"]
- assert _get_words(cli, ["get"], "s") == ["set", "start"]
- def test_help_option():
- cli = Group("cli", commands=[Command("with"), Command("no", add_help_option=False)])
- assert _get_words(cli, ["with"], "--") == ["--help"]
- assert _get_words(cli, ["no"], "--") == []
- def test_argument_order():
- cli = Command(
- "cli",
- params=[
- Argument(["plain"]),
- Argument(["c1"], type=Choice(["a1", "a2", "b"])),
- Argument(["c2"], type=Choice(["c1", "c2", "d"])),
- ],
- )
- # first argument has no completions
- assert _get_words(cli, [], "") == []
- assert _get_words(cli, [], "a") == []
- # first argument filled, now completion can happen
- assert _get_words(cli, ["x"], "a") == ["a1", "a2"]
- assert _get_words(cli, ["x", "b"], "d") == ["d"]
- def test_argument_default():
- cli = Command(
- "cli",
- add_help_option=False,
- params=[
- Argument(["a"], type=Choice(["a"]), default="a"),
- Argument(["b"], type=Choice(["b"]), default="b"),
- ],
- )
- assert _get_words(cli, [], "") == ["a"]
- assert _get_words(cli, ["a"], "b") == ["b"]
- # ignore type validation
- assert _get_words(cli, ["x"], "b") == ["b"]
- def test_type_choice():
- cli = Command("cli", params=[Option(["-c"], type=Choice(["a1", "a2", "b"]))])
- assert _get_words(cli, ["-c"], "") == ["a1", "a2", "b"]
- assert _get_words(cli, ["-c"], "a") == ["a1", "a2"]
- assert _get_words(cli, ["-c"], "a2") == ["a2"]
- def test_choice_special_characters():
- cli = Command("cli", params=[Option(["-c"], type=Choice(["!1", "!2", "+3"]))])
- assert _get_words(cli, ["-c"], "") == ["!1", "!2", "+3"]
- assert _get_words(cli, ["-c"], "!") == ["!1", "!2"]
- assert _get_words(cli, ["-c"], "!2") == ["!2"]
- def test_choice_conflicting_prefix():
- cli = Command(
- "cli",
- params=[
- Option(["-c"], type=Choice(["!1", "!2", "+3"])),
- Option(["+p"], is_flag=True),
- ],
- )
- assert _get_words(cli, ["-c"], "") == ["!1", "!2", "+3"]
- assert _get_words(cli, ["-c"], "+") == ["+p"]
- def test_option_count():
- cli = Command("cli", params=[Option(["-c"], count=True)])
- assert _get_words(cli, ["-c"], "") == []
- assert _get_words(cli, ["-c"], "-") == ["--help"]
- def test_option_optional():
- cli = Command(
- "cli",
- add_help_option=False,
- params=[
- Option(["--name"], is_flag=False, flag_value="value"),
- Option(["--flag"], is_flag=True),
- ],
- )
- assert _get_words(cli, ["--name"], "") == []
- assert _get_words(cli, ["--name"], "-") == ["--flag"]
- assert _get_words(cli, ["--name", "--flag"], "-") == []
- @pytest.mark.parametrize(
- ("type", "expect"),
- [(File(), "file"), (Path(), "file"), (Path(file_okay=False), "dir")],
- )
- def test_path_types(type, expect):
- cli = Command("cli", params=[Option(["-f"], type=type)])
- out = _get_completions(cli, ["-f"], "ab")
- assert len(out) == 1
- c = out[0]
- assert c.value == "ab"
- assert c.type == expect
- def test_absolute_path():
- cli = Command("cli", params=[Option(["-f"], type=Path())])
- out = _get_completions(cli, ["-f"], "/ab")
- assert len(out) == 1
- c = out[0]
- assert c.value == "/ab"
- def test_option_flag():
- cli = Command(
- "cli",
- add_help_option=False,
- params=[
- Option(["--on/--off"]),
- Argument(["a"], type=Choice(["a1", "a2", "b"])),
- ],
- )
- assert _get_words(cli, [], "--") == ["--on", "--off"]
- # flag option doesn't take value, use choice argument
- assert _get_words(cli, ["--on"], "a") == ["a1", "a2"]
- def test_option_custom():
- def custom(ctx, param, incomplete):
- return [incomplete.upper()]
- cli = Command(
- "cli",
- params=[
- Argument(["x"]),
- Argument(["y"]),
- Argument(["z"], shell_complete=custom),
- ],
- )
- assert _get_words(cli, ["a", "b"], "") == [""]
- assert _get_words(cli, ["a", "b"], "c") == ["C"]
- def test_option_multiple():
- cli = Command(
- "type",
- params=[Option(["-m"], type=Choice(["a", "b"]), multiple=True), Option(["-f"])],
- )
- assert _get_words(cli, ["-m"], "") == ["a", "b"]
- assert "-m" in _get_words(cli, ["-m", "a"], "-")
- assert _get_words(cli, ["-m", "a", "-m"], "") == ["a", "b"]
- # used single options aren't suggested again
- assert "-c" not in _get_words(cli, ["-c", "f"], "-")
- def test_option_nargs():
- cli = Command("cli", params=[Option(["-c"], type=Choice(["a", "b"]), nargs=2)])
- assert _get_words(cli, ["-c"], "") == ["a", "b"]
- assert _get_words(cli, ["-c", "a"], "") == ["a", "b"]
- assert _get_words(cli, ["-c", "a", "b"], "") == []
- def test_argument_nargs():
- cli = Command(
- "cli",
- params=[
- Argument(["x"], type=Choice(["a", "b"]), nargs=2),
- Argument(["y"], type=Choice(["c", "d"]), nargs=-1),
- Option(["-z"]),
- ],
- )
- assert _get_words(cli, [], "") == ["a", "b"]
- assert _get_words(cli, ["a"], "") == ["a", "b"]
- assert _get_words(cli, ["a", "b"], "") == ["c", "d"]
- assert _get_words(cli, ["a", "b", "c"], "") == ["c", "d"]
- assert _get_words(cli, ["a", "b", "c", "d"], "") == ["c", "d"]
- assert _get_words(cli, ["a", "-z", "1"], "") == ["a", "b"]
- assert _get_words(cli, ["a", "-z", "1", "b"], "") == ["c", "d"]
- def test_double_dash():
- cli = Command(
- "cli",
- add_help_option=False,
- params=[
- Option(["--opt"]),
- Argument(["name"], type=Choice(["name", "--", "-o", "--opt"])),
- ],
- )
- assert _get_words(cli, [], "-") == ["--opt"]
- assert _get_words(cli, ["value"], "-") == ["--opt"]
- assert _get_words(cli, [], "") == ["name", "--", "-o", "--opt"]
- assert _get_words(cli, ["--"], "") == ["name", "--", "-o", "--opt"]
- def test_hidden():
- cli = Group(
- "cli",
- commands=[
- Command(
- "hidden",
- add_help_option=False,
- hidden=True,
- params=[
- Option(["-a"]),
- Option(["-b"], type=Choice(["a", "b"]), hidden=True),
- ],
- )
- ],
- )
- assert "hidden" not in _get_words(cli, [], "")
- assert "hidden" not in _get_words(cli, [], "hidden")
- assert _get_words(cli, ["hidden"], "-") == ["-a"]
- assert _get_words(cli, ["hidden", "-b"], "") == ["a", "b"]
- def test_add_different_name():
- cli = Group("cli", commands={"renamed": Command("original")})
- words = _get_words(cli, [], "")
- assert "renamed" in words
- assert "original" not in words
- def test_completion_item_data():
- c = CompletionItem("test", a=1)
- assert c.a == 1
- assert c.b is None
- @pytest.fixture()
- def _patch_for_completion(monkeypatch):
- monkeypatch.setattr(
- "click.shell_completion.BashComplete._check_version", lambda self: True
- )
- @pytest.mark.parametrize("shell", ["bash", "zsh", "fish"])
- @pytest.mark.usefixtures("_patch_for_completion")
- def test_full_source(runner, shell):
- cli = Group("cli", commands=[Command("a"), Command("b")])
- result = runner.invoke(cli, env={"_CLI_COMPLETE": f"{shell}_source"})
- assert f"_CLI_COMPLETE={shell}_complete" in result.output
- @pytest.mark.parametrize(
- ("shell", "env", "expect"),
- [
- ("bash", {"COMP_WORDS": "", "COMP_CWORD": "0"}, "plain,a\nplain,b\n"),
- ("bash", {"COMP_WORDS": "a b", "COMP_CWORD": "1"}, "plain,b\n"),
- ("zsh", {"COMP_WORDS": "", "COMP_CWORD": "0"}, "plain\na\n_\nplain\nb\nbee\n"),
- ("zsh", {"COMP_WORDS": "a b", "COMP_CWORD": "1"}, "plain\nb\nbee\n"),
- ("fish", {"COMP_WORDS": "", "COMP_CWORD": ""}, "plain,a\nplain,b\tbee\n"),
- ("fish", {"COMP_WORDS": "a b", "COMP_CWORD": "b"}, "plain,b\tbee\n"),
- ],
- )
- @pytest.mark.usefixtures("_patch_for_completion")
- def test_full_complete(runner, shell, env, expect):
- cli = Group("cli", commands=[Command("a"), Command("b", help="bee")])
- env["_CLI_COMPLETE"] = f"{shell}_complete"
- result = runner.invoke(cli, env=env)
- assert result.output == expect
- @pytest.mark.usefixtures("_patch_for_completion")
- def test_context_settings(runner):
- def complete(ctx, param, incomplete):
- return ctx.obj["choices"]
- cli = Command("cli", params=[Argument("x", shell_complete=complete)])
- result = runner.invoke(
- cli,
- obj={"choices": ["a", "b"]},
- env={"COMP_WORDS": "", "COMP_CWORD": "0", "_CLI_COMPLETE": "bash_complete"},
- )
- assert result.output == "plain,a\nplain,b\n"
- @pytest.mark.parametrize(("value", "expect"), [(False, ["Au", "al"]), (True, ["al"])])
- def test_choice_case_sensitive(value, expect):
- cli = Command(
- "cli",
- params=[Option(["-a"], type=Choice(["Au", "al", "Bc"], case_sensitive=value))],
- )
- completions = _get_words(cli, ["-a"], "a")
- assert completions == expect
- @pytest.fixture()
- def _restore_available_shells(tmpdir):
- prev_available_shells = click.shell_completion._available_shells.copy()
- click.shell_completion._available_shells.clear()
- yield
- click.shell_completion._available_shells.clear()
- click.shell_completion._available_shells.update(prev_available_shells)
- @pytest.mark.usefixtures("_restore_available_shells")
- def test_add_completion_class():
- # At first, "mysh" is not in available shells
- assert "mysh" not in click.shell_completion._available_shells
- class MyshComplete(ShellComplete):
- name = "mysh"
- source_template = "dummy source"
- # "mysh" still not in available shells because it is not registered
- assert "mysh" not in click.shell_completion._available_shells
- # Adding a completion class should return that class
- assert add_completion_class(MyshComplete) is MyshComplete
- # Now, "mysh" is finally in available shells
- assert "mysh" in click.shell_completion._available_shells
- assert click.shell_completion._available_shells["mysh"] is MyshComplete
- @pytest.mark.usefixtures("_restore_available_shells")
- def test_add_completion_class_with_name():
- # At first, "mysh" is not in available shells
- assert "mysh" not in click.shell_completion._available_shells
- assert "not_mysh" not in click.shell_completion._available_shells
- class MyshComplete(ShellComplete):
- name = "not_mysh"
- source_template = "dummy source"
- # "mysh" and "not_mysh" are still not in available shells because
- # it is not registered yet
- assert "mysh" not in click.shell_completion._available_shells
- assert "not_mysh" not in click.shell_completion._available_shells
- # Adding a completion class should return that class.
- # Because we are using the "name" parameter, the name isn't taken
- # from the class.
- assert add_completion_class(MyshComplete, name="mysh") is MyshComplete
- # Now, "mysh" is finally in available shells
- assert "mysh" in click.shell_completion._available_shells
- assert "not_mysh" not in click.shell_completion._available_shells
- assert click.shell_completion._available_shells["mysh"] is MyshComplete
- @pytest.mark.usefixtures("_restore_available_shells")
- def test_add_completion_class_decorator():
- # At first, "mysh" is not in available shells
- assert "mysh" not in click.shell_completion._available_shells
- @add_completion_class
- class MyshComplete(ShellComplete):
- name = "mysh"
- source_template = "dummy source"
- # Using `add_completion_class` as a decorator adds the new shell immediately
- assert "mysh" in click.shell_completion._available_shells
- assert click.shell_completion._available_shells["mysh"] is MyshComplete
|