python click bash zsh auto complete same files nested command groups

Sharing the same auto completion for zsh and bash. In zsh there is somthing called bashcompinit that can be used to share the same completion for bash and zsh. here is a small example project that will fix completion with command groups and nested commands.

project structur

├── MANIFEST.in
├── Makefile
├── auto_compleate_install.sh
├── ecli-complete-nested.sh
├── ecli-nested
├── ecli_nested_lib
│   ├── __init__.py
│   └── main.py
├── requirements.txt
└── setup.py

click example cli command

#!/usr/bin/env python2
import click

@click.group(help='')
def ecli():
    pass

@ecli.group(help='')
def cmd1():
    pass

@ecli.group(help='')
def cmd2():
    pass

@cmd1.group(help='')
def foo():
    pass

@cmd2.group(help='')
def bar():
    pass

ecli.add_command(cmd1)
ecli.add_command(cmd2)

cmd1.add_command(foo)
cmd2.add_command(bar)

@foo.command('do_thing')
@click.option('-t', '--thing', help='', required=True)
def foo_do_thing(thing):
    click.echo(thing)

@bar.command('do_stuff')
@click.option('-s', '--stuff', help='', required=True)
def bar_do_stuff(stuff):
    click.echo(stuff)

install script that is run post install

#!/usr/bin/env bash
if [[ $(basename $SHELL) = 'bash' ]];
then
    if [ -f ~/.bashrc ];
    then
        echo "Installing bash autocompletion"
        cp ecli-complete-nested.sh ~/.ecli-complete-nested.sh
        grep -q 'ecli-complete-nested' ~/.bashrc
        if [[ $? -ne 0 ]]; then
            echo "" >> ~/.bashrc
            echo "source ~/.ecli-complete-nested.sh" >> ~/.bashrc
        fi
    fi
elif [[ $(basename $SHELL) = 'zsh' ]];
then
    if [ -f ~/.zshrc ];
    then
        echo "Installing zsh autocompletion"
        cp ecli-complete-nested.sh ~/.ecli-complete-nested.sh
        grep -q 'ecli-complete-nested' ~/.zshrc
        if [[ $? -ne 0 ]]; then
            echo "" >> ~/.zshrc
            echo "autoload bashcompinit" >> ~/.zshrc
            echo "bashcompinit" >> ~/.zshrc
            echo "source ~/.ecli-complete-nested.sh" >> ~/.zshrc
        fi
    fi
fi

setup.py file that runs the post install with auto complete in zsh and bash

from pip.req import parse_requirements
from setuptools import find_packages
from setuptools import setup
from subprocess import call
from setuptools.command.install import install as _install

install_requirements = parse_requirements('requirements.txt', session=False)
requirements = [str(ir.req) for ir in install_requirements]

class install(_install):

    def __post_install(self, dir):
        call(['./auto_compleate_install.sh'])

    def run(self):
        _install.run(self)
        self.execute(
            self.__post_install,
            (self.install_lib,),
            msg="installing auto completion"
            )

setup(
    name='ecli_nested',
    version='0.1a1',
    author=u'example',
    author_email=[email protected]',
    description='Some description',
    packages=find_packages(),
    include_package_data=True,
    install_requires=requirements,
    entry_points={
        'console_scripts': [
            'ecli-nested=ecli_nested_lib.main:ecli',
        ]},
    cmdclass={'install': install},
    )

completion file for bash that can be used with zsh

_ecli()
{
    local cur prev

    cur=${COMP_WORDS[COMP_CWORD]}
    prev=${COMP_WORDS[COMP_CWORD-1]}

    case ${COMP_CWORD} in
        1)
            COMPREPLY=($(compgen -W "cmd1 cmd2" ${cur}))
            ;;
        2)
            case ${prev} in
                cmd1)
                    COMPREPLY=($(compgen -W "foo" ${cur}))
                    ;;
                cmd2)
                    COMPREPLY=($(compgen -W "bar" ${cur}))
                    ;;
            esac
            ;;
        3)
            case ${prev} in
                foo)
                    COMPREPLY=($(compgen -W "do_thing" ${cur}))
                    ;;
                bar)
                    COMPREPLY=($(compgen -W "do_stuff" ${cur}))
                    ;;
            esac
            ;;
        *)
            COMPREPLY=()
            ;;
    esac
}

complete -F _ecli ecli\-nested

The example can be found here. Source