#!/usr/bin/env python
# -*- Mode: Python -*-
# vi:si:et:sw=4:sts=4:ts=4

# generate bash completion for all commands
# first argument should be program name
# second argument should be the main Command entry class's fully qualified name

import sys

def funcName(cmd):
    """
    Generate a name for the given command by walking up the ancestry
    and seperating with underscore.
    """
    l = [cmd.name]
    while cmd.parentCommand:
        cmd = cmd.parentCommand
        l.append(cmd.name)

    l.reverse()
    return "_%s_complete_" % cmd.name + "_".join(l)
    
def generateOneCommand(cmd):
    function = funcName(cmd)
    commandList = cmd.subCommands.keys()
    commandList.sort()
    commands = '"' + " ".join(commandList) + '"'

    # poking into private instance variables for optparse.Option is nasty,
    # but I see no alternative
    optionBooleanList = []
    optionValueList = []
    optionList = []
    for option in cmd.parser.option_list:
        optionList.extend(option._short_opts + option._long_opts)
        if not option.nargs:
            optionBooleanList.extend(option._short_opts + option._long_opts)
        else:
            optionValueList.extend(option._short_opts + option._long_opts)
    optionList.sort()
    options = '"' + " ".join(optionList) + '"'
    optionsBoolean = '"' + " ".join(optionBooleanList) + '"'
    optionsValue = '"' + " ".join(optionValueList) + '"'

    name = cmd.name

    return """
%(function)s()
{
    options=%(options)s
    optionsboolean=%(optionsBoolean)s
    optionsvalue=%(optionsValue)s
    commands=%(commands)s
    completed=false

    debug "function %(function)s"
    debug "args '$@'"
    debug "ARG1 '$1'"
    debug "ARG2 '$2'"
    shift
    debug "after shift: args '$@'"
    debug "ARG1 '$1'"
    debug "ARG2 '$2'"


    while [[ "$completed" == "false" ]]
    do
        if [[ "$1" == -* ]]
        then
            # handle as argument
            debug "handling argument $#"
            # found will be set to true when the current argument fully matches
            # an option, causing us to swallow it
            found=false
            # first check for boolean options
            for option in $optionsboolean
            do
                debug "matching option $option to args $1"
                if [[ "$option" == "$1" ]]
                then
                    debug "found full boolean option $option, eating"
                    found=true
                    shift
                fi
            done
            # then check for valued options
            if [[ "$found" == false ]]
            then
                for option in $optionsvalue
                do
                    debug "matching option $option to args $1"
                    if [[ "$option" == "$1" ]]
                    then
                        found=true
                        if [[ $# -eq 1 ]]
                        then
                            # a valued option with no value
                            # we can't complete this since we don't know what
                            # values the option takes
                            completed=true
                            COMPREPLY=()
                        else
                            # eat option and its value
                            shift 2
                        fi
                    fi
                done
            fi
            if [[ "$found" == false ]]
            then
                debug "completing argument"
                COMPREPLY=( $( compgen -W "$options" -- $1 ) )
                debug "COMPREPLY ${COMPREPLY[*]}"
                completed=true
            fi
        else
            # handle as command
            debug "handle as command"
            found=false
            if [[ $# -eq 0 ]]; then
                # completing this command
                COMPREPLY=( $( compgen -W "$commands" -- $1 ) )
                debug "command, COMPREPLY ${COMPREPLY[*]}"
                completed=true
            else
                for command in $commands
                do
                    debug "matching arg $1 against command $command"
                    if [[ "$command" == "$1" ]]
                    then
                        debug "found full command, delegating"
                         # completing a subcommand, delegate
                        debug "delegate, $# args, 1 is $1"
                        %(function)s_$1 $@
                        debug "delegated, COMPREPLY ${COMPREPLY[*]}"
                        found=true
                    fi
                done
                # if not found, we must still be wanting to complete the
                # current partial command
                if [[ "$found" == false ]]
                then
                    COMPREPLY=( $( compgen -W "$commands" -- $1 ) )
                    completed=true
                fi
            fi
        fi
        debug "function %(function)s: COMPREPLY ${COMPREPLY[*]}"
    done
        
}
""" % locals()

def generateSubCommands(cmd):
    snippets = []
    if cmd.subCommands:
        for subCommand in cmd.subCommands.values():
            snippets.extend(generateSubCommands(subCommand))
    snippets.append(generateOneCommand(cmd))
    return snippets

        
def start():
    if len(sys.argv) != 3:
        sys.stderr.write('Usage: %s [program-name] [entry-class]\n')
        sys.exit(1)

    name = sys.argv[1]
    entry = sys.argv[2]
    parts = entry.split('.')
    if len(parts) <= 1:
        sys.stderr.write(
            'The entry class should be a module-qualified Class.\n')
        sys.exit(1)
    module = ".".join(parts[:-1])
    command = "from %s import %s as EntryClass" % (module, parts[-1])
    exec command
    entry = EntryClass()
    print """#-*- mode: shell-script;-*-

# Programmed completion for bash to use %s

""" % name

    print "\n".join(generateSubCommands(entry))

    print """
# helper debug function
debug()
{
    if [[ ! -z "$DEBUG" ]]
    then
        echo $@
    fi
}

# main entry point
# dispatches to a command-specific function, passing in the rest of the
# command line as arguments, starting with the command name being called
_%(name)s()
{
        COMPREPLY=()
        # pass as a list, not as a single string
        _%(name)s_complete_%(name)s ${COMP_WORDS[*]}
}

complete -F _%(name)s -o default %(name)s
""" % locals()

start()
