Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bashautocompletion of arguments on chained commands #774

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Version 7.0

(upcoming release with new features, release date to be decided)
- 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
Expand All @@ -22,6 +22,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
-----------
Expand Down
19 changes: 14 additions & 5 deletions click/_bashcomplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,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):
"""
Expand Down Expand Up @@ -164,6 +168,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
Expand Down
26 changes: 26 additions & 0 deletions tests/test_bashcomplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,32 @@ def csub(csub_opt, color):
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']))
Expand Down