Skip to content

↔️ Automagic shell tab completion for Python CLI applications

License

Notifications You must be signed in to change notification settings

simaoafonso-pwt/shtab

 
 

Repository files navigation

Logo

Tests Coverage conda-forge PyPI

  • What: Automatically generate shell tab completion scripts for Python CLI apps
  • Why: Speed & correctness. Alternatives like argcomplete and pyzshcomplete are slow and have side-effects
  • How: shtab processes an argparse.ArgumentParser object to generate a tab completion script for your shell
  • Outputs tab completion scripts for
    • bash
    • zsh
  • Supports
  • Supports arguments, options and subparsers
  • Supports choices (e.g. --say={hello,goodbye})
  • Supports file and directory path completion
  • Supports custom path completion (e.g. --file={*.txt})

Choose one of:

  • pip install shtab
  • conda install -c conda-forge shtab

bash users who have never used any kind of tab completion before should also follow the OS-specific instructions below.

Recent versions should have completion already enabled. For older versions, first run sudo apt install --reinstall bash-completion, then make sure these lines appear in ~/.bashrc:

# enable bash completion in interactive shells
if ! shopt -oq posix; then
 if [ -f /usr/share/bash-completion/bash_completion ]; then
   . /usr/share/bash-completion/bash_completion
 elif [ -f /etc/bash_completion ]; then
   . /etc/bash_completion
 fi
fi

First run brew install bash-completion, then add the following to ~/.bash_profile:

if [ -f $(brew --prefix)/etc/bash_completion ]; then
   . $(brew --prefix)/etc/bash_completion
fi

There are two ways of using shtab:

  • CLI Usage: shtab's own CLI interface for external applications
    • may not require any code modifications whatsoever
    • end-users execute shtab your_cli_app.your_parser_object
  • Library Usage: as a library integrated into your CLI application
    • adds a couple of lines to your application
    • argument mode: end-users execute your_cli_app --print-completion {bash,zsh}
    • subparser mode: end-users execute your_cli_app completion {bash,zsh}

The only requirement is that external CLI applications provide an importable argparse.ArgumentParser object (or alternatively an importable function which returns a parser object). This may require a trivial code change.

Once that's done, simply put the output of shtab --shell=your_shell your_cli_app.your_parser_object somewhere your shell looks for completions.

Below are various examples of enabling shtab's own tab completion scripts.

shtab --shell=bash shtab.main.get_main_parser --error-unimportable \
  | sudo tee "$BASH_COMPLETION_COMPAT_DIR"/shtab

If both shtab and the module it's completing are globally importable, eager usage is an option. "Eager" means automatically updating completions each time a terminal is opened.

# Install locally
echo 'eval "$(shtab --shell=bash shtab.main.get_main_parser)"' \
  >> ~/.bash_completion

# Install locally (lazy load for bash-completion>=2.8)
echo 'eval "$(shtab --shell=bash shtab.main.get_main_parser)"' \
  > "${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions/shtab"

# Install system-wide
echo 'eval "$(shtab --shell=bash shtab.main.get_main_parser)"' \
  | sudo tee "$(pkg-config --variable=completionsdir bash-completion)"/shtab

# Install system-wide (legacy)
echo 'eval "$(shtab --shell=bash shtab.main.get_main_parser)"' \
  | sudo tee "$BASH_COMPLETION_COMPAT_DIR"/shtab

Note that zsh requires completion script files to be named _{EXECUTABLE} (with an underscore prefix).

# note the underscore `_` prefix
shtab --shell=zsh shtab.main.get_main_parser --error-unimportable \
  | sudo tee /usr/local/share/zsh/site-functions/_shtab

To be more eager, place the generated script somewhere in $fpath. For example, add these lines to the top of ~/.zshrc:

mkdir -p ~/.zsh/completions
fpath=($fpath ~/.zsh/completions)  # must be before `compinit` lines
shtab --shell=zsh shtab.main.get_main_parser > ~/.zsh/completions/_shtab

See the examples/ folder for more.

Any existing argparse-based scripts should be supported with minimal effort. For example, starting with this existing code:

#!/usr/bin/env python
import argparse

def get_main_parser():
    parser = argparse.ArgumentParser(prog="MY_PROG", ...)
    parser.add_argument(...)
    parser.add_subparsers(...)
    ...
    return parser

if __name__ == "__main__":
    parser = get_main_parser()
    args = parser.parse_args()
    ...

Assuming this code example is installed in MY_PROG.command.main, simply run:

# bash
shtab --shell=bash -u MY_PROG.command.main.get_main_parser \
  | sudo tee "$BASH_COMPLETION_COMPAT_DIR"/MY_PROG

# zsh
shtab --shell=zsh -u MY_PROG.command.main.get_main_parser \
  | sudo tee /usr/local/share/zsh/site-functions/_MY_PROG

Not working? Make sure that shtab and the application you're trying to complete are both accessible from your environment.

"Eager" installation (completions are re-generated upon login/terminal start) is recommended. Naturally, shtab and the CLI application to complete should be accessible/importable from the login environment. If installing shtab in a different virtual environment, you'd have to add a line somewhere appropriate (e.g. $CONDA_PREFIX/etc/conda/activate.d/env_vars.sh).

By default, shtab will silently do nothing if it cannot import the requested application. Use -u, --error-unimportable to noisily complain.

See the examples/ folder for more.

Complex projects with subparsers and custom completions for paths matching certain patterns (e.g. --file=*.txt) are fully supported (see examples/customcomplete.py or even iterative/dvc:command/completion.py for example).

Add direct support to scripts for a little more configurability:

#!/usr/bin/env python
import argparse
import shtab  # for completion magic

def get_main_parser():
    parser = argparse.ArgumentParser(prog="pathcomplete")
    shtab.add_argument_to(parser, ["-s", "--print-completion"])  # magic!
    # file & directory tab complete
    parser.add_argument("file", nargs="?").complete = shtab.FILE
    parser.add_argument("--dir", default=".").complete = shtab.DIRECTORY
    return parser

if __name__ == "__main__":
    parser = get_main_parser()
    args = parser.parse_args()
    print("received <file>=%r --dir=%r" % (args.file, args.dir))

Simply use argopt to create a parser object from docopt syntax:

#!/usr/bin/env python
"""Greetings and partings.

Usage:
  greeter [options] [<you>] [<me>]

Options:
  -g, --goodbye  : Say "goodbye" (instead of "hello")

Arguments:
  <you>  : Your name [default: Anon]
  <me>  : My name [default: Casper]
"""
import sys, argopt, shtab  # NOQA

parser = argopt.argopt(__doc__)
shtab.add_argument_to(parser, ["-s", "--print-completion"])  # magic!
if __name__ == "__main__":
    args = parser.parse_args()
    msg = "k thx bai!" if args.goodbye else "hai!"
    print("{} says '{}' to {}".format(args.me, msg, args.you))
  • argcomplete
    • executes the underlying script every time <TAB> is pressed (slow and has side-effects)
    • only provides bash completion
  • pyzshcomplete
    • executes the underlying script every time <TAB> is pressed (slow and has side-effects)
    • only provides zsh completion
  • click
    • different framework completely replacing the builtin argparse
    • solves multiple problems (rather than POSIX-style "do one thing well")

Please do open issues & pull requests! Some ideas:

  • support fish
  • support powershell
  • support tcsh

See CONTRIBUTING.md for more guidance.

Hits

About

↔️ Automagic shell tab completion for Python CLI applications

Resources

License

Stars

Watchers

Forks

Languages

  • Python 100.0%