Statistics
| Branch: | Tag: | Revision:

root / autotools / build-zsh-completion @ f7ee7f53

History | View | Annotate | Download (16.5 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2011 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 zsh completion script for Ganeti.
23

    
24
"""
25

    
26
# pylint: disable-msg=C0103
27
# [C0103] Invalid name build-zsh-completion
28

    
29
import os
30
import re
31
from cStringIO import StringIO
32

    
33
from ganeti import constants
34
from ganeti import cli
35
from ganeti import utils
36
from ganeti import build
37

    
38
# _autoconf shouldn't be imported from anywhere except constants.py, but we're
39
# making an exception here because this script is only used at build time.
40
from ganeti import _autoconf
41

    
42
def WritePreamble(sw):
43
  """Writes the script preamble.
44

    
45
  Helper functions should be written here.
46

    
47
  """
48
  sw.Write("#compdef %s" % " ".join(_autoconf.GNT_SCRIPTS))
49
  sw.Write("")
50
  sw.Write("# This script is automatically generated at build time.")
51
  sw.Write("# Do not modify manually.")
52

    
53
  sw.Write("_ganeti_dbglog() {")
54
  sw.IncIndent()
55
  try:
56
    sw.Write("if [[ -n \"$GANETI_COMPL_LOG\" ]]; then")
57
    sw.IncIndent()
58
    try:
59
      sw.Write("{")
60
      sw.IncIndent()
61
      try:
62
        sw.Write("echo ---")
63
        sw.Write("echo \"$@\"")
64
        sw.Write("echo")
65
      finally:
66
        sw.DecIndent()
67
      sw.Write("} >> $GANETI_COMPL_LOG")
68
    finally:
69
      sw.DecIndent()
70
    sw.Write("fi")
71
  finally:
72
    sw.DecIndent()
73
  sw.Write("}")
74

    
75
  sw.Write("_ganeti_nodes() {")
76
  sw.IncIndent()
77
  try:
78
    node_list_path = os.path.join(constants.DATA_DIR, "ssconf_node_list")
79
    sw.Write("compadd -X 'completing nodes' ${(f)\"$(cat %s 2>/dev/null)\"}",
80
             utils.ShellQuote(node_list_path))
81
  finally:
82
    sw.DecIndent()
83
  sw.Write("}")
84

    
85
  sw.Write("_ganeti_many_nodes() {")
86
  sw.IncIndent()
87
  try:
88
    node_list_path = os.path.join(constants.DATA_DIR, "ssconf_node_list")
89
    sw.Write("if compset -P '*,'; then")
90
    sw.IncIndent()
91
    try:
92
      sw.Write("compadd -X 'completing nodes' -S ',' -q"
93
               " ${(f)\"$(grep -vxF \"$(echo \"$IPREFIX\" |"
94
               " sed 's/,/\\n/g')\" %s 2>/dev/null)\"}",
95
               utils.ShellQuote(node_list_path))
96
    finally:
97
      sw.DecIndent()
98
    sw.Write("else")
99
    sw.IncIndent()
100
    try:
101
      sw.Write("compadd -X 'completing nodes' -S ','"
102
               " -q ${(f)\"$(cat %s 2>/dev/null)\"}",
103
               utils.ShellQuote(node_list_path))
104
    finally:
105
      sw.DecIndent()
106
    sw.Write("fi")
107
  finally:
108
    sw.DecIndent()
109
  sw.Write("}")
110

    
111
  sw.Write("_ganeti_instance_nodes() {")
112
  sw.IncIndent()
113
  sw.Write("# Complete primary and optionally secondary instance node")
114
  try:
115
    node_list_path = os.path.join(constants.DATA_DIR, "ssconf_node_list")
116
    sw.Write("if compset -P 1 '*:'; then")
117
    sw.IncIndent()
118
    try:
119
      sw.Write("compadd -X 'completing nodes'"
120
               " ${(f)\"$(grep -vx \"${IPREFIX%%:}\" %s 2>/dev/null)\"}",
121
               utils.ShellQuote(node_list_path))
122
    finally:
123
      sw.DecIndent()
124
    sw.Write("else")
125
    sw.IncIndent()
126
    try:
127
      sw.Write("compadd -X 'completing nodes' -S ':' -q"
128
               " ${(f)\"$(cat %s 2>/dev/null)\"}",
129
               utils.ShellQuote(node_list_path))
130
    finally:
131
      sw.DecIndent()
132
  finally:
133
    sw.DecIndent()
134
    sw.Write("fi")
135
  sw.Write("}")
136

    
137
  sw.Write("_ganeti_instances() {")
138
  sw.IncIndent()
139
  try:
140
    instance_list_path = os.path.join(constants.DATA_DIR,
141
                                      "ssconf_instance_list")
142
    sw.Write("compadd -X 'completing instances'"
143
             " ${(f)\"$(cat %s 2>/dev/null)\"}",
144
             utils.ShellQuote(instance_list_path))
145
  finally:
146
    sw.DecIndent()
147
  sw.Write("}")
148

    
149
  sw.Write("_ganeti_jobs() {")
150
  sw.IncIndent()
151
  try:
152
    # FIXME: this is really going into the internals of the job queue
153
    sw.Write("_description job expl 'job id'")
154
    sw.Write("compadd ${(f)\"$(cd %s 2>/dev/null && echo job-* |"
155
             " sed 's/job-//g' | xargs -n 1)\"}",
156
             utils.ShellQuote(constants.QUEUE_DIR))
157
  finally:
158
    sw.DecIndent()
159
  sw.Write("}")
160

    
161
  for (fnname, paths) in [
162
      ("os", constants.OS_SEARCH_PATH),
163
      ("iallocator", constants.IALLOCATOR_SEARCH_PATH),
164
      ]:
165
    sw.Write("_ganeti_%s() {", fnname)
166
    sw.IncIndent()
167
    try:
168
      # FIXME: Make querying the master for all OSes cheap
169
      for path in paths:
170
        sw.Write("compadd -X 'completing %s'"
171
                 " ${(f)\"$( cd %s 2>/dev/null && echo * | xargs -n 1)\"}" %
172
                 (fnname,
173
                 utils.ShellQuote(path)))
174
    finally:
175
      sw.DecIndent()
176
    sw.Write("}")
177

    
178
  sw.Write("_ganeti_nodegroup() {")
179
  sw.IncIndent()
180
  try:
181
    nodegroups_path = os.path.join(constants.DATA_DIR, "ssconf_nodegroups")
182
    sw.Write("compadd -X 'completing node group'"
183
             " ${(f)\"$(cat %s | xargs -n 1 | sort 2>/dev/null)\"}",
184
             utils.ShellQuote(nodegroups_path))
185
  finally:
186
    sw.DecIndent()
187
  sw.Write("}")
188

    
189
  sw.Write("_ganeti_network() {")
190
  sw.IncIndent()
191
  try:
192
    networks_path = os.path.join(constants.DATA_DIR, "ssconf_networks")
193
    sw.Write("compadd -X 'completing networks'"
194
             " ${(f)\"$(cat %s | xargs -n 1 | sort 2>/dev/null)\"}",
195
             utils.ShellQuote(networks_path))
196
  finally:
197
    sw.DecIndent()
198
  sw.Write("}")
199

    
200
  sw.Write("_ganeti_nic_params() {")
201
  sw.IncIndent()
202
  try:
203
    sw.Write("compset -P 1 '*:'")
204
    sw.Write("_values -X 'nic parameters' -s , 'NIC parameters' %s" %
205
             " ".join(["'%s:'" % p for p in constants.INIC_PARAMS]))
206
  finally:
207
    sw.DecIndent()
208
  sw.Write("}")
209

    
210
  sw.Write("_ganeti_disk_params() {")
211
  sw.IncIndent()
212
  try:
213
    sw.Write("compset -P 1 '*:'")
214
    sw.Write("_values -X 'disk parameters' -s , 'disk parameters' %s" %
215
             " ".join(["'%s:'" % p for p in constants.IDISK_PARAMS]))
216
  finally:
217
    sw.DecIndent()
218
  sw.Write("}")
219

    
220
  sw.Write("_ganeti_backend_params() {")
221
  sw.IncIndent()
222
  try:
223
    sw.Write("_values -X 'backend parameters' -s , 'backend parameters' %s" %
224
             " ".join(["'%s:'" % p for p in constants.BES_PARAMETERS]))
225
  finally:
226
    sw.DecIndent()
227
  sw.Write("}")
228

    
229

    
230
def WriteAppendix(sw):
231
  sw.Write("case $service in")
232
  sw.IncIndent()
233
  try:
234
    for scriptname in _autoconf.GNT_SCRIPTS:
235
      sw.Write("%s)" % scriptname)
236
      sw.IncIndent()
237
      try:
238
        sw.Write("%s \"$@\"" % GetFunctionName(scriptname))
239
        sw.Write(";;")
240
      finally:
241
        sw.DecIndent()
242

    
243
  finally:
244
    sw.DecIndent()
245
  sw.Write("esac")
246

    
247
# pylint: disable=W0212
248
def WriteGntCompletion(sw, scriptname, funcname, commands=None):
249
  """Writes the completion code for one command.
250

    
251
  @type sw: ShellWriter
252
  @param sw: Script writer
253
  @type scriptname: string
254
  @param scriptname: Name of command line program
255
  @type funcname: string
256
  @param funcname: Shell function name
257
  @type commands: list
258
  @param commands: List of all subcommands in this program
259

    
260
  """
261
  sw.Write("%s() {", funcname)
262
  sw.IncIndent()
263
  try:
264
    sw.Write("local cmds")
265
    sw.Write("local subcmd")
266
    sw.Write("""if (( CURRENT == 2 )); then""")
267
    sw.IncIndent()
268
    try:
269
      # Complete the command name
270
      sw.Write("cmds=(")
271
      sw.IncIndent()
272
      try:
273
        for cmd in sorted(commands.keys()):
274
          sw.Write(utils.ShellQuote("%s:%s" %
275
                                    (cmd, commands[cmd][-1])))
276
      finally:
277
        sw.Write(")")
278
        sw.DecIndent()
279
      sw.Write("_describe '%s command' cmds" % scriptname)
280
    finally:
281
      sw.DecIndent()
282
    sw.Write("else")
283
    sw.IncIndent()
284
    try:
285
      sw.Write("shift words")
286
      sw.Write("(( CURRENT-- ))")
287
      sw.Write("subcmd=\"$words[1]\"")
288
      sw.Write("case \"$subcmd\" in")
289
      for cmd, (_, argdef, optdef, _, _) in commands.iteritems():
290
        if not (argdef or optdef):
291
          continue
292

    
293
        # TODO: Group by arguments and options
294
        sw.Write("%s)", utils.ShellQuote(cmd))
295
        sw.IncIndent()
296
        try:
297
          #CompletionWriter(optdef, argdef).WriteTo(sw)
298
          sw.Write("_arguments -A \"-*\" \\")
299
          sw.IncIndent()
300
          try:
301
            for o in optdef:
302
              message = ""
303
              desc = ""
304
              completer = None
305
              alternatives = None
306
              all_names = sorted(o._short_opts + o._long_opts)
307

    
308
              if len(all_names) > 1:
309
                opt = "(%s)" % " ".join(all_names)
310
                switches = []
311
                for switch in all_names:
312
                  switch_string = switch
313
                  if o.takes_value():
314
                    if switch in o._long_opts:
315
                      switch_string += "="
316
                  if o.action == "append":
317
                    switch_string = "*" + switch_string
318
                  switches.append(switch_string)
319
                alternatives = "{%s}" % ",".join(switches)
320

    
321
              else:
322
                if o.action == "append":
323
                  # This option can be specified multiple times.
324
                  opt = "*"
325
                else:
326
                  opt = ""
327
                opt += o.get_opt_string()
328
                if o.takes_value():
329
                  if o.get_opt_string() in o._long_opts:
330
                    opt += "="
331

    
332
              if o.help:
333
                desc = o.help
334
                desc = desc.replace("[", "\\[")
335
                desc = desc.replace("]", "\\]")
336
                desc = desc.replace(":", "\\:")
337
                desc = "[%s]" % desc
338

    
339
              # Only static choices implemented so far (e.g. no node list)
340
              suggest = getattr(o, "completion_suggest", None)
341

    
342
              # our custom option type
343
              if o.type == "bool":
344
                suggest = ["yes", "no"]
345

    
346
              if not suggest:
347
                suggest = o.choices
348

    
349
              if o.metavar:
350
                message = o.metavar.lower()
351
                message = message.replace(">","")
352
                message = message.replace("<","")
353
                message = message.replace("[","\\[")
354
                message = message.replace("]","\\]")
355
                message = message.replace(":", "\\:")
356

    
357
              if (isinstance(suggest, (int, long)) and
358
                  suggest in cli.OPT_COMPL_ALL):
359
                if suggest == cli.OPT_COMPL_MANY_NODES:
360
                  completer = "_ganeti_many_nodes"
361
                elif suggest == cli.OPT_COMPL_ONE_NODE:
362
                  completer = "_ganeti_nodes"
363
                elif suggest == cli.OPT_COMPL_ONE_INSTANCE:
364
                  completer = "_ganeti_instances"
365
                elif suggest == cli.OPT_COMPL_ONE_OS:
366
                  completer = "_ganeti_os"
367
                elif suggest == cli.OPT_COMPL_ONE_IALLOCATOR:
368
                  completer = "_ganeti_iallocator"
369
                elif suggest == cli.OPT_COMPL_INST_ADD_NODES:
370
                  completer = "_ganeti_instance_nodes"
371
                elif suggest == cli.OPT_COMPL_ONE_NODEGROUP:
372
                  completer = "_ganeti_nodegroup"
373
                elif suggest == cli.OPT_COMPL_ONE_NETWORK:
374
                  completer = "_ganeti_network"
375
                elif suggest == cli.OPT_COMPL_NIC_PARAMS:
376
                  completer = "_ganeti_nic_params"
377
                elif suggest == cli.OPT_COMPL_DISK_PARAMS:
378
                  completer = "_ganeti_disk_params"
379
                elif suggest == cli.OPT_COMPL_BACKEND_PARAMS:
380
                  completer = "_ganeti_backend_params"
381
              elif suggest is not None:
382
                completer = "(%s)" % " ".join(sorted(suggest))
383

    
384
              if completer and not message:
385
                message = " "
386

    
387
              if completer:
388
                tail = "%s:%s:%s" % (desc, message, completer)
389
              else:
390
                tail = desc
391
                if o.takes_value():
392
                  tail += ":"
393

    
394
              if alternatives:
395
                opt = utils.ShellQuote(opt)
396
                opt += alternatives
397
                opt += utils.ShellQuote(tail)
398
              else:
399
                opt = utils.ShellQuote(opt + tail)
400

    
401
              sw.Write(opt + " \\")
402

    
403
            # Argument completion
404
            last_idx = len(argdef) - 1
405
            varlen_arg_idx = None
406

    
407
            if argdef:
408
              for idx, arg in enumerate(argdef):
409
                assert arg.min is not None and arg.min >= 0
410
                assert not (idx < last_idx and arg.max is None)
411

    
412
                if arg.min != arg.max or arg.max is None:
413
                  if varlen_arg_idx is not None:
414
                    raise Exception("Only one argument can have"
415
                                    " variable length")
416
                  varlen_arg_idx = idx
417

    
418
              for idx, arg in enumerate(argdef):
419
                if isinstance(arg, cli.ArgUnknown):
420
                  completer = ""
421
                elif isinstance(arg, cli.ArgSuggest):
422
                  completer = "(%s)" % " ".join(arg.choices)
423
                elif isinstance(arg, cli.ArgInstance):
424
                  completer = "_ganeti_instances"
425
                elif isinstance(arg, cli.ArgNode):
426
                  completer = "_ganeti_nodes"
427
                elif isinstance(arg, cli.ArgGroup):
428
                  completer = "_ganeti_nodegroup"
429
                elif isinstance(arg, cli.ArgNetwork):
430
                  completer = "_ganeti_network"
431
                elif isinstance(arg, cli.ArgJobId):
432
                  completer = "_ganeti_jobs"
433
                elif isinstance(arg, cli.ArgOs):
434
                  completer = "_ganeti_os"
435
                elif isinstance(arg, cli.ArgFile):
436
                  completer = "_files"
437
                elif isinstance(arg, cli.ArgCommand):
438
                  completer = "_path_commands"
439
                elif isinstance(arg, cli.ArgHost):
440
                  completer = "_hosts"
441
                else:
442
                  raise Exception("Unknown argument type %r" % arg)
443

    
444
                argopt = ""
445
                if idx == varlen_arg_idx:
446
                  if arg.min == 0:
447
                    argopt = "*:: :%s" % completer
448
                  else:
449
                    argopt = "*: :%s" % completer
450
                  sw.Write(utils.ShellQuote(argopt) + ' \\')
451

    
452
                else:
453
                  for _ in range(0, arg.min):
454
                    sw.Write(utils.ShellQuote(": :%s" % completer) + ' \\')
455
                  for _ in range(arg.min, arg.max):
456
                    sw.Write(utils.ShellQuote(":: :%s" % completer) + ' \\')
457

    
458
          finally:
459
            sw.DecIndent()
460

    
461
        finally:
462
          sw.DecIndent()
463

    
464
        sw.Write(";;")
465
      sw.Write("esac")
466
    finally:
467
      sw.DecIndent()
468
      sw.Write("fi")
469
  finally:
470
    sw.DecIndent()
471
  sw.Write("}")
472

    
473
  sw.Write("")
474
  #sw.Write("compdef %s %s" % (funcname, scriptname))
475
  #sw.Write("")
476

    
477

    
478
def GetFunctionName(name):
479
  return "_" + re.sub(r"[^a-z0-9]+", "_", name.lower())
480

    
481

    
482
def GetCommands(filename, module):
483
  """Returns the commands defined in a module.
484

    
485
  Aliases are also added as commands.
486

    
487
  """
488
  try:
489
    commands = getattr(module, "commands")
490
  except AttributeError:
491
    raise Exception("Script %s doesn't have 'commands' attribute" %
492
                    filename)
493

    
494
  # Add the implicit "--help" option
495
  help_option = cli.cli_option("-h", "--help", default=False,
496
                               action="store_true",
497
                               help="Display usage information")
498

    
499
  for name, (_, _, optdef, _, _) in commands.items():
500
    if help_option not in optdef:
501
      optdef.append(help_option)
502
    for opt in cli.COMMON_OPTS:
503
      if opt in optdef:
504
        raise Exception("Common option '%s' listed for command '%s' in %s" %
505
                        (opt, name, filename))
506
      optdef.append(opt)
507

    
508
  # Use aliases
509
  aliases = getattr(module, "aliases", {})
510
  if aliases:
511
    commands = commands.copy()
512
    for name, target in aliases.iteritems():
513
      commands[name] = commands[target]
514

    
515
  return commands
516

    
517

    
518
def main():
519
  buf = StringIO()
520
  sw = utils.ShellWriter(buf)
521

    
522
  WritePreamble(sw)
523

    
524
  # gnt-* scripts
525
  for scriptname in _autoconf.GNT_SCRIPTS:
526
    filename = "scripts/%s" % scriptname
527

    
528
    WriteGntCompletion(sw, scriptname,
529
                       GetFunctionName(scriptname),
530
                       commands=GetCommands(filename,
531
                                            build.LoadModule(filename)))
532

    
533
  WriteAppendix(sw)
534
  print buf.getvalue()
535

    
536

    
537
if __name__ == "__main__":
538
  main()