Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom tab completion in python argparse

How to get shell tab completion cooperating with argparse in a Python script?

#!/usr/bin/env python import argparse  def main(**args):     pass  if __name__ == '__main__':     parser = argparse.ArgumentParser()     parser.add_argument('positional', choices=['spam', 'eggs'])     parser.add_argument('--optional', choices=['foo1', 'foo2', 'bar'])     args = parser.parse_args()     main(**vars(args)) 

With an executable flag set on the .py file, the expected results should be something like:

$ ./example.py sp<tab>                 ->  completes to "./example.py spam" $ ./example.py --op<tab>     ->  completes to "./example.py --optional" $ ./example.py --optional b<tab>    ->  completes to "./example.py --optional bar" $ ./example.py --optional f<tab>       ->  completes to "./example.py --optional foo"        and, additionally, prints  "foo1  foo2"  choices on stdout on a new line 
like image 573
wim Avatar asked Jan 30 '13 05:01

wim


2 Answers

For auto-complete to work you need a bash function to generate the possible options, and then you need to run complete -F <function_name> <program_name>

The best way of doing this is to have the program generate the completion function based on it's own parsing algorithm to avoid duplication. However, at a quick glance on argparse, I could not find a way to access it's internal structure, but I suggest you look for it.

Here is a bash function that will do for the above program:

function _example_auto() {     local cur=${COMP_WORDS[COMP_CWORD]}     local prev=${COMP_WORDS[COMP_CWORD-1]}      case "$prev" in     --optional )          COMPREPLY=( $(compgen -W "foo1 foo2 bar" -- $cur) )         return 0         ;;     *)         COMPREPLY=( $(compgen -W "--optional spam eggs" -- $cur) )         return 0         ;;     esac } 
like image 30
Sorin Avatar answered Sep 20 '22 21:09

Sorin


Have a look at argcomplete by Andrey Kislyuk.

Install it with:

pip install argcomplete 

Import the module and add one line in your source before calling parser.parse_args():

#!/usr/bin/env python  import argparse as ap import argcomplete  def main(**args):   pass  if __name__ == '__main__':   parser = ap.ArgumentParser()   parser.add_argument('positional', choices=['spam', 'eggs'])   parser.add_argument('--optional', choices=['foo1', 'foo2', 'bar'])   argcomplete.autocomplete(parser)   args = parser.parse_args()   main(**vars(args)) 

and to make sure that bash knows about this script, you use

eval "$(register-python-argcomplete your_script)" 

you should put that line in your ~/.bashrc or follow argcomplete's docs and activate 'global' completion.

After that you completion works as requested.

The way this works is that the eval line creates a function _python_argcomlete which is registered using complete. (Run register-python-argcomplete your_script to just have a look at what gets eval-ed into bash). The autocomplete function looks for environment variables set by the bash completion mechanism to see if it needs to act. If it acts, it exits the program. If it doesn't act, this is a normal call to the program that function does nothing and the normal flow of the program continues.

like image 136
Anthon Avatar answered Sep 21 '22 21:09

Anthon