Skip to content
Closed
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
14 changes: 13 additions & 1 deletion click/_bashcomplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,30 @@ def resolve_ctx(cli, prog_name, args):


def get_choices(cli, prog_name, args, incomplete):
# resolve_ctx will change args in place
# need to grab the last completed one here and now
last_complete = [a for a in (args if args else ['']) if a != '='][-1]
if incomplete == '=':
incomplete = ''

ctx = resolve_ctx(cli, prog_name, args)
if ctx is None:
return

choices = []

if last_complete:
for param in ctx.command.params:
if last_complete in param.opts:
choices.extend(param.get_completion())
break
if incomplete and not incomplete[:1].isalnum():
for param in ctx.command.params:
if not isinstance(param, Option):
continue
choices.extend(param.opts)
choices.extend(param.secondary_opts)
elif isinstance(ctx.command, MultiCommand):
if isinstance(ctx.command, MultiCommand):
choices.extend(ctx.command.list_commands(ctx))

for item in choices:
Expand Down
14 changes: 13 additions & 1 deletion click/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1260,7 +1260,8 @@ class Parameter(object):

def __init__(self, param_decls=None, type=None, required=False,
default=None, callback=None, nargs=None, metavar=None,
expose_value=True, is_eager=False, envvar=None):
expose_value=True, is_eager=False, envvar=None,
completer=None, choices=None):
self.name, self.opts, self.secondary_opts = \
self._parse_decls(param_decls or (), expose_value)

Expand All @@ -1283,6 +1284,8 @@ def __init__(self, param_decls=None, type=None, required=False,
self.is_eager = is_eager
self.metavar = metavar
self.envvar = envvar
self.completer = completer
self.choices = choices

@property
def human_readable_name(self):
Expand Down Expand Up @@ -1415,6 +1418,15 @@ def get_help_record(self, ctx):
def get_usage_pieces(self, ctx):
return []

def get_completion(self):
if self.completer:
for thing in self.completer():
yield str(thing)
if self.choices:
for thing in self.choices:
yield str(thing)



class Option(Parameter):
"""Options are usually optional values on the command line and
Expand Down
Empty file added tests/__init__.py
Empty file.
28 changes: 28 additions & 0 deletions tests/test_bashcomplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,31 @@ def csub(csub_opt):
assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '')) == ['csub']
assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '-')) == ['--csub-opt']
assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '')) == []


def test_completer():

def custom_completer():
yield 'custom'
for i in [0, 1, 2]:
yield i
yield 'bye'

@click.command()
@click.option('--local-opt', completer=custom_completer)
def cli(local_opt):
pass

assert list(get_choices(cli, 'lol', [], '-')) == ['--local-opt']
assert list(get_choices(cli, 'lol', ['--local-opt'], '')) == ['custom', '0', '1', '2', 'bye']


def test_choices():

@click.command()
@click.option('--local-opt', choices=['foo', 'bar', 'spam', 'eggs'])
def cli(local_opt):
pass

assert list(get_choices(cli, 'lol', [], '-')) == ['--local-opt']
assert list(get_choices(cli, 'lol', ['--local-opt'], '')) == ['foo', 'bar', 'spam', 'eggs']