summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES5
-rw-r--r--click/_bashcomplete.py19
-rw-r--r--click/core.py11
-rw-r--r--docs/bashcomplete.rst2
-rw-r--r--tests/test_bashcomplete.py26
5 files changed, 55 insertions, 8 deletions
diff --git a/CHANGES b/CHANGES
index a0b6479..223e4c5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,7 +9,7 @@ Version 7.0
(upcoming release with new features, release date to be decided)
- Added support for bash completions containing spaces. See #773.
- Added support for dynamic bash completion from a user-supplied callback.
- See #755
+ See #755.
- Added support for bash completion of type=click.Choice for Options and
Arguments. See #535.
- The user is now presented with the available choices if prompt=True and
@@ -23,6 +23,8 @@ Version 7.0
- ``launch`` now works properly under Cygwin. See #650.
- `CliRunner.invoke` now may receive `args` as a string representing
a Unix shell command. See #664.
+- Fix bug that caused bashcompletion to give inproper completions on
+ chained commands. See #774.
Version 6.8
-----------
@@ -33,6 +35,7 @@ Version 6.8
#728.
- Fix bug in test runner when calling ``sys.exit`` with ``None``. See #739.
- Fix crash on Windows console, see #744.
+- Fix bashcompletion on chained commands. See #754.
Version 6.7
-----------
diff --git a/click/_bashcomplete.py b/click/_bashcomplete.py
index 7ea9683..536b5d7 100644
--- a/click/_bashcomplete.py
+++ b/click/_bashcomplete.py
@@ -43,14 +43,18 @@ def resolve_ctx(cli, prog_name, args):
:return: the final context/command parsed
"""
ctx = cli.make_context(prog_name, args, resilient_parsing=True)
- while ctx.protected_args + ctx.args and isinstance(ctx.command, MultiCommand):
- a = ctx.protected_args + ctx.args
- cmd = ctx.command.get_command(ctx, a[0])
+ args_remaining = ctx.protected_args + ctx.args
+ while ctx is not None and args_remaining:
+ if isinstance(ctx.command, MultiCommand):
+ cmd = ctx.command.get_command(ctx, args_remaining[0])
if cmd is None:
return None
- ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True)
- return ctx
+ ctx = cmd.make_context(args_remaining[0], args_remaining[1:], parent=ctx, resilient_parsing=True)
+ args_remaining = ctx.protected_args + ctx.args
+ else:
+ ctx = ctx.parent
+ return ctx
def start_of_option(param_str):
"""
@@ -165,6 +169,11 @@ def get_choices(cli, prog_name, args, incomplete):
# completion for any subcommands
choices.extend(ctx.command.list_commands(ctx))
+ if not start_of_option(incomplete) and ctx.parent is not None and isinstance(ctx.parent.command, MultiCommand) and ctx.parent.command.chain:
+ # completion for chained commands
+ remaining_comands = set(ctx.parent.command.list_commands(ctx.parent))-set(ctx.parent.protected_args)
+ choices.extend(remaining_comands)
+
for item in choices:
if item.startswith(incomplete):
yield item
diff --git a/click/core.py b/click/core.py
index 5e8d0ee..0741528 100644
--- a/click/core.py
+++ b/click/core.py
@@ -25,6 +25,15 @@ SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...'
SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...'
+def fast_exit(code):
+ """Exit without garbage collection, this speeds up exit by about 10ms for
+ things like bash completion.
+ """
+ sys.stdout.flush()
+ sys.stderr.flush()
+ os._exit(code)
+
+
def _bashcomplete(cmd, prog_name, complete_var=None):
"""Internal handler for the bash completion support."""
if complete_var is None:
@@ -35,7 +44,7 @@ def _bashcomplete(cmd, prog_name, complete_var=None):
from ._bashcomplete import bashcomplete
if bashcomplete(cmd, prog_name, complete_var, complete_instr):
- sys.exit(1)
+ fast_exit(1)
def _check_multicommand(base_command, cmd_name, cmd, register=False):
diff --git a/docs/bashcomplete.rst b/docs/bashcomplete.rst
index 37d10e6..e2e2d49 100644
--- a/docs/bashcomplete.rst
+++ b/docs/bashcomplete.rst
@@ -46,7 +46,7 @@ Here is an example of using a callback function to generate dynamic suggestions:
import os
- def get_env_vars(ctx, incomplete, cwords, cword):
+ def get_env_vars(ctx, args, incomplete):
return os.environ.keys()
@click.command()
diff --git a/tests/test_bashcomplete.py b/tests/test_bashcomplete.py
index f8e91b4..268e046 100644
--- a/tests/test_bashcomplete.py
+++ b/tests/test_bashcomplete.py
@@ -112,6 +112,32 @@ def test_long_chain():
assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], 'b')) == ['blue']
+def test_chaining():
+ @click.group('cli', chain=True)
+ @click.option('--cli-opt')
+ def cli(cli_opt):
+ pass
+
+ @cli.command('asub')
+ @click.option('--asub-opt')
+ def asub(asub_opt):
+ pass
+
+ @cli.command('bsub')
+ @click.option('--bsub-opt')
+ @click.argument('arg', type=click.Choice(['arg1', 'arg2']))
+ def bsub(bsub_opt, arg):
+ pass
+
+ assert list(get_choices(cli, 'lol', [], '-')) == ['--cli-opt']
+ assert list(get_choices(cli, 'lol', [], '')) == ['asub', 'bsub']
+ assert list(get_choices(cli, 'lol', ['asub'], '-')) == ['--asub-opt']
+ assert list(get_choices(cli, 'lol', ['asub'], '')) == ['bsub']
+ assert list(get_choices(cli, 'lol', ['bsub'], '')) == ['arg1', 'arg2', 'asub']
+ assert list(get_choices(cli, 'lol', ['asub', '--asub-opt', '5', 'bsub'], '-')) == ['--bsub-opt']
+ assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '-')) == ['--bsub-opt']
+
+
def test_argument_choice():
@click.command()
@click.argument('arg1', required=False, type=click.Choice(['arg11', 'arg12']))