diff options
author | Markus Unterwaditzer <markus@unterwaditzer.net> | 2017-04-16 12:43:52 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-04-16 12:43:52 +0200 |
commit | 5f2ff4faf627dc9d0925cffb7d9b8f3162fbdaad (patch) | |
tree | dfefda64336c6a761bd14ce7dd1014fd24f4e430 | |
parent | 430c50a3b6dca4e23936389513d2099b0af865cf (diff) | |
parent | 32fb787bbd4acf84983b5c49f87cfc76c7699d44 (diff) | |
download | click-5f2ff4faf627dc9d0925cffb7d9b8f3162fbdaad.tar.gz |
Merge pull request #755 from stopthatcow/feature/bash_autocompletion_6.6
Dynamic bash autocompletion
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | click/_bashcomplete.py | 23 | ||||
-rw-r--r-- | click/core.py | 4 | ||||
-rw-r--r-- | docs/bashcomplete.rst | 30 | ||||
-rw-r--r-- | examples/bashcompletion/README | 12 | ||||
-rw-r--r-- | examples/bashcompletion/bashcompletion.py | 31 | ||||
-rw-r--r-- | examples/bashcompletion/setup.py | 15 | ||||
-rw-r--r-- | tests/test_bashcomplete.py | 22 |
8 files changed, 127 insertions, 15 deletions
@@ -7,12 +7,13 @@ 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 - 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 type=click.Choice in a click.option. The choices are listed within - parentthesis like 'Choose fruit (apple, orange): '. + parenthesis like 'Choose fruit (apple, orange): '. - The exception objects now store unicode properly. - Added the ability to hide commands and options from help. - Added Float Range in Types. diff --git a/click/_bashcomplete.py b/click/_bashcomplete.py index bd1c278..10a9dc2 100644 --- a/click/_bashcomplete.py +++ b/click/_bashcomplete.py @@ -8,7 +8,6 @@ from .parser import split_arg_string from .core import MultiCommand, Option, Argument from .types import Choice - WORDBREAK = '=' COMPLETION_SCRIPT = ''' @@ -97,6 +96,22 @@ def is_incomplete_argument(current_params, cmd_param): return True return False +def get_user_autocompletions(ctx, args, incomplete, cmd_param): + """ + :param ctx: context associated with the parsed command + :param args: full list of args + :param incomplete: the incomplete text to autocomplete + :param cmd_param: command definition + :return: all the possible user-specified completions for the param + """ + if isinstance(cmd_param.type, Choice): + return cmd_param.type.choices + elif cmd_param.autocompletion is not None: + return cmd_param.autocompletion(ctx=ctx, + args=args, + incomplete=incomplete) + else: + return [] def get_choices(cli, prog_name, args, incomplete): """ @@ -134,16 +149,14 @@ def get_choices(cli, prog_name, args, incomplete): # completion for option values by choices for cmd_param in ctx.command.params: if isinstance(cmd_param, Option) and is_incomplete_option(all_args, cmd_param): - if isinstance(cmd_param.type, Choice): - choices.extend(cmd_param.type.choices) + choices.extend(get_user_autocompletions(ctx, all_args, incomplete, cmd_param)) found_param = True break if not found_param: # completion for argument values by choices for cmd_param in ctx.command.params: if isinstance(cmd_param, Argument) and is_incomplete_argument(ctx.params, cmd_param): - if isinstance(cmd_param.type, Choice): - choices.extend(cmd_param.type.choices) + choices.extend(get_user_autocompletions(ctx, all_args, incomplete, cmd_param)) found_param = True break diff --git a/click/core.py b/click/core.py index 18c1766..5e8d0ee 100644 --- a/click/core.py +++ b/click/core.py @@ -1266,7 +1266,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, + autocompletion=None): self.name, self.opts, self.secondary_opts = \ self._parse_decls(param_decls or (), expose_value) @@ -1289,6 +1290,7 @@ class Parameter(object): self.is_eager = is_eager self.metavar = metavar self.envvar = envvar + self.autocompletion = autocompletion @property def human_readable_name(self): diff --git a/docs/bashcomplete.rst b/docs/bashcomplete.rst index 9dd9a99..37d10e6 100644 --- a/docs/bashcomplete.rst +++ b/docs/bashcomplete.rst @@ -16,9 +16,6 @@ how to do that, see :ref:`setuptools-integration`. Also, Click currently only supports completion for Bash. Zsh support is available through Zsh's bash completion compatibility mode. -Currently, Bash completion is an internal feature that is not customizable. -This might be relaxed in future versions. - What it Completes ----------------- @@ -32,6 +29,33 @@ least a dash has been provided. Example:: $ repo clone -<TAB><TAB> --deep --help --rev --shallow -r +Additionally, custom suggestions can be provided for arguments and options with +the ``autocompletion`` parameter. ``autocompletion`` should a callback function +that returns a list of strings. This is useful when the suggestions need to be +dynamically generated at bash completion time. The callback function will be +passed 3 keyword arguments: + +- ``ctx`` - The current click context. +- ``args`` - The list of arguments passed in. +- ``incomplete`` - The partial word that is being completed, as a string. May + be an empty string ``''`` if no characters have been entered yet. + +Here is an example of using a callback function to generate dynamic suggestions: + +.. click:example:: + + import os + + def get_env_vars(ctx, incomplete, cwords, cword): + return os.environ.keys() + + @click.command() + @click.argument("envvar", type=click.STRING, autocompletion=get_env_vars) + def cmd1(envvar): + click.echo('Environment variable: %s' % envvar) + click.echo('Value: %s' % os.environ[envvar]) + + Activation ---------- diff --git a/examples/bashcompletion/README b/examples/bashcompletion/README new file mode 100644 index 0000000..f8a0d51 --- /dev/null +++ b/examples/bashcompletion/README @@ -0,0 +1,12 @@ +$ bashcompletion + + bashcompletion is a simple example of an application that + tries to autocomplete commands, arguments and options. + + This example requires Click 2.0 or higher. + +Usage: + + $ pip install --editable . + $ eval "$(_BASHCOMPLETION_COMPLETE=source bashcompletion)" + $ bashcompletion --help diff --git a/examples/bashcompletion/bashcompletion.py b/examples/bashcompletion/bashcompletion.py new file mode 100644 index 0000000..8aaf174 --- /dev/null +++ b/examples/bashcompletion/bashcompletion.py @@ -0,0 +1,31 @@ +import click +import os + +@click.group() +def cli(): + pass + +def get_env_vars(ctx, args, incomplete): + return os.environ.keys() + +@cli.command() +@click.argument("envvar", type=click.STRING, autocompletion=get_env_vars) +def cmd1(envvar): + click.echo('Environment variable: %s' % envvar) + click.echo('Value: %s' % os.environ[envvar]) + +@click.group() +def group(): + pass + +def list_users(ctx, args, incomplete): + # Here you can generate completions dynamically + users = ['bob', 'alice'] + return users + +@group.command() +@click.argument("user", type=click.STRING, autocompletion=list_users) +def subcmd(user): + click.echo('Chosen user is %s' % user) + +cli.add_command(group) diff --git a/examples/bashcompletion/setup.py b/examples/bashcompletion/setup.py new file mode 100644 index 0000000..ad20081 --- /dev/null +++ b/examples/bashcompletion/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup + +setup( + name='click-example-bashcompletion', + version='1.0', + py_modules=['bashcompletion'], + include_package_data=True, + install_requires=[ + 'click', + ], + entry_points=''' + [console_scripts] + bashcompletion=bashcompletion:cli + ''', +) diff --git a/tests/test_bashcomplete.py b/tests/test_bashcomplete.py index 42d2255..f8e91b4 100644 --- a/tests/test_bashcomplete.py +++ b/tests/test_bashcomplete.py @@ -83,9 +83,18 @@ def test_long_chain(): def bsub(bsub_opt): pass + COLORS = ['red', 'green', 'blue'] + def get_colors(ctx, args, incomplete): + for c in COLORS: + yield c + + CSUB_OPT_CHOICES = ['foo', 'bar'] + CSUB_CHOICES = ['bar', 'baz'] @bsub.command('csub') - @click.option('--csub-opt') - def csub(csub_opt): + @click.option('--csub-opt', type=click.Choice(CSUB_OPT_CHOICES)) + @click.option('--csub', type=click.Choice(CSUB_CHOICES)) + @click.argument('color', autocompletion=get_colors) + def csub(csub_opt, color): pass assert list(get_choices(cli, 'lol', [], '-')) == ['--cli-opt'] @@ -94,8 +103,13 @@ def test_long_chain(): assert list(get_choices(cli, 'lol', ['asub'], '')) == ['bsub'] assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '-')) == ['--bsub-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'], '')) == [] + assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '-')) == ['--csub-opt', '--csub'] + assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub', '--csub-opt'], '')) == CSUB_OPT_CHOICES + assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '--csub')) == ['--csub-opt', '--csub'] + assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub', '--csub'], '')) == CSUB_CHOICES + assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub', '--csub-opt'], 'f')) == ['foo'] + assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '')) == COLORS + assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], 'b')) == ['blue'] def test_argument_choice(): |