Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending BASH completion with Python Click

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?

like image 242
Scott Crooks Avatar asked Sep 11 '18 18:09

Scott Crooks


People also ask

Does bash have tab completion?

Bash completion is a functionality through which Bash helps users type their commands more quickly and easily. It does this by presenting possible options when users press the Tab key while typing a command.

What key is used for bash completion?

Bash completion is a bash function that allows you to auto complete commands or arguments by typing partially commands or arguments, then pressing the [Tab] key. This will help you when writing the bash command in terminal.

What is Shell completions?

Shell completion suggests command names, option names, and values for choice, file, and path parameter types. Options are only listed if at least a dash has been entered. Hidden commands and options are not shown.


1 Answers

Using click-completion, this is quite straight forward.

Code:

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:

Test Code:

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

Results:

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.
like image 103
Stephen Rauch Avatar answered Oct 10 '22 03:10

Stephen Rauch