Reputation: 1663
I'm wanting to customize the BASH completion functionality of my Python Click CLI program to include not only the commands / subcommands of the script, but also objects that the script creates.
Let's say my program is called cli-tool
, and it can create object foo
using the command:
cli-tool object create foo
For simplicity, let's say the command simply concatenates the argument string (foo
in this case) to a text file of the same name located in ~/.cli-tool/objects/foo
. Doing cat ~/.cli-tool/objects/foo
would then print foo
in your terminal.
What I would like for the tool to do is when I then type:
cli-tool object get <TAB><TAB>
The terminal would then list foo
and any other files that live inside ~/.cli-tool/objects
.
For the record, I have read the Python Click 6.x stable documentation, which clearly states:
Currently, Bash completion is an internal feature that is not customizable. This might be relaxed in future versions.
What I was hoping is that there would be a way to extract the full BASH completion script from the following command:
eval "$(_CLI_TOOL_COMPLETE=source cli-tool)"
And then customize it myself. I've also seen the click-completion project, but I'm not sure what it does beyond extending the completion for Zsh and Fish shells.
Has anyone achieved the type of completion I mention above?
Upvotes: 6
Views: 6565
Reputation:
For Click 8.1.3, you can use either of Overriding Value Completion or Custom Type Completion.
Here's an example from the docs where you can use a custom shell_complete
function. It must return a list of CompletionItem
objects, or as a shortcut it can return a list of strings.
def complete_env_vars(ctx, param, incomplete):
return [k for k in os.environ if k.startswith(incomplete)]
@click.command()
@click.argument("name", shell_complete=complete_env_vars)
def cli(name):
click.echo(f"Name: {name}")
click.echo(f"Value: {os.environ[name]}")
Upvotes: 2
Reputation: 49812
Using click-completion, this is quite straight forward.
Import and init()
Click Completion:
import click
import click_completion
click_completion.init()
Then instantiate a click.Choice
object:
option_type = click.Choice('obj1 obj2 obj3'.split())
In the case of your option directory, pass in a list of the appropriate items instead of the example obj1-obj3.
Then pass the option type to the click.argument()
decorator like:
@click.argument('option', type=option_type)
And don't forget to activate your completion with your shell. The click variation for bash is here:
import click
import click_completion
click_completion.init()
option_type = click.Choice('obj1 obj2 obj3'.split())
@click.group()
def cli():
"""My Cool Tool"""
@cli.group(name='object')
def object_group():
"""Object subcommand"""
@object_group.command()
@click.argument('option', type=option_type)
def get(option):
click.echo('option: {}'.format(option))
commands = (
('"" object get ""', 1),
('"" object get ""', 2),
('"" object get ""', 3),
'object get obj1',
'--help',
'object --help',
'object get --help',
)
os.environ['BASH_COMP'] = 'complete'
import sys, time
time.sleep(1)
print('Click Version: {}'.format(click.__version__))
print('Click Completion Version: {}'.format(click_completion.__version__))
print('Python Version: {}'.format(sys.version))
for cmd in commands:
try:
time.sleep(0.1)
print('\n-----------')
print('> ' + str(cmd))
time.sleep(0.1)
if len(cmd) == 2:
os.environ['COMP_WORDS'] = cmd[0]
os.environ['COMP_CWORD'] = str(cmd[1])
cli(complete_var='BASH_COMP')
else:
try:
del os.environ['COMP_WORDS']
del os.environ['COMP_CWORD']
except:
pass
cli(cmd.split())
except BaseException as exc:
if str(exc) != '0' and \
not isinstance(exc, (click.ClickException, SystemExit)):
raise
Click Version: 6.7
Click Completion Version: 0.4.1
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> ('"" object get ""', 1)
object
-----------
> ('"" object get ""', 2)
get
-----------
> ('"" object get ""', 3)
obj1 obj2 obj3
-----------
> object get obj1
option: obj1
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...
My Cool Tool
Options:
--help Show this message and exit.
Commands:
object Object subcommand
-----------
> object --help
Usage: test.py object [OPTIONS] COMMAND [ARGS]...
Object subcommand
Options:
--help Show this message and exit.
Commands:
get
-----------
> object get --help
Usage: test.py object get [OPTIONS] OPTION
Options:
--help Show this message and exit.
Upvotes: 8