212 lines
6.2 KiB
Python
Executable File
212 lines
6.2 KiB
Python
Executable File
#!/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()
|