Merge ganeti-master-cleaner back into ganeti-cleaner
[ganeti-local] / autotools / build-bash-completion
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Script to generate bash_completion script for Ganeti.
23
24 """
25
26 # pylint: disable=C0103
27 # [C0103] Invalid name build-bash-completion
28
29 import os
30 import os.path
31 import re
32 import itertools
33 import optparse
34 from cStringIO import StringIO
35
36 from ganeti import constants
37 from ganeti import cli
38 from ganeti import utils
39 from ganeti import build
40 from ganeti import pathutils
41
42 # _autoconf shouldn't be imported from anywhere except constants.py, but we're
43 # making an exception here because this script is only used at build time.
44 from ganeti import _autoconf
45
46 #: Regular expression describing desired format of option names. Long names can
47 #: contain lowercase characters, numbers and dashes only.
48 _OPT_NAME_RE = re.compile(r"^-[a-zA-Z0-9]|--[a-z][-a-z0-9]+$")
49
50
51 def WritePreamble(sw, support_debug):
52   """Writes the script preamble.
53
54   Helper functions should be written here.
55
56   """
57   sw.Write("# This script is automatically generated at build time.")
58   sw.Write("# Do not modify manually.")
59
60   if support_debug:
61     sw.Write("_gnt_log() {")
62     sw.IncIndent()
63     try:
64       sw.Write("if [[ -n \"$GANETI_COMPL_LOG\" ]]; then")
65       sw.IncIndent()
66       try:
67         sw.Write("{")
68         sw.IncIndent()
69         try:
70           sw.Write("echo ---")
71           sw.Write("echo \"$@\"")
72           sw.Write("echo")
73         finally:
74           sw.DecIndent()
75         sw.Write("} >> $GANETI_COMPL_LOG")
76       finally:
77         sw.DecIndent()
78       sw.Write("fi")
79     finally:
80       sw.DecIndent()
81     sw.Write("}")
82
83   sw.Write("_ganeti_nodes() {")
84   sw.IncIndent()
85   try:
86     node_list_path = os.path.join(pathutils.DATA_DIR, "ssconf_node_list")
87     sw.Write("cat %s 2>/dev/null || :", utils.ShellQuote(node_list_path))
88   finally:
89     sw.DecIndent()
90   sw.Write("}")
91
92   sw.Write("_ganeti_instances() {")
93   sw.IncIndent()
94   try:
95     instance_list_path = os.path.join(pathutils.DATA_DIR,
96                                       "ssconf_instance_list")
97     sw.Write("cat %s 2>/dev/null || :", utils.ShellQuote(instance_list_path))
98   finally:
99     sw.DecIndent()
100   sw.Write("}")
101
102   sw.Write("_ganeti_jobs() {")
103   sw.IncIndent()
104   try:
105     # FIXME: this is really going into the internals of the job queue
106     sw.Write(("local jlist=$( shopt -s nullglob &&"
107               " cd %s 2>/dev/null && echo job-* || : )"),
108              utils.ShellQuote(pathutils.QUEUE_DIR))
109     sw.Write('echo "${jlist//job-/}"')
110   finally:
111     sw.DecIndent()
112   sw.Write("}")
113
114   for (fnname, paths) in [
115     ("os", pathutils.OS_SEARCH_PATH),
116     ("iallocator", constants.IALLOCATOR_SEARCH_PATH),
117     ]:
118     sw.Write("_ganeti_%s() {", fnname)
119     sw.IncIndent()
120     try:
121       # FIXME: Make querying the master for all OSes cheap
122       for path in paths:
123         sw.Write("( shopt -s nullglob && cd %s 2>/dev/null && echo * || : )",
124                  utils.ShellQuote(path))
125     finally:
126       sw.DecIndent()
127     sw.Write("}")
128
129   sw.Write("_ganeti_nodegroup() {")
130   sw.IncIndent()
131   try:
132     nodegroups_path = os.path.join(pathutils.DATA_DIR, "ssconf_nodegroups")
133     sw.Write("cat %s 2>/dev/null || :", utils.ShellQuote(nodegroups_path))
134   finally:
135     sw.DecIndent()
136   sw.Write("}")
137
138   # Params: <offset> <options with values> <options without values>
139   # Result variable: $first_arg_idx
140   sw.Write("_ganeti_find_first_arg() {")
141   sw.IncIndent()
142   try:
143     sw.Write("local w i")
144
145     sw.Write("first_arg_idx=")
146     sw.Write("for (( i=$1; i < COMP_CWORD; ++i )); do")
147     sw.IncIndent()
148     try:
149       sw.Write("w=${COMP_WORDS[$i]}")
150
151       # Skip option value
152       sw.Write("""if [[ -n "$2" && "$w" == @($2) ]]; then let ++i""")
153
154       # Skip
155       sw.Write("""elif [[ -n "$3" && "$w" == @($3) ]]; then :""")
156
157       # Ah, we found the first argument
158       sw.Write("else first_arg_idx=$i; break;")
159       sw.Write("fi")
160     finally:
161       sw.DecIndent()
162     sw.Write("done")
163   finally:
164     sw.DecIndent()
165   sw.Write("}")
166
167   # Params: <list of options separated by space>
168   # Input variable: $first_arg_idx
169   # Result variables: $arg_idx, $choices
170   sw.Write("_ganeti_list_options() {")
171   sw.IncIndent()
172   try:
173     sw.Write("""if [[ -z "$first_arg_idx" ]]; then""")
174     sw.IncIndent()
175     try:
176       sw.Write("arg_idx=0")
177       # Show options only if the current word starts with a dash
178       sw.Write("""if [[ "$cur" == -* ]]; then""")
179       sw.IncIndent()
180       try:
181         sw.Write("choices=$1")
182       finally:
183         sw.DecIndent()
184       sw.Write("fi")
185       sw.Write("return")
186     finally:
187       sw.DecIndent()
188     sw.Write("fi")
189
190     # Calculate position of current argument
191     sw.Write("arg_idx=$(( COMP_CWORD - first_arg_idx ))")
192     sw.Write("choices=")
193   finally:
194     sw.DecIndent()
195   sw.Write("}")
196
197   # Params: <long options with equal sign> <all options>
198   # Result variable: $optcur
199   sw.Write("_gnt_checkopt() {")
200   sw.IncIndent()
201   try:
202     sw.Write("""if [[ -n "$1" && "$cur" == @($1) ]]; then""")
203     sw.IncIndent()
204     try:
205       sw.Write("optcur=\"${cur#--*=}\"")
206       sw.Write("return 0")
207     finally:
208       sw.DecIndent()
209     sw.Write("""elif [[ -n "$2" && "$prev" == @($2) ]]; then""")
210     sw.IncIndent()
211     try:
212       sw.Write("optcur=\"$cur\"")
213       sw.Write("return 0")
214     finally:
215       sw.DecIndent()
216     sw.Write("fi")
217
218     if support_debug:
219       sw.Write("_gnt_log optcur=\"'$optcur'\"")
220
221     sw.Write("return 1")
222   finally:
223     sw.DecIndent()
224   sw.Write("}")
225
226   # Params: <compgen options>
227   # Result variable: $COMPREPLY
228   sw.Write("_gnt_compgen() {")
229   sw.IncIndent()
230   try:
231     sw.Write("""COMPREPLY=( $(compgen "$@") )""")
232     if support_debug:
233       sw.Write("_gnt_log COMPREPLY=\"${COMPREPLY[@]}\"")
234   finally:
235     sw.DecIndent()
236   sw.Write("}")
237
238
239 def WriteCompReply(sw, args, cur="\"$cur\""):
240   sw.Write("_gnt_compgen %s -- %s", args, cur)
241   sw.Write("return")
242
243
244 class CompletionWriter:
245   """Command completion writer class.
246
247   """
248   def __init__(self, arg_offset, opts, args, support_debug):
249     self.arg_offset = arg_offset
250     self.opts = opts
251     self.args = args
252     self.support_debug = support_debug
253
254     for opt in opts:
255       # While documented, these variables aren't seen as public attributes by
256       # pylint. pylint: disable=W0212
257       opt.all_names = sorted(opt._short_opts + opt._long_opts)
258
259       invalid = list(itertools.ifilterfalse(_OPT_NAME_RE.match, opt.all_names))
260       if invalid:
261         raise Exception("Option names don't match regular expression '%s': %s" %
262                         (_OPT_NAME_RE.pattern, utils.CommaJoin(invalid)))
263
264   def _FindFirstArgument(self, sw):
265     ignore = []
266     skip_one = []
267
268     for opt in self.opts:
269       if opt.takes_value():
270         # Ignore value
271         for i in opt.all_names:
272           if i.startswith("--"):
273             ignore.append("%s=*" % utils.ShellQuote(i))
274           skip_one.append(utils.ShellQuote(i))
275       else:
276         ignore.extend([utils.ShellQuote(i) for i in opt.all_names])
277
278     ignore = sorted(utils.UniqueSequence(ignore))
279     skip_one = sorted(utils.UniqueSequence(skip_one))
280
281     if ignore or skip_one:
282       # Try to locate first argument
283       sw.Write("_ganeti_find_first_arg %s %s %s",
284                self.arg_offset + 1,
285                utils.ShellQuote("|".join(skip_one)),
286                utils.ShellQuote("|".join(ignore)))
287     else:
288       # When there are no options the first argument is always at position
289       # offset + 1
290       sw.Write("first_arg_idx=%s", self.arg_offset + 1)
291
292   def _CompleteOptionValues(self, sw):
293     # Group by values
294     # "values" -> [optname1, optname2, ...]
295     values = {}
296
297     for opt in self.opts:
298       if not opt.takes_value():
299         continue
300
301       # Only static choices implemented so far (e.g. no node list)
302       suggest = getattr(opt, "completion_suggest", None)
303
304       # our custom option type
305       if opt.type == "bool":
306         suggest = ["yes", "no"]
307
308       if not suggest:
309         suggest = opt.choices
310
311       if (isinstance(suggest, (int, long)) and
312           suggest in cli.OPT_COMPL_ALL):
313         key = suggest
314       elif suggest:
315         key = " ".join(sorted(suggest))
316       else:
317         key = ""
318
319       values.setdefault(key, []).extend(opt.all_names)
320
321     # Don't write any code if there are no option values
322     if not values:
323       return
324
325     cur = "\"$optcur\""
326
327     wrote_opt = False
328
329     for (suggest, allnames) in values.items():
330       longnames = [i for i in allnames if i.startswith("--")]
331
332       if wrote_opt:
333         condcmd = "elif"
334       else:
335         condcmd = "if"
336
337       sw.Write("%s _gnt_checkopt %s %s; then", condcmd,
338                utils.ShellQuote("|".join(["%s=*" % i for i in longnames])),
339                utils.ShellQuote("|".join(allnames)))
340       sw.IncIndent()
341       try:
342         if suggest == cli.OPT_COMPL_MANY_NODES:
343           # TODO: Implement comma-separated values
344           WriteCompReply(sw, "-W ''", cur=cur)
345         elif suggest == cli.OPT_COMPL_ONE_NODE:
346           WriteCompReply(sw, "-W \"$(_ganeti_nodes)\"", cur=cur)
347         elif suggest == cli.OPT_COMPL_ONE_INSTANCE:
348           WriteCompReply(sw, "-W \"$(_ganeti_instances)\"", cur=cur)
349         elif suggest == cli.OPT_COMPL_ONE_OS:
350           WriteCompReply(sw, "-W \"$(_ganeti_os)\"", cur=cur)
351         elif suggest == cli.OPT_COMPL_ONE_IALLOCATOR:
352           WriteCompReply(sw, "-W \"$(_ganeti_iallocator)\"", cur=cur)
353         elif suggest == cli.OPT_COMPL_ONE_NODEGROUP:
354           WriteCompReply(sw, "-W \"$(_ganeti_nodegroup)\"", cur=cur)
355         elif suggest == cli.OPT_COMPL_INST_ADD_NODES:
356           sw.Write("local tmp= node1= pfx= curvalue=\"${optcur#*:}\"")
357
358           sw.Write("if [[ \"$optcur\" == *:* ]]; then")
359           sw.IncIndent()
360           try:
361             sw.Write("node1=\"${optcur%%:*}\"")
362
363             sw.Write("if [[ \"$COMP_WORDBREAKS\" != *:* ]]; then")
364             sw.IncIndent()
365             try:
366               sw.Write("pfx=\"$node1:\"")
367             finally:
368               sw.DecIndent()
369             sw.Write("fi")
370           finally:
371             sw.DecIndent()
372           sw.Write("fi")
373
374           if self.support_debug:
375             sw.Write("_gnt_log pfx=\"'$pfx'\" curvalue=\"'$curvalue'\""
376                      " node1=\"'$node1'\"")
377
378           sw.Write("for i in $(_ganeti_nodes); do")
379           sw.IncIndent()
380           try:
381             sw.Write("if [[ -z \"$node1\" ]]; then")
382             sw.IncIndent()
383             try:
384               sw.Write("tmp=\"$tmp $i $i:\"")
385             finally:
386               sw.DecIndent()
387             sw.Write("elif [[ \"$i\" != \"$node1\" ]]; then")
388             sw.IncIndent()
389             try:
390               sw.Write("tmp=\"$tmp $i\"")
391             finally:
392               sw.DecIndent()
393             sw.Write("fi")
394           finally:
395             sw.DecIndent()
396           sw.Write("done")
397
398           WriteCompReply(sw, "-P \"$pfx\" -W \"$tmp\"", cur="\"$curvalue\"")
399         else:
400           WriteCompReply(sw, "-W %s" % utils.ShellQuote(suggest), cur=cur)
401       finally:
402         sw.DecIndent()
403
404       wrote_opt = True
405
406     if wrote_opt:
407       sw.Write("fi")
408
409     return
410
411   def _CompleteArguments(self, sw):
412     if not (self.opts or self.args):
413       return
414
415     all_option_names = []
416     for opt in self.opts:
417       all_option_names.extend(opt.all_names)
418     all_option_names.sort()
419
420     # List options if no argument has been specified yet
421     sw.Write("_ganeti_list_options %s",
422              utils.ShellQuote(" ".join(all_option_names)))
423
424     if self.args:
425       last_idx = len(self.args) - 1
426       last_arg_end = 0
427       varlen_arg_idx = None
428       wrote_arg = False
429
430       sw.Write("compgenargs=")
431
432       for idx, arg in enumerate(self.args):
433         assert arg.min is not None and arg.min >= 0
434         assert not (idx < last_idx and arg.max is None)
435
436         if arg.min != arg.max or arg.max is None:
437           if varlen_arg_idx is not None:
438             raise Exception("Only one argument can have a variable length")
439           varlen_arg_idx = idx
440
441         compgenargs = []
442
443         if isinstance(arg, cli.ArgUnknown):
444           choices = ""
445         elif isinstance(arg, cli.ArgSuggest):
446           choices = utils.ShellQuote(" ".join(arg.choices))
447         elif isinstance(arg, cli.ArgInstance):
448           choices = "$(_ganeti_instances)"
449         elif isinstance(arg, cli.ArgNode):
450           choices = "$(_ganeti_nodes)"
451         elif isinstance(arg, cli.ArgGroup):
452           choices = "$(_ganeti_nodegroup)"
453         elif isinstance(arg, cli.ArgJobId):
454           choices = "$(_ganeti_jobs)"
455         elif isinstance(arg, cli.ArgOs):
456           choices = "$(_ganeti_os)"
457         elif isinstance(arg, cli.ArgFile):
458           choices = ""
459           compgenargs.append("-f")
460         elif isinstance(arg, cli.ArgCommand):
461           choices = ""
462           compgenargs.append("-c")
463         elif isinstance(arg, cli.ArgHost):
464           choices = ""
465           compgenargs.append("-A hostname")
466         else:
467           raise Exception("Unknown argument type %r" % arg)
468
469         if arg.min == 1 and arg.max == 1:
470           cmpcode = """"$arg_idx" == %d""" % (last_arg_end)
471         elif arg.max is None:
472           cmpcode = """"$arg_idx" -ge %d""" % (last_arg_end)
473         elif arg.min <= arg.max:
474           cmpcode = (""""$arg_idx" -ge %d && "$arg_idx" -lt %d""" %
475                      (last_arg_end, last_arg_end + arg.max))
476         else:
477           raise Exception("Unable to generate argument position condition")
478
479         last_arg_end += arg.min
480
481         if choices or compgenargs:
482           if wrote_arg:
483             condcmd = "elif"
484           else:
485             condcmd = "if"
486
487           sw.Write("""%s [[ %s ]]; then""", condcmd, cmpcode)
488           sw.IncIndent()
489           try:
490             if choices:
491               sw.Write("""choices="$choices "%s""", choices)
492             if compgenargs:
493               sw.Write("compgenargs=%s",
494                        utils.ShellQuote(" ".join(compgenargs)))
495           finally:
496             sw.DecIndent()
497
498           wrote_arg = True
499
500       if wrote_arg:
501         sw.Write("fi")
502
503     if self.args:
504       WriteCompReply(sw, """-W "$choices" $compgenargs""")
505     else:
506       # $compgenargs exists only if there are arguments
507       WriteCompReply(sw, '-W "$choices"')
508
509   def WriteTo(self, sw):
510     self._FindFirstArgument(sw)
511     self._CompleteOptionValues(sw)
512     self._CompleteArguments(sw)
513
514
515 def WriteCompletion(sw, scriptname, funcname, support_debug,
516                     commands=None,
517                     opts=None, args=None):
518   """Writes the completion code for one command.
519
520   @type sw: ShellWriter
521   @param sw: Script writer
522   @type scriptname: string
523   @param scriptname: Name of command line program
524   @type funcname: string
525   @param funcname: Shell function name
526   @type commands: list
527   @param commands: List of all subcommands in this program
528
529   """
530   sw.Write("%s() {", funcname)
531   sw.IncIndent()
532   try:
533     sw.Write("local "
534              ' cur="${COMP_WORDS[COMP_CWORD]}"'
535              ' prev="${COMP_WORDS[COMP_CWORD-1]}"'
536              ' i first_arg_idx choices compgenargs arg_idx optcur')
537
538     if support_debug:
539       sw.Write("_gnt_log cur=\"$cur\" prev=\"$prev\"")
540       sw.Write("[[ -n \"$GANETI_COMPL_LOG\" ]] &&"
541                " _gnt_log \"$(set | grep ^COMP_)\"")
542
543     sw.Write("COMPREPLY=()")
544
545     if opts is not None and args is not None:
546       assert not commands
547       CompletionWriter(0, opts, args, support_debug).WriteTo(sw)
548
549     else:
550       sw.Write("""if [[ "$COMP_CWORD" == 1 ]]; then""")
551       sw.IncIndent()
552       try:
553         # Complete the command name
554         WriteCompReply(sw,
555                        ("-W %s" %
556                         utils.ShellQuote(" ".join(sorted(commands.keys())))))
557       finally:
558         sw.DecIndent()
559       sw.Write("fi")
560
561       # Group commands by arguments and options
562       grouped_cmds = {}
563       for cmd, (_, argdef, optdef, _, _) in commands.items():
564         if not (argdef or optdef):
565           continue
566         grouped_cmds.setdefault((tuple(argdef), tuple(optdef)), set()).add(cmd)
567
568       # We're doing options and arguments to commands
569       sw.Write("""case "${COMP_WORDS[1]}" in""")
570       sort_grouped = sorted(grouped_cmds.items(),
571                             key=lambda (_, y): sorted(y)[0])
572       for ((argdef, optdef), cmds) in sort_grouped:
573         assert argdef or optdef
574         sw.Write("%s)", "|".join(map(utils.ShellQuote, sorted(cmds))))
575         sw.IncIndent()
576         try:
577           CompletionWriter(1, optdef, argdef, support_debug).WriteTo(sw)
578         finally:
579           sw.DecIndent()
580         sw.Write(";;")
581       sw.Write("esac")
582   finally:
583     sw.DecIndent()
584   sw.Write("}")
585
586   sw.Write("complete -F %s -o filenames %s",
587            utils.ShellQuote(funcname),
588            utils.ShellQuote(scriptname))
589
590
591 def GetFunctionName(name):
592   return "_" + re.sub(r"[^a-z0-9]+", "_", name.lower())
593
594
595 def GetCommands(filename, module):
596   """Returns the commands defined in a module.
597
598   Aliases are also added as commands.
599
600   """
601   try:
602     commands = getattr(module, "commands")
603   except AttributeError:
604     raise Exception("Script %s doesn't have 'commands' attribute" %
605                     filename)
606
607   # Add the implicit "--help" option
608   help_option = cli.cli_option("-h", "--help", default=False,
609                                action="store_true")
610
611   for name, (_, _, optdef, _, _) in commands.items():
612     if help_option not in optdef:
613       optdef.append(help_option)
614     for opt in cli.COMMON_OPTS:
615       if opt in optdef:
616         raise Exception("Common option '%s' listed for command '%s' in %s" %
617                         (opt, name, filename))
618       optdef.append(opt)
619
620   # Use aliases
621   aliases = getattr(module, "aliases", {})
622   if aliases:
623     commands = commands.copy()
624     for name, target in aliases.items():
625       commands[name] = commands[target]
626
627   return commands
628
629
630 def HaskellOptToOptParse(opts, kind):
631   """Converts a Haskell options to Python cli_options.
632
633   @type opts: string
634   @param opts: comma-separated string with short and long options
635   @type kind: string
636   @param kind: type generated by Common.hs/complToText; needs to be
637       kept in sync
638
639   """
640   # pylint: disable=W0142
641   # since we pass *opts in a number of places
642   opts = opts.split(",")
643   if kind == "none":
644     return cli.cli_option(*opts, action="store_true")
645   elif kind in ["file", "string", "host", "dir", "inetaddr"]:
646     return cli.cli_option(*opts, type="string")
647   elif kind == "integer":
648     return cli.cli_option(*opts, type="int")
649   elif kind == "float":
650     return cli.cli_option(*opts, type="float")
651   elif kind == "onegroup":
652     return cli.cli_option(*opts, type="string",
653                            completion_suggest=cli.OPT_COMPL_ONE_NODEGROUP)
654   elif kind == "onenode":
655     return cli.cli_option(*opts, type="string",
656                           completion_suggest=cli.OPT_COMPL_ONE_NODE)
657   elif kind == "manyinstances":
658     # FIXME: no support for many instances
659     return cli.cli_option(*opts, type="string")
660   elif kind.startswith("choices="):
661     choices = kind[len("choices="):].split(",")
662     return cli.cli_option(*opts, type="choice", choices=choices)
663   else:
664     # FIXME: there are many other currently unused completion types,
665     # should be added on an as-needed basis
666     raise Exception("Unhandled option kind '%s'" % kind)
667
668
669 #: serialised kind to arg type
670 _ARG_MAP = {
671   "choices": cli.ArgChoice,
672   "command": cli.ArgCommand,
673   "file": cli.ArgFile,
674   "host": cli.ArgHost,
675   "jobid": cli.ArgJobId,
676   "onegroup": cli.ArgGroup,
677   "oneinstance": cli.ArgInstance,
678   "onenode": cli.ArgNode,
679   "oneos": cli.ArgOs,
680   "string": cli.ArgUnknown,
681   "suggests": cli.ArgSuggest,
682   }
683
684
685 def HaskellArgToCliArg(kind, min_cnt, max_cnt):
686   """Converts a Haskell options to Python _Argument.
687
688   @type kind: string
689   @param kind: type generated by Common.hs/argComplToText; needs to be
690       kept in sync
691
692   """
693   min_cnt = int(min_cnt)
694   if max_cnt == "none":
695     max_cnt = None
696   else:
697     max_cnt = int(max_cnt)
698   # pylint: disable=W0142
699   # since we pass **kwargs
700   kwargs = {"min": min_cnt, "max": max_cnt}
701
702   if kind.startswith("choices=") or kind.startswith("suggest="):
703     (kind, choices) = kind.split("=", 1)
704     kwargs["choices"] = choices.split(",")
705
706   if kind not in _ARG_MAP:
707     raise Exception("Unhandled argument kind '%s'" % kind)
708   else:
709     return _ARG_MAP[kind](**kwargs)
710
711
712 def WriteHaskellCompletion(sw, script, htools=True, debug=True):
713   """Generates completion information for a Haskell program.
714
715   This Converts completion info from a Haskell program into 'fake'
716   cli_opts and then builds completion for them.
717
718   """
719   if htools:
720     cmd = "./htools/htools"
721     env = {"HTOOLS": script}
722     script_name = script
723     func_name = "htools_%s" % script
724   else:
725     cmd = "./" + script
726     env = {}
727     script_name = os.path.basename(script)
728     func_name = script_name
729   func_name = func_name.replace("-", "_")
730   output = utils.RunCmd([cmd, "--help-completion"], env=env, cwd=".").output
731   cli_opts = []
732   args = []
733   for line in output.splitlines():
734     v = line.split(None)
735     exc = lambda msg: Exception("Invalid %s output from %s: %s" %
736                                 (msg, script, v))
737     if len(v) < 2:
738       raise exc("help completion")
739     if v[0].startswith("-"):
740       if len(v) != 2:
741         raise exc("option format")
742       (opts, kind) = v
743       cli_opts.append(HaskellOptToOptParse(opts, kind))
744     else:
745       if len(v) != 3:
746         raise exc("argument format")
747       (kind, min_cnt, max_cnt) = v
748       args.append(HaskellArgToCliArg(kind, min_cnt, max_cnt))
749   WriteCompletion(sw, script_name, func_name, debug, opts=cli_opts, args=args)
750
751
752 def main():
753   parser = optparse.OptionParser(usage="%prog [--compact]")
754   parser.add_option("--compact", action="store_true",
755                     help=("Don't indent output and don't include debugging"
756                           " facilities"))
757
758   options, args = parser.parse_args()
759   if args:
760     parser.error("Wrong number of arguments")
761
762   buf = StringIO()
763   sw = utils.ShellWriter(buf, indent=not options.compact)
764
765   # Remember original state of extglob and enable it (required for pattern
766   # matching; must be enabled while parsing script)
767   sw.Write("gnt_shopt_extglob=$(shopt -p extglob || :)")
768   sw.Write("shopt -s extglob")
769
770   WritePreamble(sw, not options.compact)
771
772   # gnt-* scripts
773   for scriptname in _autoconf.GNT_SCRIPTS:
774     filename = "scripts/%s" % scriptname
775
776     WriteCompletion(sw, scriptname, GetFunctionName(scriptname),
777                     not options.compact,
778                     commands=GetCommands(filename,
779                                          build.LoadModule(filename)))
780
781   # Burnin script
782   burnin = build.LoadModule("tools/burnin")
783   WriteCompletion(sw, "%s/burnin" % pathutils.TOOLSDIR, "_ganeti_burnin",
784                   not options.compact,
785                   opts=burnin.OPTIONS, args=burnin.ARGUMENTS)
786
787   # ganeti-cleaner
788   WriteHaskellCompletion(sw, "daemons/ganeti-cleaner", htools=False,
789                          debug=not options.compact)
790
791   # htools, if enabled
792   if _autoconf.HTOOLS:
793     for script in _autoconf.HTOOLS_PROGS:
794       WriteHaskellCompletion(sw, script, htools=True,
795                              debug=not options.compact)
796
797   # ganeti-confd, if enabled
798   if _autoconf.ENABLE_CONFD:
799     WriteHaskellCompletion(sw, "htools/ganeti-confd", htools=False,
800                            debug=not options.compact)
801
802   # Reset extglob to original value
803   sw.Write("[[ -n \"$gnt_shopt_extglob\" ]] && $gnt_shopt_extglob")
804   sw.Write("unset gnt_shopt_extglob")
805
806   print buf.getvalue()
807
808
809 if __name__ == "__main__":
810   main()