Python 点击 (str, str) 选项的自动完成

Python Click autocomplete for (str, str) option

我正在使用 Python 和 Click 编写 CLI 工具。其中一个命令有一个 type=(str, str)) 选项,用法如下:command subcommand --option foo bar.

第一个参数 (foo) 有多个选项可供选择,对于第一个参数的每个选择,第二个参数 (bar) 也有多个选项可供选择。所以第二个参数的选项取决于第一个参数的选择!

问题是:如何使用 support that Click 提供的功能为此编写自动完成功能?

特别是:

要自动完成包含两个字符串的点击选项,其中第二个字符串取决于第一个字符串,您不需要两个完成函数。您只需要一种方法来确定当前正在完成两个字符串中的哪一个。对于名为 --opt 的选项,我们可以完成一种 (str, str) 类型,例如:

代码:

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)]

使用自动完成

可以通过autocompletion函数点赞:

@click.option('--opt', type=(str, str), autocompletion=get_opts)

这是如何工作的?

autocompletion 函数被传递给 args 的列表。当完成一个选项时,我们可以在args中找到我们的选项名称。在这种情况下,我们可以在 args 中查找 --opt 以获得我们是完成第一个字符串还是第二个字符串的位置的锚点。然后我们 return 匹配已输入字符的字符串。

测试代码:

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')

测试结果:

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