From 8981a95d4032fb752ffd0b87059d9048e8c996f1 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 3 Oct 2020 14:35:08 -0700 Subject: [PATCH] pass extra context settings to completion --- CHANGES.rst | 5 ++++- src/click/core.py | 6 +++--- src/click/shell_completion.py | 16 ++++++++++------ tests/test_shell_completion.py | 16 +++++++++++++++- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7f5bb5faf..f7dd40577 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,7 +12,6 @@ Unreleased parameter. :issue:`1264`, :pr:`1329` - Add an optional parameter to ``ProgressBar.update`` to set the ``current_item``. :issue:`1226`, :pr:`1332` -- Include ``--help`` option in completion. :pr:`1504` - ``version_option`` uses ``importlib.metadata`` (or the ``importlib_metadata`` backport) instead of ``pkg_resources``. :issue:`1582` @@ -105,6 +104,10 @@ Unreleased ``ShellComplete``. The old name and behavior is deprecated and will be removed in 8.1. +- Extra context settings (``obj=...``, etc.) are passed on to the + completion system. :issue:`942` +- Include ``--help`` option in completion. :pr:`1504` + Version 7.1.2 ------------- diff --git a/src/click/core.py b/src/click/core.py index b2ccaa85b..3a24f44ed 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -950,7 +950,7 @@ def main( prog_name = _detect_program_name() # Process shell completion requests and exit early. - self._main_shell_completion(prog_name, complete_var) + self._main_shell_completion(extra, prog_name, complete_var) try: try: @@ -1000,7 +1000,7 @@ def main( echo("Aborted!", file=sys.stderr) sys.exit(1) - def _main_shell_completion(self, prog_name, complete_var=None): + def _main_shell_completion(self, ctx_args, prog_name, complete_var=None): """Check if the shell is asking for tab completion, process that, then exit early. Called from :meth:`main` before the program is invoked. @@ -1020,7 +1020,7 @@ def _main_shell_completion(self, prog_name, complete_var=None): from .shell_completion import shell_complete - rv = shell_complete(self, prog_name, complete_var, instruction) + rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) _fast_exit(rv) def __call__(self, *args, **kwargs): diff --git a/src/click/shell_completion.py b/src/click/shell_completion.py index 2ce3eb928..c9664ad07 100644 --- a/src/click/shell_completion.py +++ b/src/click/shell_completion.py @@ -8,10 +8,12 @@ from .utils import echo -def shell_complete(cli, prog_name, complete_var, instruction): +def shell_complete(cli, ctx_args, prog_name, complete_var, instruction): """Perform shell completion for the given CLI program. :param cli: Command being called. + :param ctx_args: Extra arguments to pass to + ``cli.make_context``. :param prog_name: Name of the executable in the shell. :param complete_var: Name of the environment variable that holds the completion instruction. @@ -25,7 +27,7 @@ def shell_complete(cli, prog_name, complete_var, instruction): if comp_cls is None: return 1 - comp = comp_cls(cli, prog_name, complete_var) + comp = comp_cls(cli, ctx_args, prog_name, complete_var) if instruction == "source": echo(comp.source()) @@ -190,8 +192,9 @@ class ShellComplete: be provided by subclasses. """ - def __init__(self, cli, prog_name, complete_var): + def __init__(self, cli, ctx_args, prog_name, complete_var): self.cli = cli + self.ctx_args = ctx_args self.prog_name = prog_name self.complete_var = complete_var @@ -238,7 +241,7 @@ def get_completions(self, args, incomplete): :param args: List of complete args before the incomplete value. :param incomplete: Value being completed. May be empty. """ - ctx = _resolve_context(self.cli, self.prog_name, args) + ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) if ctx is None: return [] @@ -446,7 +449,7 @@ def _is_incomplete_option(args, param): return last_option is not None and last_option in param.opts -def _resolve_context(cli, prog_name, args): +def _resolve_context(cli, ctx_args, prog_name, args): """Produce the context hierarchy starting with the command and traversing the complete arguments. This only follows the commands, it doesn't trigger input prompts or callbacks. @@ -455,7 +458,8 @@ def _resolve_context(cli, prog_name, args): :param prog_name: Name of the executable in the shell. :param args: List of complete args before the incomplete value. """ - ctx = cli.make_context(prog_name, args.copy(), resilient_parsing=True) + ctx_args["resilient_parsing"] = True + ctx = cli.make_context(prog_name, args.copy(), **ctx_args) args = ctx.protected_args + ctx.args while args: diff --git a/tests/test_shell_completion.py b/tests/test_shell_completion.py index 2357cb78d..39c016121 100644 --- a/tests/test_shell_completion.py +++ b/tests/test_shell_completion.py @@ -14,7 +14,7 @@ def _get_completions(cli, args, incomplete): - comp = ShellComplete(cli, cli.name, "_CLICK_COMPLETE") + comp = ShellComplete(cli, {}, cli.name, "_CLICK_COMPLETE") return comp.get_completions(args, incomplete) @@ -275,3 +275,17 @@ def test_full_complete(runner, shell, env, expect): env["_CLI_COMPLETE"] = f"complete_{shell}" 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": "complete_bash"}, + ) + assert result.output == "plain,a\nplain,b\n"