diff options
-rw-r--r-- | CHANGES.rst | 6 | ||||
-rw-r--r-- | docs/shell-completion.rst | 7 | ||||
-rw-r--r-- | examples/bashcompletion/README | 12 | ||||
-rw-r--r-- | examples/bashcompletion/bashcompletion.py | 45 | ||||
-rw-r--r-- | examples/completion/README | 28 | ||||
-rw-r--r-- | examples/completion/completion.py | 53 | ||||
-rw-r--r-- | examples/completion/setup.py (renamed from examples/bashcompletion/setup.py) | 6 | ||||
-rw-r--r-- | src/click/core.py | 65 | ||||
-rw-r--r-- | src/click/types.py | 12 | ||||
-rw-r--r-- | tests/test_shell_completion.py | 17 |
10 files changed, 166 insertions, 85 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 54bcefe..e6d5c23 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -98,6 +98,12 @@ Unreleased completions suggestions. - Groups complete the names commands were registered with, which can differ from the name they were created with. + - The ``autocompletion`` parameter for options and arguments is + renamed to ``shell_complete``. The function must take four + parameters ``ctx, param, args, incomplete``, must do matching + rather than return all values, and must return a list of strings + or a list of ``ShellComplete``. The old name and behavior is + deprecated and will be removed in 8.1. Version 7.1.2 diff --git a/docs/shell-completion.rst b/docs/shell-completion.rst index 739a7c7..a02c3be 100644 --- a/docs/shell-completion.rst +++ b/docs/shell-completion.rst @@ -148,11 +148,12 @@ Overriding Value Completion --------------------------- Value completions for a parameter can be customized without a custom -type by providing an ``autocompletion`` function. The function is used +type by providing a ``shell_complete`` function. The function is used instead of any completion provided by the type. It is passed 3 keyword arguments: - ``ctx`` - The current command context. +- ``param`` - The current parameter requesting completion. - ``args`` - The list of complete args before the incomplete value. - ``incomplete`` - The partial word that is being completed. May be an empty string if no characters have been entered yet. @@ -165,11 +166,11 @@ start with the incomplete value. .. code-block:: python - def complete_env_vars(ctx, args, incomplete): + def complete_env_vars(ctx, param, args, incomplete): return [k for k in os.environ if k.startswith(incomplete)] @click.command() - @click.argument("name", autocompletion=complete_env_vars) + @click.argument("name", shell_complete=complete_env_vars) def cli(name): click.echo(f"Name: {name}") click.echo(f"Value: {os.environ[name]}") diff --git a/examples/bashcompletion/README b/examples/bashcompletion/README deleted file mode 100644 index f8a0d51..0000000 --- a/examples/bashcompletion/README +++ /dev/null @@ -1,12 +0,0 @@ -$ bashcompletion - - bashcompletion is a simple example of an application that - tries to autocomplete commands, arguments and options. - - This example requires Click 2.0 or higher. - -Usage: - - $ pip install --editable . - $ eval "$(_BASHCOMPLETION_COMPLETE=source bashcompletion)" - $ bashcompletion --help diff --git a/examples/bashcompletion/bashcompletion.py b/examples/bashcompletion/bashcompletion.py deleted file mode 100644 index 3f8c9df..0000000 --- a/examples/bashcompletion/bashcompletion.py +++ /dev/null @@ -1,45 +0,0 @@ -import os - -import click - - -@click.group() -def cli(): - pass - - -def get_env_vars(ctx, args, incomplete): - # Completions returned as strings do not have a description displayed. - for key in os.environ.keys(): - if incomplete in key: - yield key - - -@cli.command(help="A command to print environment variables") -@click.argument("envvar", type=click.STRING, autocompletion=get_env_vars) -def cmd1(envvar): - click.echo(f"Environment variable: {envvar}") - click.echo(f"Value: {os.environ[envvar]}") - - -@click.group(help="A group that holds a subcommand") -def group(): - pass - - -def list_users(ctx, args, incomplete): - # You can generate completions with descriptions by returning - # tuples in the form (completion, description). - users = [("bob", "butcher"), ("alice", "baker"), ("jerry", "candlestick maker")] - # Ths will allow completion matches based on matches within the - # description string too! - return [user for user in users if incomplete in user[0] or incomplete in user[1]] - - -@group.command(help="Choose a user") -@click.argument("user", type=click.STRING, autocompletion=list_users) -def subcmd(user): - click.echo(f"Chosen user is {user}") - - -cli.add_command(group) diff --git a/examples/completion/README b/examples/completion/README new file mode 100644 index 0000000..f15654e --- /dev/null +++ b/examples/completion/README @@ -0,0 +1,28 @@ +$ completion +============ + +Demonstrates Click's shell completion support. + +.. code-block:: bash + + pip install --editable . + +For Bash: + +.. code-block:: bash + + eval "$(_COMPLETION_COMPLETE=source_bash completion)" + +For Zsh: + +.. code-block:: zsh + + eval "$(_COMPLETION_COMPLETE=source_zsh completion)" + +For Fish: + +.. code-block:: fish + + eval (env _COMPLETION_COMPLETE=source_fish completion) + +Now press tab (maybe twice) after typing something to see completions. diff --git a/examples/completion/completion.py b/examples/completion/completion.py new file mode 100644 index 0000000..92dcc74 --- /dev/null +++ b/examples/completion/completion.py @@ -0,0 +1,53 @@ +import os + +import click +from click.shell_completion import CompletionItem + + +@click.group() +def cli(): + pass + + +@cli.command() +@click.option("--dir", type=click.Path(file_okay=False)) +def ls(dir): + click.echo("\n".join(os.listdir(dir))) + + +def get_env_vars(ctx, param, args, incomplete): + # Returning a list of values is a shortcut to returning a list of + # CompletionItem(value). + return [k for k in os.environ if incomplete in k] + + +@cli.command(help="A command to print environment variables") +@click.argument("envvar", shell_complete=get_env_vars) +def show_env(envvar): + click.echo(f"Environment variable: {envvar}") + click.echo(f"Value: {os.environ[envvar]}") + + +@cli.group(help="A group that holds a subcommand") +def group(): + pass + + +def list_users(ctx, args, incomplete): + # You can generate completions with help strings by returning a list + # of CompletionItem. You can match on whatever you want, including + # the help. + items = [("bob", "butcher"), ("alice", "baker"), ("jerry", "candlestick maker")] + + for value, help in items: + if incomplete in value or incomplete in help: + yield CompletionItem(value, help=help) + + +@group.command(help="Choose a user") +@click.argument("user", type=click.STRING, autocompletion=list_users) +def select_user(user): + click.echo(f"Chosen user is {user}") + + +cli.add_command(group) diff --git a/examples/bashcompletion/setup.py b/examples/completion/setup.py index f9a2c29..a78d140 100644 --- a/examples/bashcompletion/setup.py +++ b/examples/completion/setup.py @@ -1,13 +1,13 @@ from setuptools import setup setup( - name="click-example-bashcompletion", + name="click-example-completion", version="1.0", - py_modules=["bashcompletion"], + py_modules=["completion"], include_package_data=True, install_requires=["click"], entry_points=""" [console_scripts] - bashcompletion=bashcompletion:cli + completion=completion:cli """, ) diff --git a/src/click/core.py b/src/click/core.py index 6e45beb..cccf939 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -1781,9 +1781,17 @@ class Parameter: order of processing. :param envvar: a string or list of strings that are environment variables that should be checked. - :param autocompletion: A function that returns custom shell + :param shell_complete: A function that returns custom shell completions. Used instead of the param's type completion if - given. + given. Takes ``ctx, param, args, incomplete`` and returns a list + of :class:`~click.shell_completion.CompletionItem` or a list of + strings. + + .. versionchanged:: 8.0 + ``autocompletion`` is renamed to ``shell_complete`` and has new + semantics described in the docs above. The old name is + deprecated and will be removed in 8.1, until then it will be + wrapped to match the new requirements. .. versionchanged:: 7.1 Empty environment variables are ignored rather than taking the @@ -1795,6 +1803,7 @@ class Parameter: parameter. The old callback format will still work, but it will raise a warning to give you a chance to migrate the code easier. """ + param_type_name = "parameter" def __init__( @@ -1809,6 +1818,7 @@ class Parameter: expose_value=True, is_eager=False, envvar=None, + shell_complete=None, autocompletion=None, ): self.name, self.opts, self.secondary_opts = self._parse_decls( @@ -1834,7 +1844,35 @@ class Parameter: self.is_eager = is_eager self.metavar = metavar self.envvar = envvar - self.autocompletion = autocompletion + + if autocompletion is not None: + import warnings + + warnings.warn( + "'autocompletion' is renamed to 'shell_complete'. The old name is" + " deprecated and will be removed in Click 8.1. See the docs about" + " 'Parameter' for information about new behavior.", + DeprecationWarning, + stacklevel=2, + ) + + def shell_complete(ctx, param, args, incomplete): + from click.shell_completion import CompletionItem + + out = [] + + for c in autocompletion(ctx, args, incomplete): + if isinstance(c, tuple): + c = CompletionItem(c[0], help=c[1]) + elif isinstance(c, str): + c = CompletionItem(c) + + if c.value.startswith(incomplete): + out.append(c) + + return out + + self._custom_shell_complete = shell_complete def to_info_dict(self): """Gather information that could be useful for a tool generating @@ -2025,8 +2063,8 @@ class Parameter: return " / ".join(repr(x) for x in hint_list) def shell_complete(self, ctx, args, incomplete): - """Return a list of completions for the incomplete value. If an - :attr:`autocompletion` function was given, it is used. + """Return a list of completions for the incomplete value. If a + ``shell_complete`` function was given during init, it is used. Otherwise, the :attr:`type` :meth:`~click.types.ParamType.shell_complete` function is used. @@ -2036,22 +2074,17 @@ class Parameter: .. versionadded:: 8.0 """ - from click.shell_completion import CompletionItem + if self._custom_shell_complete is not None: + results = self._custom_shell_complete(ctx, self, args, incomplete) - if self.autocompletion is not None: - results = [] + if results and isinstance(results[0], str): + from click.shell_completion import CompletionItem - for c in self.autocompletion(ctx=ctx, args=args, incomplete=incomplete): - if isinstance(c, CompletionItem): - results.append(c) - elif isinstance(c, tuple): - results.append(CompletionItem(c[0], help=c[1])) - else: - results.append(CompletionItem(c)) + results = [CompletionItem(c) for c in results] return results - return self.type.shell_complete(ctx, args, incomplete) + return self.type.shell_complete(ctx, self, args, incomplete) class Option(Parameter): diff --git a/src/click/types.py b/src/click/types.py index 4cedc27..f26b8b5 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -107,7 +107,7 @@ class ParamType: """Helper method to fail with an invalid value message.""" raise BadParameter(message, ctx=ctx, param=param) - def shell_complete(self, ctx, args, incomplete): + def shell_complete(self, ctx, param, args, incomplete): """Return a list of :class:`~click.shell_completion.CompletionItem` objects for the incomplete value. Most types do not provide completions, but @@ -115,6 +115,7 @@ class ParamType: completions as well. :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. :param args: List of complete args before the incomplete value. :param incomplete: Value being completed. May be empty. @@ -264,10 +265,11 @@ class Choice(ParamType): def __repr__(self): return f"Choice({list(self.choices)})" - def shell_complete(self, ctx, args, incomplete): + def shell_complete(self, ctx, param, args, incomplete): """Complete choices that start with the incomplete value. :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. :param args: List of complete args before the incomplete value. :param incomplete: Value being completed. May be empty. @@ -612,11 +614,12 @@ class File(ParamType): ctx, ) - def shell_complete(self, ctx, args, incomplete): + def shell_complete(self, ctx, param, args, incomplete): """Return a special completion marker that tells the completion system to use the shell to provide file path completions. :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. :param args: List of complete args before the incomplete value. :param incomplete: Value being completed. May be empty. @@ -757,12 +760,13 @@ class Path(ParamType): return self.coerce_path_result(rv) - def shell_complete(self, ctx, args, incomplete): + def shell_complete(self, ctx, param, args, incomplete): """Return a special completion marker that tells the completion system to use the shell to provide path completions for only directories or any paths. :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. :param args: List of complete args before the incomplete value. :param incomplete: Value being completed. May be empty. diff --git a/tests/test_shell_completion.py b/tests/test_shell_completion.py index e40d08a..e2d865c 100644 --- a/tests/test_shell_completion.py +++ b/tests/test_shell_completion.py @@ -114,7 +114,7 @@ def test_option_flag(): def test_option_custom(): - def custom(ctx, args, incomplete): + def custom(ctx, param, args, incomplete): return [incomplete.upper()] cli = Command( @@ -122,13 +122,26 @@ def test_option_custom(): params=[ Argument(["x"]), Argument(["y"]), - Argument(["z"], autocompletion=custom), + Argument(["z"], shell_complete=custom), ], ) assert _get_words(cli, ["a", "b"], "") == [""] assert _get_words(cli, ["a", "b"], "c") == ["C"] +def test_autocompletion_deprecated(): + # old function takes three params, returns all values, can mix + # strings and tuples + def custom(ctx, args, incomplete): + return [("art", "x"), "bat", "cat"] + + with pytest.deprecated_call(): + cli = Command("cli", params=[Argument(["x"], autocompletion=custom)]) + + assert _get_words(cli, [], "") == ["art", "bat", "cat"] + assert _get_words(cli, [], "c") == ["cat"] + + def test_option_multiple(): cli = Command( "type", |