X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/580ef58d723fa359e477f675ea5ca40abaf3d9b4..2e6469a14ae2b2c7b547076efe3fdc8eacae6050:/autotools/build-bash-completion diff --git a/autotools/build-bash-completion b/autotools/build-bash-completion index 497db89..bcb1239 100755 --- a/autotools/build-bash-completion +++ b/autotools/build-bash-completion @@ -19,59 +19,27 @@ # 02110-1301, USA. -import imp -import optparse +"""Script to generate bash_completion script for Ganeti. + +""" + +# pylint: disable-msg=C0103 +# [C0103] Invalid name build-bash-completion + import os -import sys import re from cStringIO import StringIO from ganeti import constants from ganeti import cli from ganeti import utils +from ganeti import build # _autoconf shouldn't be imported from anywhere except constants.py, but we're # making an exception here because this script is only used at build time. from ganeti import _autoconf -class ShellWriter: - """Helper class to write scripts with indentation. - - """ - INDENT_STR = " " - - def __init__(self, fh): - self._fh = fh - self._indent = 0 - - def IncIndent(self): - """Increase indentation level by 1. - - """ - self._indent += 1 - - def DecIndent(self): - """Decrease indentation level by 1. - - """ - assert self._indent > 0 - self._indent -= 1 - - def Write(self, txt, *args): - """Write line to output file. - - """ - self._fh.write(self._indent * self.INDENT_STR) - - if args: - self._fh.write(txt % args) - else: - self._fh.write(txt) - - self._fh.write("\n") - - def WritePreamble(sw): """Writes the script preamble. @@ -81,6 +49,28 @@ def WritePreamble(sw): sw.Write("# This script is automatically generated at build time.") sw.Write("# Do not modify manually.") + sw.Write("_ganeti_dbglog() {") + sw.IncIndent() + try: + sw.Write("if [[ -n \"$GANETI_COMPL_LOG\" ]]; then") + sw.IncIndent() + try: + sw.Write("{") + sw.IncIndent() + try: + sw.Write("echo ---") + sw.Write("echo \"$@\"") + sw.Write("echo") + finally: + sw.DecIndent() + sw.Write("} >> $GANETI_COMPL_LOG") + finally: + sw.DecIndent() + sw.Write("fi") + finally: + sw.DecIndent() + sw.Write("}") + sw.Write("_ganeti_nodes() {") sw.IncIndent() try: @@ -112,16 +102,20 @@ def WritePreamble(sw): sw.DecIndent() sw.Write("}") - sw.Write("_ganeti_os() {") - sw.IncIndent() - try: - # FIXME: Make querying the master for all OSes cheap - for path in constants.OS_SEARCH_PATH: - sw.Write("( shopt -s nullglob && cd %s 2>/dev/null && echo * || : )", - utils.ShellQuote(path)) - finally: - sw.DecIndent() - sw.Write("}") + for (fnname, paths) in [ + ("os", constants.OS_SEARCH_PATH), + ("iallocator", constants.IALLOCATOR_SEARCH_PATH), + ]: + sw.Write("_ganeti_%s() {", fnname) + sw.IncIndent() + try: + # FIXME: Make querying the master for all OSes cheap + for path in paths: + sw.Write("( shopt -s nullglob && cd %s 2>/dev/null && echo * || : )", + utils.ShellQuote(path)) + finally: + sw.DecIndent() + sw.Write("}") # Params: # Result variable: $first_arg_idx @@ -182,9 +176,48 @@ def WritePreamble(sw): sw.DecIndent() sw.Write("}") + # Params: + # Result variable: $optcur + sw.Write("_ganeti_checkopt() {") + sw.IncIndent() + try: + sw.Write("""if [[ -n "$1" && "$cur" == @($1) ]]; then""") + sw.IncIndent() + try: + sw.Write("optcur=\"${cur#--*=}\"") + sw.Write("return 0") + finally: + sw.DecIndent() + sw.Write("""elif [[ -n "$2" && "$prev" == @($2) ]]; then""") + sw.IncIndent() + try: + sw.Write("optcur=\"$cur\"") + sw.Write("return 0") + finally: + sw.DecIndent() + sw.Write("fi") + + sw.Write("_ganeti_dbglog optcur=\"'$optcur'\"") + + sw.Write("return 1") + finally: + sw.DecIndent() + sw.Write("}") + + # Params: + # Result variable: $COMPREPLY + sw.Write("_ganeti_compgen() {") + sw.IncIndent() + try: + sw.Write("""COMPREPLY=( $(compgen "$@") )""") + sw.Write("_ganeti_dbglog COMPREPLY=\"${COMPREPLY[@]}\"") + finally: + sw.DecIndent() + sw.Write("}") + -def WriteCompReply(sw, args): - sw.Write("""COMPREPLY=( $(compgen %s -- "$cur") )""", args) +def WriteCompReply(sw, args, cur="\"$cur\""): + sw.Write("_ganeti_compgen %s -- %s", args, cur) sw.Write("return") @@ -198,6 +231,8 @@ class CompletionWriter: self.args = args for opt in opts: + # While documented, these variables aren't seen as public attributes by + # pylint. pylint: disable-msg=W0212 opt.all_names = sorted(opt._short_opts + opt._long_opts) def _FindFirstArgument(self, sw): @@ -240,37 +275,109 @@ class CompletionWriter: # Only static choices implemented so far (e.g. no node list) suggest = getattr(opt, "completion_suggest", None) + # our custom option type + if opt.type == "bool": + suggest = ["yes", "no"] + if not suggest: suggest = opt.choices - if suggest: - suggest_text = " ".join(sorted(suggest)) + if (isinstance(suggest, (int, long)) and + suggest in cli.OPT_COMPL_ALL): + key = suggest + elif suggest: + key = " ".join(sorted(suggest)) else: - suggest_text = "" + key = "" - values.setdefault(suggest_text, []).extend(opt.all_names) + values.setdefault(key, []).extend(opt.all_names) # Don't write any code if there are no option values if not values: return - sw.Write("if [[ $COMP_CWORD -gt %s ]]; then", self.arg_offset + 1) - sw.IncIndent() - try: - sw.Write("""case "$prev" in""") - for (choices, names) in values.iteritems(): - # TODO: Implement completion for --foo=bar form - sw.Write("%s)", "|".join([utils.ShellQuote(i) for i in names])) - sw.IncIndent() - try: - WriteCompReply(sw, "-W %s" % utils.ShellQuote(choices)) - finally: - sw.DecIndent() - sw.Write(";;") - sw.Write("""esac""") - finally: - sw.DecIndent() - sw.Write("""fi""") + cur = "\"$optcur\"" + + wrote_opt = False + + for (suggest, allnames) in values.iteritems(): + longnames = [i for i in allnames if i.startswith("--")] + + if wrote_opt: + condcmd = "elif" + else: + condcmd = "if" + + sw.Write("%s _ganeti_checkopt %s %s; then", condcmd, + utils.ShellQuote("|".join(["%s=*" % i for i in longnames])), + utils.ShellQuote("|".join(allnames))) + sw.IncIndent() + try: + if suggest == cli.OPT_COMPL_MANY_NODES: + # TODO: Implement comma-separated values + WriteCompReply(sw, "-W ''", cur=cur) + elif suggest == cli.OPT_COMPL_ONE_NODE: + WriteCompReply(sw, "-W \"$(_ganeti_nodes)\"", cur=cur) + elif suggest == cli.OPT_COMPL_ONE_INSTANCE: + WriteCompReply(sw, "-W \"$(_ganeti_instances)\"", cur=cur) + elif suggest == cli.OPT_COMPL_ONE_OS: + WriteCompReply(sw, "-W \"$(_ganeti_os)\"", cur=cur) + elif suggest == cli.OPT_COMPL_ONE_IALLOCATOR: + WriteCompReply(sw, "-W \"$(_ganeti_iallocator)\"", cur=cur) + elif suggest == cli.OPT_COMPL_INST_ADD_NODES: + sw.Write("local tmp= node1= pfx= curvalue=\"${optcur#*:}\"") + + sw.Write("if [[ \"$optcur\" == *:* ]]; then") + sw.IncIndent() + try: + sw.Write("node1=\"${optcur%%:*}\"") + + sw.Write("if [[ \"$COMP_WORDBREAKS\" != *:* ]]; then") + sw.IncIndent() + try: + sw.Write("pfx=\"$node1:\"") + finally: + sw.DecIndent() + sw.Write("fi") + finally: + sw.DecIndent() + sw.Write("fi") + + sw.Write("_ganeti_dbglog pfx=\"'$pfx'\" curvalue=\"'$curvalue'\"" + " node1=\"'$node1'\"") + + sw.Write("for i in $(_ganeti_nodes); do") + sw.IncIndent() + try: + sw.Write("if [[ -z \"$node1\" ]]; then") + sw.IncIndent() + try: + sw.Write("tmp=\"$tmp $i $i:\"") + finally: + sw.DecIndent() + sw.Write("elif [[ \"$i\" != \"$node1\" ]]; then") + sw.IncIndent() + try: + sw.Write("tmp=\"$tmp $i\"") + finally: + sw.DecIndent() + sw.Write("fi") + finally: + sw.DecIndent() + sw.Write("done") + + WriteCompReply(sw, "-P \"$pfx\" -W \"$tmp\"", cur="\"$curvalue\"") + else: + WriteCompReply(sw, "-W %s" % utils.ShellQuote(suggest), cur=cur) + finally: + sw.DecIndent() + + wrote_opt = True + + if wrote_opt: + sw.Write("fi") + + return def _CompleteArguments(self, sw): if not (self.opts or self.args): @@ -318,6 +425,8 @@ class CompletionWriter: choices = "$(_ganeti_nodes)" elif isinstance(arg, cli.ArgJobId): choices = "$(_ganeti_jobs)" + elif isinstance(arg, cli.ArgOs): + choices = "$(_ganeti_os)" elif isinstance(arg, cli.ArgFile): choices = "" compgenargs.append("-f") @@ -332,11 +441,11 @@ class CompletionWriter: if arg.min == 1 and arg.max == 1: cmpcode = """"$arg_idx" == %d""" % (last_arg_end) + elif arg.max is None: + cmpcode = """"$arg_idx" -ge %d""" % (last_arg_end) elif arg.min <= arg.max: cmpcode = (""""$arg_idx" -ge %d && "$arg_idx" -lt %d""" % (last_arg_end, last_arg_end + arg.max)) - elif arg.max is None: - cmpcode = """"$arg_idx" -ge %d""" % (last_arg_end) else: raise Exception("Unable to generate argument position condition") @@ -354,7 +463,8 @@ class CompletionWriter: if choices: sw.Write("""choices="$choices "%s""", choices) if compgenargs: - sw.Write("compgenargs=%s", utils.ShellQuote(" ".join(compgenargs))) + sw.Write("compgenargs=%s", + utils.ShellQuote(" ".join(compgenargs))) finally: sw.DecIndent() @@ -394,13 +504,13 @@ def WriteCompletion(sw, scriptname, funcname, sw.IncIndent() try: sw.Write("local " - ' cur="${COMP_WORDS[$COMP_CWORD]}"' + ' cur="${COMP_WORDS[COMP_CWORD]}"' ' prev="${COMP_WORDS[COMP_CWORD-1]}"' - ' i first_arg_idx choices compgenargs arg_idx') + ' i first_arg_idx choices compgenargs arg_idx optcur') - # Useful for debugging: - #sw.Write("echo cur=\"$cur\" prev=\"$prev\"") - #sw.Write("set | grep ^COMP_") + sw.Write("_ganeti_dbglog cur=\"$cur\" prev=\"$prev\"") + sw.Write("[[ -n \"$GANETI_COMPL_LOG\" ]] &&" + " _ganeti_dbglog \"$(set | grep ^COMP_)\"") sw.Write("COMPREPLY=()") @@ -449,19 +559,6 @@ def GetFunctionName(name): return "_" + re.sub(r"[^a-z0-9]+", "_", name.lower()) -def LoadModule(filename): - """Loads an external module by filename. - - """ - (name, ext) = os.path.splitext(filename) - - fh = open(filename, "U") - try: - return imp.load_module(name, fh, filename, (ext, "U", imp.PY_SOURCE)) - finally: - fh.close() - - def GetCommands(filename, module): """Returns the commands defined in a module. @@ -470,10 +567,20 @@ def GetCommands(filename, module): """ try: commands = getattr(module, "commands") - except AttributeError, err: + except AttributeError: raise Exception("Script %s doesn't have 'commands' attribute" % filename) + # Add the implicit "--help" option + help_option = cli.cli_option("-h", "--help", default=False, + action="store_true") + + for (_, _, optdef, _, _) in commands.itervalues(): + if help_option not in optdef: + optdef.append(help_option) + if cli.DEBUG_OPT not in optdef: + optdef.append(cli.DEBUG_OPT) + # Use aliases aliases = getattr(module, "aliases", {}) if aliases: @@ -486,7 +593,7 @@ def GetCommands(filename, module): def main(): buf = StringIO() - sw = ShellWriter(buf) + sw = utils.ShellWriter(buf) WritePreamble(sw) @@ -496,10 +603,11 @@ def main(): WriteCompletion(sw, scriptname, GetFunctionName(scriptname), - commands=GetCommands(filename, LoadModule(filename))) + commands=GetCommands(filename, + build.LoadModule(filename))) # Burnin script - burnin = LoadModule("tools/burnin") + burnin = build.LoadModule("tools/burnin") WriteCompletion(sw, "%s/burnin" % constants.TOOLSDIR, "_ganeti_burnin", opts=burnin.OPTIONS, args=burnin.ARGUMENTS)