# [C0103] Invalid name build-bash-completion
import os
+import os.path
import re
import itertools
import optparse
sw.DecIndent()
sw.Write("}")
+ sw.Write("_ganeti_network() {")
+ sw.IncIndent()
+ try:
+ networks_path = os.path.join(pathutils.DATA_DIR, "ssconf_networks")
+ sw.Write("cat %s 2>/dev/null || :", utils.ShellQuote(networks_path))
+ finally:
+ sw.DecIndent()
+ sw.Write("}")
+
# Params: <offset> <options with values> <options without values>
# Result variable: $first_arg_idx
sw.Write("_ganeti_find_first_arg() {")
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_ONE_NETWORK:
+ WriteCompReply(sw, "-W \"$(_ganeti_network)\"", cur=cur)
elif suggest == cli.OPT_COMPL_INST_ADD_NODES:
sw.Write("local tmp= node1= pfx= curvalue=\"${optcur#*:}\"")
choices = "$(_ganeti_nodes)"
elif isinstance(arg, cli.ArgGroup):
choices = "$(_ganeti_nodegroup)"
+ elif isinstance(arg, cli.ArgNetwork):
+ choices = "$(_ganeti_network)"
elif isinstance(arg, cli.ArgJobId):
choices = "$(_ganeti_jobs)"
elif isinstance(arg, cli.ArgOs):
@param commands: List of all subcommands in this program
"""
- sw.Write("%s_inner() {", funcname)
+ sw.Write("%s() {", funcname)
sw.IncIndent()
try:
- sw.Write("local i first_arg_idx choices compgenargs arg_idx optcur"
+ sw.Write("local "
' cur="${COMP_WORDS[COMP_CWORD]}"'
- ' prev="${COMP_WORDS[COMP_CWORD-1]}"')
+ ' prev="${COMP_WORDS[COMP_CWORD-1]}"'
+ ' i first_arg_idx choices compgenargs arg_idx optcur')
if support_debug:
sw.Write("_gnt_log cur=\"$cur\" prev=\"$prev\"")
sw.DecIndent()
sw.Write("}")
- # Wrapper function to always enable extglob (needed for advanced pattern
- # matching)
- sw.Write("%s() {", funcname)
- sw.IncIndent()
- try:
- # Get current state of extglob
- sw.Write("local -r eg=$(shopt -p extglob || :)")
-
- # Enable extglob
- sw.Write("shopt -s extglob")
-
- sw.Write("%s_inner \"$@\"", funcname)
-
- # Reset extglob to original value
- sw.Write("[[ -n \"$eg\" ]] && $eg")
- finally:
- sw.DecIndent()
- sw.Write("}")
-
sw.Write("complete -F %s -o filenames %s",
utils.ShellQuote(funcname),
utils.ShellQuote(scriptname))
return commands
+def HaskellOptToOptParse(opts, kind):
+ """Converts a Haskell options to Python cli_options.
+
+ @type opts: string
+ @param opts: comma-separated string with short and long options
+ @type kind: string
+ @param kind: type generated by Common.hs/complToText; needs to be
+ kept in sync
+
+ """
+ # pylint: disable=W0142
+ # since we pass *opts in a number of places
+ opts = opts.split(",")
+ if kind == "none":
+ return cli.cli_option(*opts, action="store_true")
+ elif kind in ["file", "string", "host", "dir", "inetaddr"]:
+ return cli.cli_option(*opts, type="string")
+ elif kind == "integer":
+ return cli.cli_option(*opts, type="int")
+ elif kind == "float":
+ return cli.cli_option(*opts, type="float")
+ elif kind == "onegroup":
+ return cli.cli_option(*opts, type="string",
+ completion_suggest=cli.OPT_COMPL_ONE_NODEGROUP)
+ elif kind == "onenode":
+ return cli.cli_option(*opts, type="string",
+ completion_suggest=cli.OPT_COMPL_ONE_NODE)
+ elif kind == "manyinstances":
+ # FIXME: no support for many instances
+ return cli.cli_option(*opts, type="string")
+ elif kind.startswith("choices="):
+ choices = kind[len("choices="):].split(",")
+ return cli.cli_option(*opts, type="choice", choices=choices)
+ else:
+ # FIXME: there are many other currently unused completion types,
+ # should be added on an as-needed basis
+ raise Exception("Unhandled option kind '%s'" % kind)
+
+
+#: serialised kind to arg type
+_ARG_MAP = {
+ "choices": cli.ArgChoice,
+ "command": cli.ArgCommand,
+ "file": cli.ArgFile,
+ "host": cli.ArgHost,
+ "jobid": cli.ArgJobId,
+ "onegroup": cli.ArgGroup,
+ "oneinstance": cli.ArgInstance,
+ "onenode": cli.ArgNode,
+ "oneos": cli.ArgOs,
+ "string": cli.ArgUnknown,
+ "suggests": cli.ArgSuggest,
+ }
+
+
+def HaskellArgToCliArg(kind, min_cnt, max_cnt):
+ """Converts a Haskell options to Python _Argument.
+
+ @type kind: string
+ @param kind: type generated by Common.hs/argComplToText; needs to be
+ kept in sync
+
+ """
+ min_cnt = int(min_cnt)
+ if max_cnt == "none":
+ max_cnt = None
+ else:
+ max_cnt = int(max_cnt)
+ # pylint: disable=W0142
+ # since we pass **kwargs
+ kwargs = {"min": min_cnt, "max": max_cnt}
+
+ if kind.startswith("choices=") or kind.startswith("suggest="):
+ (kind, choices) = kind.split("=", 1)
+ kwargs["choices"] = choices.split(",")
+
+ if kind not in _ARG_MAP:
+ raise Exception("Unhandled argument kind '%s'" % kind)
+ else:
+ return _ARG_MAP[kind](**kwargs)
+
+
+def WriteHaskellCompletion(sw, script, htools=True, debug=True):
+ """Generates completion information for a Haskell program.
+
+ This Converts completion info from a Haskell program into 'fake'
+ cli_opts and then builds completion for them.
+
+ """
+ if htools:
+ cmd = "./htools/htools"
+ env = {"HTOOLS": script}
+ script_name = script
+ func_name = "htools_%s" % script
+ else:
+ cmd = "./" + script
+ env = {}
+ script_name = os.path.basename(script)
+ func_name = script_name
+ func_name = func_name.replace("-", "_")
+ output = utils.RunCmd([cmd, "--help-completion"], env=env, cwd=".").output
+ cli_opts = []
+ args = []
+ for line in output.splitlines():
+ v = line.split(None)
+ exc = lambda msg: Exception("Invalid %s output from %s: %s" %
+ (msg, script, v))
+ if len(v) < 2:
+ raise exc("help completion")
+ if v[0].startswith("-"):
+ if len(v) != 2:
+ raise exc("option format")
+ (opts, kind) = v
+ cli_opts.append(HaskellOptToOptParse(opts, kind))
+ else:
+ if len(v) != 3:
+ raise exc("argument format")
+ (kind, min_cnt, max_cnt) = v
+ args.append(HaskellArgToCliArg(kind, min_cnt, max_cnt))
+ WriteCompletion(sw, script_name, func_name, debug, opts=cli_opts, args=args)
+
+
def main():
parser = optparse.OptionParser(usage="%prog [--compact]")
parser.add_option("--compact", action="store_true",
buf = StringIO()
sw = utils.ShellWriter(buf, indent=not options.compact)
+ # Remember original state of extglob and enable it (required for pattern
+ # matching; must be enabled while parsing script)
+ sw.Write("gnt_shopt_extglob=$(shopt -p extglob || :)")
+ sw.Write("shopt -s extglob")
+
WritePreamble(sw, not options.compact)
# gnt-* scripts
not options.compact,
opts=burnin.OPTIONS, args=burnin.ARGUMENTS)
+ # ganeti-cleaner
+ WriteHaskellCompletion(sw, "daemons/ganeti-cleaner", htools=False,
+ debug=not options.compact)
+
+ # htools, if enabled
+ if _autoconf.HTOOLS:
+ for script in _autoconf.HTOOLS_PROGS:
+ WriteHaskellCompletion(sw, script, htools=True,
+ debug=not options.compact)
+
+ # ganeti-confd, if enabled
+ if _autoconf.ENABLE_CONFD:
+ WriteHaskellCompletion(sw, "htools/ganeti-confd", htools=False,
+ debug=not options.compact)
+
+ # Reset extglob to original value
+ sw.Write("[[ -n \"$gnt_shopt_extglob\" ]] && $gnt_shopt_extglob")
+ sw.Write("unset gnt_shopt_extglob")
+
print buf.getvalue()