To autocomplete a click option which is two strings, in which the second string depends on the first, you do not need two completion functions. You simply need a way to determine which of the two strings is currently being completed. For an option named --opt
we can complete a type of (str, str)
like:
Code:
def get_opts(ctx, args, incomplete):
""" auto complete for option "opt"
:param ctx: The current click context.
:param args: The list of arguments passed in.
:param incomplete: The partial word that is being completed, as a
string. May be an empty string '' if no characters have
been entered yet.
:return: list of possible choices
"""
opts = {
'foo1': ('bar11', 'bar21', 'bar31'),
'foo2': ('bar12', 'bar22', 'bar32'),
'fox3': ('bar13', 'bar23', 'bar33'),
}
if args[-1] == '--opt':
possible_choices = opts.keys()
elif args[-1] in opts:
possible_choices = opts[args[-1]]
else:
possible_choices = ()
return [arg for arg in possible_choices if arg.startswith(incomplete)]
Using autocompletion
You can pass the autocompletion
function to click like:
@click.option('--opt', type=(str, str), autocompletion=get_opts)
How does this work?
The autocompletion
function is passed a list of args
. When completing an option, we can find our option name in the args
. In this case we can look for --opt
in the args to get an anchor for the position of whether we are completing the first or second string. Then we return the string which matches the already entered character.
Test Code:
import click
@click.command()
@click.option('--opt', type=(str, str), autocompletion=get_opts)
@click.argument('arg')
def cli(opt, arg):
"""My Great Cli"""
if __name__ == "__main__":
commands = (
('--opt', 2, 'foo1 foo2 fox3'),
('--opt f', 2, 'foo1 foo2 fox3'),
('--opt fo', 2, 'foo1 foo2 fox3'),
('--opt foo', 2, 'foo1 foo2'),
('--opt fox', 2, 'fox3'),
('--opt foz', 2, ''),
('--opt foo2 b', 3, 'bar12 bar22 bar32'),
('--opt foo2 bar1', 3, 'bar12'),
('--opt foo2 baz', 3, ''),
)
import os
import sys
from unittest import mock
from click._bashcomplete import do_complete
failed = []
for cmd_args in commands:
cmd_args_with_arg = (
'arg ' + cmd_args[0], cmd_args[1] + 1, cmd_args[2])
for cmd in (cmd_args, cmd_args_with_arg):
with mock.patch('click._bashcomplete.echo') as echo:
os.environ['COMP_WORDS'] = 'x ' + cmd[0]
os.environ['COMP_CWORD'] = str(cmd[1])
do_complete(cli, 'x', False)
completions = [c[0][0] for c in echo.call_args_list]
if completions != cmd[2].split():
failed.append(completions, cmd[2].split())
print('Click Version: {}'.format(click.__version__))
print('Python Version: {}'.format(sys.version))
if failed:
for fail in failed:
print('Got {}, expected {}'.format(completions, cmd[2].split()))
else:
print('All tests passed')
Test Results:
Click Version: 7.0
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
All tests passed