Skip to content

Commit

Permalink
don't pass all args to shell_complete methods
Browse files Browse the repository at this point in the history
  • Loading branch information
davidism committed Oct 3, 2020
1 parent 3faede8 commit 1b9a657
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 49 deletions.
10 changes: 5 additions & 5 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ Unreleased
- 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.
renamed to ``shell_complete``. The function must take
``ctx, param, 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
Expand Down
5 changes: 2 additions & 3 deletions docs/shell-completion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ with the incomplete value.
.. code-block:: python
class EnvVarType(ParamType):
def shell_complete(self, ctx, args, incomplete):
def shell_complete(self, ctx, param, incomplete):
return [
CompletionItem(name)
for name in os.environ if name.startswith(incomplete)
Expand All @@ -154,7 +154,6 @@ 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.

Expand All @@ -166,7 +165,7 @@ start with the incomplete value.

.. code-block:: python
def complete_env_vars(ctx, param, args, incomplete):
def complete_env_vars(ctx, param, incomplete):
return [k for k in os.environ if k.startswith(incomplete)]
@click.command()
Expand Down
6 changes: 3 additions & 3 deletions examples/completion/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def ls(dir):
click.echo("\n".join(os.listdir(dir)))


def get_env_vars(ctx, param, args, incomplete):
def get_env_vars(ctx, param, 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]
Expand All @@ -33,7 +33,7 @@ def group():
pass


def list_users(ctx, args, incomplete):
def list_users(ctx, param, incomplete):
# You can generate completions with help strings by returning a list
# of CompletionItem. You can match on whatever you want, including
# the help.
Expand All @@ -45,7 +45,7 @@ def list_users(ctx, args, incomplete):


@group.command(help="Choose a user")
@click.argument("user", type=click.STRING, autocompletion=list_users)
@click.argument("user", shell_complete=list_users)
def select_user(user):
click.echo(f"Chosen user is {user}")

Expand Down
55 changes: 30 additions & 25 deletions src/click/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,13 @@ def __init__(
self.command = command
#: the descriptive information name
self.info_name = info_name
#: the parsed parameters except if the value is hidden in which
#: case it's not remembered.
#: Map of parameter names to their parsed values. Parameters
#: with ``expose_value=False`` are not stored.
self.params = {}
# This tracks the actual param objects that were parsed, even if
# they didn't expose a value. Used by completion system to know
# what parameters to exclude.
self._seen_params = set()
#: the leftover arguments.
self.args = []
#: protected arguments. These are arguments that are prepended
Expand Down Expand Up @@ -864,7 +868,7 @@ def invoke(self, ctx):
"""
raise NotImplementedError("Base commands are not invokable by default")

def shell_complete(self, ctx, args, incomplete):
def shell_complete(self, ctx, incomplete):
"""Return a list of completions for the incomplete value. Looks
at the names of chained multi-commands.
Expand All @@ -873,7 +877,6 @@ def shell_complete(self, ctx, args, incomplete):
command classes will return more completions.
:param ctx: Invocation context for this command.
:param args: List of complete args before the incomplete value.
:param incomplete: Value being completed. May be empty.
.. versionadded:: 8.0
Expand Down Expand Up @@ -1278,12 +1281,11 @@ def invoke(self, ctx):
if self.callback is not None:
return ctx.invoke(self.callback, **ctx.params)

def shell_complete(self, ctx, args, incomplete):
def shell_complete(self, ctx, incomplete):
"""Return a list of completions for the incomplete value. Looks
at the names of options and chained multi-commands.
:param ctx: Invocation context for this command.
:param args: List of complete args before the incomplete value.
:param incomplete: Value being completed. May be empty.
.. versionadded:: 8.0
Expand All @@ -1294,19 +1296,20 @@ def shell_complete(self, ctx, args, incomplete):

if incomplete and not incomplete[0].isalnum():
for param in self.get_params(ctx):
if not isinstance(param, Option) or param.hidden:
if (
not isinstance(param, Option)
or param.hidden
or (not param.multiple and param in ctx._seen_params)
):
continue

results.extend(
CompletionItem(name, help=param.help)
for name in param.opts + param.secondary_opts
if (
(name not in args or param.multiple)
and name.startswith(incomplete)
)
if name.startswith(incomplete)
)

results.extend(super().shell_complete(ctx, args, incomplete))
results.extend(super().shell_complete(ctx, incomplete))
return results


Expand Down Expand Up @@ -1580,13 +1583,12 @@ def list_commands(self, ctx):
"""
return []

def shell_complete(self, ctx, args, incomplete):
def shell_complete(self, ctx, incomplete):
"""Return a list of completions for the incomplete value. Looks
at the names of options, subcommands, and chained
multi-commands.
:param ctx: Invocation context for this command.
:param args: List of complete args before the incomplete value.
:param incomplete: Value being completed. May be empty.
.. versionadded:: 8.0
Expand All @@ -1597,7 +1599,7 @@ def shell_complete(self, ctx, args, incomplete):
CompletionItem(name, help=command.get_short_help_str())
for name, command in _complete_visible_commands(ctx, incomplete)
]
results.extend(super().shell_complete(ctx, args, incomplete))
results.extend(super().shell_complete(ctx, incomplete))
return results


Expand Down Expand Up @@ -1783,15 +1785,15 @@ class Parameter:
that should be checked.
:param shell_complete: A function that returns custom shell
completions. Used instead of the param's type completion if
given. Takes ``ctx, param, args, incomplete`` and returns a list
given. Takes ``ctx, param, incomplete`` and must return 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.
semantics described 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
Expand Down Expand Up @@ -1856,12 +1858,12 @@ def __init__(
stacklevel=2,
)

def shell_complete(ctx, param, args, incomplete):
def shell_complete(ctx, param, incomplete):
from click.shell_completion import CompletionItem

out = []

for c in autocompletion(ctx, args, incomplete):
for c in autocompletion(ctx, [], incomplete):
if isinstance(c, tuple):
c = CompletionItem(c[0], help=c[1])
elif isinstance(c, str):
Expand Down Expand Up @@ -2047,6 +2049,10 @@ def handle_parse_result(self, ctx, opts, args):

if self.expose_value:
ctx.params[self.name] = value

if value is not None:
ctx._seen_params.add(self)

return value, args

def get_help_record(self, ctx):
Expand All @@ -2062,20 +2068,19 @@ def get_error_hint(self, ctx):
hint_list = self.opts or [self.human_readable_name]
return " / ".join(repr(x) for x in hint_list)

def shell_complete(self, ctx, args, incomplete):
def shell_complete(self, ctx, incomplete):
"""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.
:param ctx: Invocation context for this command.
:param args: List of complete args before the incomplete value.
:param incomplete: Value being completed. May be empty.
.. versionadded:: 8.0
"""
if self._custom_shell_complete is not None:
results = self._custom_shell_complete(ctx, self, args, incomplete)
results = self._custom_shell_complete(ctx, self, incomplete)

if results and isinstance(results[0], str):
from click.shell_completion import CompletionItem
Expand All @@ -2084,7 +2089,7 @@ def shell_complete(self, ctx, args, incomplete):

return results

return self.type.shell_complete(ctx, self, args, incomplete)
return self.type.shell_complete(ctx, self, incomplete)


class Option(Parameter):
Expand Down
4 changes: 2 additions & 2 deletions src/click/shell_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def get_completions(self, args, incomplete):
return []

obj, incomplete = _resolve_incomplete(ctx, args, incomplete)
return obj.shell_complete(ctx, args, incomplete)
return obj.shell_complete(ctx, incomplete)

def format_completion(self, item):
"""Format a completion item into the form recognized by the
Expand Down Expand Up @@ -443,7 +443,7 @@ def _is_incomplete_option(args, param):
if _start_of_option(arg):
last_option = arg

return bool(last_option and last_option in param.opts)
return last_option is not None and last_option in param.opts


def _resolve_context(cli, prog_name, args):
Expand Down
12 changes: 4 additions & 8 deletions src/click/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def fail(self, message, param=None, ctx=None):
"""Helper method to fail with an invalid value message."""
raise BadParameter(message, ctx=ctx, param=param)

def shell_complete(self, ctx, param, args, incomplete):
def shell_complete(self, ctx, param, incomplete):
"""Return a list of
:class:`~click.shell_completion.CompletionItem` objects for the
incomplete value. Most types do not provide completions, but
Expand All @@ -116,7 +116,6 @@ def shell_complete(self, ctx, param, args, incomplete):
: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.
.. versionadded:: 8.0
Expand Down Expand Up @@ -265,12 +264,11 @@ def convert(self, value, param, ctx):
def __repr__(self):
return f"Choice({list(self.choices)})"

def shell_complete(self, ctx, param, args, incomplete):
def shell_complete(self, ctx, param, 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.
.. versionadded:: 8.0
Expand Down Expand Up @@ -614,13 +612,12 @@ def convert(self, value, param, ctx):
ctx,
)

def shell_complete(self, ctx, param, args, incomplete):
def shell_complete(self, ctx, param, 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.
.. versionadded:: 8.0
Expand Down Expand Up @@ -760,14 +757,13 @@ def convert(self, value, param, ctx):

return self.coerce_path_result(rv)

def shell_complete(self, ctx, param, args, incomplete):
def shell_complete(self, ctx, param, 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.
.. versionadded:: 8.0
Expand Down
18 changes: 15 additions & 3 deletions tests/test_shell_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ def test_command():
assert _get_words(cli, [], "-") == ["-t", "--test", "--help"]
assert _get_words(cli, [], "--") == ["--test", "--help"]
assert _get_words(cli, [], "--t") == ["--test"]
assert _get_words(cli, ["-t", "a"], "-") == ["--test", "--help"]
# -t has been seen, so --test isn't suggested
assert _get_words(cli, ["-t", "a"], "-") == ["--help"]


def test_group():
Expand All @@ -37,6 +38,16 @@ def test_group():
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",
Expand Down Expand Up @@ -114,7 +125,7 @@ def test_option_flag():


def test_option_custom():
def custom(ctx, param, args, incomplete):
def custom(ctx, param, incomplete):
return [incomplete.upper()]

cli = Command(
Expand All @@ -130,9 +141,10 @@ def custom(ctx, param, args, incomplete):


def test_autocompletion_deprecated():
# old function takes three params, returns all values, can mix
# old function takes args and not param, returns all values, can mix
# strings and tuples
def custom(ctx, args, incomplete):
assert isinstance(args, list)
return [("art", "x"), "bat", "cat"]

with pytest.deprecated_call():
Expand Down

0 comments on commit 1b9a657

Please sign in to comment.