-
Notifications
You must be signed in to change notification settings - Fork 394
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
155 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
--- | ||
title: '(Tab) Complete Any Python Application in 1 Minute or Less' | ||
date: 2020-06-28 | ||
description: | | ||
We've made a painless tab-completion script generator for Python applications! | ||
Find out how to take advantage of it in this blog post. | ||
descriptionLong: | | ||
We've made a painless tab-completion script generator for Python applications! | ||
It's called `shtab` and it currently works with `argparse`, `docopt`, and | ||
`docopt` to produce `bash` and `zsh` completion scripts. DVC itself uses it. | ||
Find out how to take advantage of it in this blog post. | ||
author: casperdcl | ||
--- | ||
|
||
> Other potential titles: | ||
> | ||
> - Zero Effort Tab Completion for Python Applications | ||
> - Finally: 1-Click Tab Completion for [`argparse`], [`docopt`] and [`argopt`] | ||
> - A [`shtab`] in the dark at completing your ~~sentences~~ commands | ||
Command line tools are powerful. Things like [`make`] have manual pages | ||
spanning, well, pages, while just the list of [`git`] subcommands is longer than | ||
can fit on a standard `80 x 24` terminal screen. | ||
|
||
```bash | ||
$ git <TAB> | ||
add filter-branch rebase | ||
am format-patch reflog | ||
annotate fsck relink | ||
... | ||
describe prco unassume | ||
--More-- | ||
``` | ||
|
||
Notice the `--More--` at the bottom? That's the joy of pagination. | ||
|
||
Notice the `<TAB>` at the top? That represents actually pressing the tab key. | ||
Ah, the joy of shell tab completion. | ||
|
||
Tab completion is an indispensable part of writing anything on the command-line. | ||
Personally, I can't imaging trying to `git co` (aliased to `git checkout`) a | ||
branch without `<TAB>` to do the heavy lifting. | ||
|
||
Now there's a tool called [`dvc`] which is like a cross-platform combination of | ||
[`git`] and [`make`] designed for handling big data and multiple cloud storage | ||
repositories, as well as tracking machine learning experiments. As you can | ||
imagine, supporting that many buzzwords means it also has a large number of | ||
subcommands and options. | ||
|
||
_Every time a new feature is added, maintainers and contributors have to update | ||
tab completion scripts for multiple supported shells. At best, it's a pain, and | ||
at worst, error-prone. If you've worked on maintaining CLI applications, you'll | ||
sympathise._ | ||
|
||
Surely the parser code you've written is informative enough to automate tab | ||
completion? Surely you shouldn't have to maintain and synchronise separate tab | ||
completion scripts? | ||
|
||
Good news: [`shtab`] is a new tool which magically does all of this work. | ||
|
||
Any Python CLI application using [`argparse`], [`docopt`], or [`argopt`] can | ||
have tab completion for free! | ||
|
||
### `argparse` example | ||
|
||
Suppose you have some code in a module `hello.main`: | ||
|
||
```python | ||
import argparse | ||
|
||
def get_main_parser(): | ||
parser = argparse.ArgumentParser(prog="hello") | ||
parser.add_argument("who", help="a good question", nargs="?", default="world") | ||
parser.add_argument("--what", help="a better question", default="hello", | ||
choices=["hello", "goodbye"]) | ||
return parser | ||
|
||
if __name__ == "__main__": | ||
parser = get_main_parser() | ||
args = parser.parse_args() | ||
print("{}, {}!".format(args.what, args.who)) | ||
``` | ||
|
||
To get tab completion for `bash`, simply install [`shtab`] and then run: | ||
|
||
```bash | ||
shtab --shell=bash hello.main.get_main_parser \ | ||
| sudo tee "$BASH_COMPLETION_COMPAT_DIR"/hello >/dev/null | ||
``` | ||
|
||
Zsh user? Not a problem. Simply run: | ||
|
||
```bash | ||
shtab --shell=zsh hello.main.get_main_parser \ | ||
| sudo tee /usr/local/share/zsh/site-functions/_hello >/dev/null | ||
# note the underscore `_` prefix in the filename | ||
``` | ||
|
||
Handily you can install `shtab`'s own completions by following the above | ||
examples replacing `hello` with `shtab`. | ||
|
||
### `docopt` example | ||
|
||
Feeling minimal? How about adding `import shtab` to your code for a cleaner user | ||
interface? And let's use `argopt` to convert `docopt`'s syntax to `argparse` | ||
while we're at it. | ||
|
||
```python | ||
"""Greetings and partings. | ||
Usage: | ||
greeter [options] [<you>] [<me>] | ||
Options: | ||
-g, --goodbye : Say "goodbye" (instead of "hello") | ||
-b, --print-bash-completion : Output a bash tab-completion script | ||
-z, --print-zsh-completion : Output a zsh tab-completion script | ||
Arguments: | ||
<you> : Your name [default: Anon] | ||
<me> : My name [default: Casper] | ||
""" | ||
import sys, argopt, shtab | ||
|
||
parser = argopt.argopt(__doc__) | ||
if __name__ == "__main__": | ||
args = parser.parse_args() | ||
if args.print_bash_completion: | ||
print(shtab.complete(parser, shell="bash")) | ||
sys.exit(0) | ||
if args.print_zsh_completion: | ||
print(shtab.complete(parser, shell="zsh")) | ||
sys.exit(0) | ||
|
||
msg = "k thx bai!" if args.goodbye else "hai!" | ||
print("{} says '{}' to {}".format(args.me, msg, args.you)) | ||
``` | ||
|
||
### Try it out | ||
|
||
There are many more options and features. The [documentation][`shtab`] includes | ||
examples of working with custom file completions and providing a `completion` | ||
subcommand when integrating more tightly with existing applications. | ||
|
||
Try it out with `pip install -U shtab` or `conda install -c conda-forge shtab`! | ||
|
||
[`argopt`]: https://pypi.org/project/argopt | ||
[`argparse`]: https://docs.python.org/library/argparse | ||
[`docopt`]: https://pypi.org/project/docopt | ||
[`dvc`]: https://github.com/iterative/dvc | ||
[`git`]: https://git-scm.com | ||
[`make`]: https://en.wikipedia.org/wiki/Make_(software) | ||
[`shtab`]: https://github.com/iterative/shtab |