# 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.
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:
sw.DecIndent()
sw.Write("}")
- sw.Write("_ganeti_os() {")
+ 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("}")
+
+ sw.Write("_ganeti_nodegroup() {")
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))
+ nodegroups_path = os.path.join(constants.DATA_DIR, "ssconf_nodegroups")
+ sw.Write("cat %s 2>/dev/null || :", utils.ShellQuote(nodegroups_path))
finally:
sw.DecIndent()
sw.Write("}")
sw.DecIndent()
sw.Write("}")
+ # Params: <long options with equal sign> <all options>
+ # 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'\"")
-def WriteCompReply(sw, args):
- sw.Write("""COMPREPLY=( $(compgen %s -- "$cur") )""", args)
+ sw.Write("return 1")
+ finally:
+ sw.DecIndent()
+ sw.Write("}")
+
+ # Params: <compgen options>
+ # 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, cur="\"$cur\""):
+ sw.Write("_ganeti_compgen %s -- %s", args, cur)
sw.Write("return")
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):
if opt.takes_value():
# Ignore value
for i in opt.all_names:
- ignore.append("%s=*" % utils.ShellQuote(i))
+ if i.startswith("--"):
+ ignore.append("%s=*" % utils.ShellQuote(i))
skip_one.append(utils.ShellQuote(i))
else:
ignore.extend([utils.ShellQuote(i) for i in opt.all_names])
# 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_ONE_NODEGROUP:
+ WriteCompReply(sw, "-W \"$(_ganeti_nodegroup)\"", 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):
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")
if arg.min == 1 and arg.max == 1:
cmpcode = """"$arg_idx" == %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)
+ elif arg.min <= arg.max:
+ cmpcode = (""""$arg_idx" -ge %d && "$arg_idx" -lt %d""" %
+ (last_arg_end, last_arg_end + arg.max))
else:
raise Exception("Unable to generate argument position condition")
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()
sw.Write("%s() {", funcname)
sw.IncIndent()
try:
- sw.Write('local cur="$2" prev="$3"')
- sw.Write("local i first_arg_idx choices compgenargs arg_idx")
+ sw.Write("local "
+ ' cur="${COMP_WORDS[COMP_CWORD]}"'
+ ' prev="${COMP_WORDS[COMP_CWORD-1]}"'
+ ' i first_arg_idx choices compgenargs arg_idx optcur')
+
+ sw.Write("_ganeti_dbglog cur=\"$cur\" prev=\"$prev\"")
+ sw.Write("[[ -n \"$GANETI_COMPL_LOG\" ]] &&"
+ " _ganeti_dbglog \"$(set | grep ^COMP_)\"")
sw.Write("COMPREPLY=()")
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.
"""
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 name, (_, _, optdef, _, _) in commands.items():
+ if help_option not in optdef:
+ optdef.append(help_option)
+ for opt in cli.COMMON_OPTS:
+ if opt in optdef:
+ raise Exception("Common option '%s' listed for command '%s' in %s" %
+ (opt, name, filename))
+ optdef.append(opt)
+
# Use aliases
aliases = getattr(module, "aliases", {})
if aliases:
def main():
buf = StringIO()
- sw = ShellWriter(buf)
+ sw = utils.ShellWriter(buf)
WritePreamble(sw)
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)