- 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 anargparse.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}
)
Table of Contents
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
- executes the underlying script every time
- pyzshcomplete
- executes the underlying script every time
<TAB>
is pressed (slow and has side-effects) - only provides
zsh
completion
- executes the underlying script every time
- click
- different framework completely replacing the builtin
argparse
- solves multiple problems (rather than POSIX-style "do one thing well")
- different framework completely replacing the builtin
Please do open issues & pull requests! Some ideas:
- support
fish
- support
powershell
- support
tcsh
See CONTRIBUTING.md for more guidance.