root / trunk / twisted / python / zshcomp.py

Revision 22742, 26.9 kB (checked in by radix, 1 year ago)

Merge api-stability-removal-2847

Remove all "API Stability" markers, as they were causing more grief than good,
and we have a universally strict compatibility policy, so they have long
been irrelevent.

Author: radix
Reviewer: therve
Fixes #2847

Line 
1 # -*- test-case-name: twisted.test.test_zshcomp -*-
2 # Copyright (c) 2006 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 Rebuild the completion functions for the currently active version of Twisted::
7     $ python zshcomp.py -i
8
9 This module implements a zsh code generator which generates completion code for
10 commands that use twisted.python.usage. This is the stuff that makes pressing
11 Tab at the command line work.
12
13 Maintainer: Eric Mangold
14
15 To build completion functions for your own commands, and not Twisted commands,
16 then just do something like this::
17
18     o = mymodule.MyOptions()
19     f = file('_mycommand', 'w')
20     Builder("mycommand", o, f).write()
21
22 Then all you have to do is place the generated file somewhere in your
23 C{$fpath}, and restart zsh. Note the "site-functions" directory in your
24 C{$fpath} where you may install 3rd-party completion functions (like the one
25 you're building). Call C{siteFunctionsPath} to locate this directory
26 programmatically.
27
28 SPECIAL CLASS VARIABLES. You may set these on your usage.Options subclass::
29
30     zsh_altArgDescr
31     zsh_multiUse
32     zsh_mutuallyExclusive
33     zsh_actions
34     zsh_actionDescr
35     zsh_extras
36
37 Here is what they mean (with examples)::
38
39     zsh_altArgDescr = {"foo":"use this description for foo instead"}
40         A dict mapping long option names to alternate descriptions.  When this
41         variable is present, the descriptions contained here will override
42         those descriptions provided in the optFlags and optParameters
43         variables.
44
45     zsh_multiUse = ["foo", "bar"]
46         A sequence containing those long option names which may appear on the
47         command line more than once. By default, options will only be completed
48         one time.
49
50     zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")]
51         A sequence of sequences, with each sub-sequence containing those long
52         option names that are mutually exclusive. That is, those options that
53         cannot appear on the command line together.
54
55     zsh_actions = {"foo":'_files -g "*.foo"', "bar":"(one two three)",
56             "colors":"_values -s , 'colors to use' red green blue"}
57         A dict mapping long option names to Zsh "actions". These actions
58         define what will be completed as the argument to the given option.  By
59         default, all files/dirs will be completed if no action is given.
60
61         Callables may instead be given for the values in this dict. The
62         callable should accept no arguments, and return a string that will be
63         used as the zsh "action" in the same way as the literal strings in the
64         examples above.
65
66         As you can see in the example above. The "foo" option will have files
67         that end in .foo completed when the user presses Tab. The "bar"
68         option will have either of the strings "one", "two", or "three"
69         completed when the user presses Tab.
70
71         "colors" will allow multiple arguments to be completed, seperated by
72         commas. The possible arguments are red, green, and blue. Examples::
73
74             my_command --foo some-file.foo --colors=red,green
75             my_command --colors=green
76             my_command --colors=green,blue
77
78         Actions may take many forms, and it is beyond the scope of this
79         document to illustrate them all. Please refer to the documention for
80         the Zsh _arguments function. zshcomp is basically a front-end to Zsh's
81         _arguments completion function.
82
83         That documentation is available on the zsh web site at this URL:
84         U{http://zsh.sunsite.dk/Doc/Release/zsh_19.html#SEC124}
85
86     zsh_actionDescr = {"logfile":"log file name", "random":"random seed"}
87         A dict mapping long option names to a description for the corresponding
88         zsh "action". These descriptions are show above the generated matches
89         when the user is doing completions for this option.
90
91         Normally Zsh does not show these descriptions unless you have
92         "verbose" completion turned on. Turn on verbosity with this in your
93         ~/.zshrc::
94
95             zstyle ':completion:*' verbose yes
96             zstyle ':completion:*:descriptions' format '%B%d%b'
97
98     zsh_extras = [":file to read from:action", ":file to write to:action"]
99         A sequence of extra arguments that will be passed verbatim to Zsh's
100         _arguments completion function. The _arguments function does all the
101         hard work of doing command line completions. You can see how zshcomp
102         invokes the _arguments call by looking at the generated completion
103         files that this module creates.
104
105    *** NOTE ***
106
107         You will need to use this variable to describe completions for normal
108         command line arguments. That is, those arguments that are not
109         associated with an option. That is, the arguments that are given to the
110         parseArgs method of your usage.Options subclass.
111
112         In the example above, the 1st non-option argument will be described as
113         "file to read from" and completion options will be generated in
114         accordance with the "action". (See above about zsh "actions") The
115         2nd non-option argument will be described as "file to write to" and
116         the action will be interpreted likewise.
117
118         Things you can put here are all documented under the _arguments
119         function here: U{http://zsh.sunsite.dk/Doc/Release/zsh_19.html#SEC124}
120
121 Zsh Notes:
122
123 To enable advanced completion add something like this to your ~/.zshrc::
124
125     autoload -U compinit
126     compinit
127
128 For some extra verbosity, and general niceness add these lines too::
129
130     zstyle ':completion:*' verbose yes
131     zstyle ':completion:*:descriptions' format '%B%d%b'
132     zstyle ':completion:*:messages' format '%d'
133     zstyle ':completion:*:warnings' format 'No matches for: %d'
134
135 Have fun!
136 """
137 import itertools, sys, commands, os.path
138
139 from twisted.python import reflect, util, usage
140 from twisted.scripts.mktap import IServiceMaker
141
142 class MyOptions(usage.Options):
143     """
144     Options for this file
145     """
146     longdesc = ""
147     synopsis = "Usage: python zshcomp.py [--install | -i] | <output directory>"
148     optFlags = [["install", "i",
149                  'Output files to the "installation" directory ' \
150                  '(twisted/python/zsh in the currently active ' \
151                  'Twisted package)']]
152     optParameters = [["directory", "d", None,
153                       "Output files to this directory"]]
154     def postOptions(self):
155         if self['install'] and self['directory']:
156             raise usage.UsageError, "Can't have --install and " \
157                                     "--directory at the same time"
158         if not self['install'] and not self['directory']:
159             raise usage.UsageError, "Not enough arguments"
160         if self['directory'] and not os.path.isdir(self['directory']):
161             raise usage.UsageError, "%s is not a directory" % self['directory']
162
163 class Builder:
164     def __init__(self, cmd_name, options, file):
165         """
166         @type cmd_name: C{str}
167         @param cmd_name: The name of the command
168
169         @type options: C{twisted.usage.Options}
170         @param options: The C{twisted.usage.Options} instance defined for
171                         this command
172
173         @type file: C{file}
174         @param file: The C{file} to write the completion function to
175         """
176
177         self.cmd_name = cmd_name
178         self.options = options
179         self.file = file
180
181     def write(self):
182         """
183         Write the completion function to the file given to __init__
184         @return: C{None}
185         """
186         # by default, we just write out a single call to _arguments
187         self.file.write('#compdef %s\n' % (self.cmd_name,))
188         gen = ArgumentsGenerator(self.cmd_name, self.options, self.file)
189         gen.write()
190
191 class SubcommandBuilder(Builder):
192     """
193     Use this builder for commands that have sub-commands. twisted.python.usage
194     has the notion of sub-commands that are defined using an entirely seperate
195     Options class.
196     """
197     interface = None
198     subcmdLabel = None
199
200     def write(self):
201         """
202         Write the completion function to the file given to __init__
203         @return: C{None}
204         """
205         self.file.write('#compdef %s\n' % (self.cmd_name,))
206         self.file.write('local _zsh_subcmds_array\n_zsh_subcmds_array=(\n')
207         from twisted import plugin as newplugin
208         plugins = newplugin.getPlugins(self.interface)
209
210         for p in plugins:
211             self.file.write('"%s:%s"\n' % (p.tapname, p.description))
212         self.file.write(")\n\n")
213
214         self.options.__class__.zsh_extras = ['*::subcmd:->subcmd']
215         gen = ArgumentsGenerator(self.cmd_name, self.options, self.file)
216         gen.write()
217
218         self.file.write("""if (( CURRENT == 1 )); then
219   _describe "%s" _zsh_subcmds_array && ret=0
220 fi
221 (( ret )) || return 0
222
223 service="$words[1]"
224
225 case $service in\n""" % (self.subcmdLabel,))
226
227         plugins = newplugin.getPlugins(self.interface)
228         for p in plugins:
229             self.file.write(p.tapname + ")\n")
230             gen = ArgumentsGenerator(p.tapname, p.options(), self.file)
231             gen.write()
232             self.file.write(";;\n")
233         self.file.write("*) _message \"don't know how to" \
234                         " complete $service\";;\nesac")
235
236 class MktapBuilder(SubcommandBuilder):
237     """
238     Builder for the mktap command
239     """
240     interface = IServiceMaker
241     subcmdLabel = 'tap to build'
242
243 class TwistdBuilder(SubcommandBuilder):
244     """
245     Builder for the twistd command
246     """
247     interface = IServiceMaker
248     subcmdLabel = 'service to run'
249
250 class ArgumentsGenerator:
251     """
252     Generate a call to the zsh _arguments completion function
253     based on data in a usage.Options subclass
254     """
255     def __init__(self, cmd_name, options, file):
256         """
257         @type cmd_name: C{str}
258         @param cmd_name: The name of the command
259
260         @type options: C{twisted.usage.Options}
261         @param options: The C{twisted.usage.Options} instance defined
262                         for this command
263
264         @type file: C{file}
265         @param file: The C{file} to write the completion function to
266         """
267         self.cmd_name = cmd_name
268         self.options = options
269         self.file = file
270
271         self.altArgDescr = {}
272         self.actionDescr = {}
273         self.multiUse = []
274         self.mutuallyExclusive = []
275         self.actions = {}
276         self.extras = []
277
278         aCL = reflect.accumulateClassList
279         aCD = reflect.accumulateClassDict
280
281         aCD(options.__class__, 'zsh_altArgDescr', self.altArgDescr)
282         aCD(options.__class__, 'zsh_actionDescr', self.actionDescr)
283         aCL(options.__class__, 'zsh_multiUse', self.multiUse)
284         aCL(options.__class__, 'zsh_mutuallyExclusive',
285             self.mutuallyExclusive)
286         aCD(options.__class__, 'zsh_actions', self.actions)
287         aCL(options.__class__, 'zsh_extras', self.extras)
288
289         optFlags = []
290         optParams = []
291
292         aCL(options.__class__, 'optFlags', optFlags)
293         aCL(options.__class__, 'optParameters', optParams)
294
295         for i, optList in enumerate(optFlags):
296             if len(optList) != 3:
297                 optFlags[i] = util.padTo(3, optList)
298
299         for i, optList in enumerate(optParams):
300             if len(optList) != 4:
301                 optParams[i] = util.padTo(4, optList)
302
303
304         self.optFlags = optFlags
305         self.optParams = optParams
306
307         optParams_d = {}
308         for optList in optParams:
309             optParams_d[optList[0]] = optList[1:]
310         self.optParams_d = optParams_d
311
312         optFlags_d = {}
313         for optList in optFlags:
314             optFlags_d[optList[0]] = optList[1:]
315         self.optFlags_d = optFlags_d
316
317         optAll_d = {}
318         optAll_d.update(optParams_d)
319         optAll_d.update(optFlags_d)
320         self.optAll_d = optAll_d
321
322         self.addAdditionalOptions()
323
324         # makes sure none of the zsh_ data structures reference option
325         # names that don't exist. (great for catching typos)
326         self.verifyZshNames()
327
328         self.excludes = self.makeExcludesDict()
329
330     def write(self):
331         """
332         Write the zsh completion code to the file given to __init__
333         @return: C{None}
334         """
335         self.writeHeader()
336         self.writeExtras()
337         self.writeOptions()
338         self.writeFooter()
339
340     def writeHeader(self):
341         """
342         This is the start of the code that calls _arguments
343         @return: C{None}
344         """
345         self.file.write('_arguments -s -A "-*" \\\n')
346
347     def writeOptions(self):
348         """
349         Write out zsh code for each option in this command
350         @return: C{None}
351         """
352         optNames = self.optAll_d.keys()
353         optNames.sort()
354         for long in optNames:
355             self.writeOpt(long)
356
357     def writeExtras(self):
358         """
359         Write out the "extras" list. These are just passed verbatim to the
360         _arguments call
361         @return: C{None}
362         """
363         for s in self.extras:
364             self.file.write(escape(s))
365             self.file.write(' \\\n')
366
367     def writeFooter(self):
368         """
369         Write the last bit of code that finishes the call to _arguments
370         @return: C{None}
371         """
372         self.file.write('&& return 0\n')
373
374     def verifyZshNames(self):
375         """
376         Ensure that none of the names given in zsh_* variables are typoed
377         @return: C{None}
378         @raise ValueError: Raised if unknown option names have been given in
379                            zsh_* variables
380         """
381         def err(name):
382             raise ValueError, "Unknown option name \"%s\" found while\n" \
383                 "examining zsh_ attributes for the %s command" % (
384                     name, self.cmd_name)
385
386         for name in itertools.chain(self.altArgDescr, self.actionDescr,
387         self.actions, self.multiUse):
388             if name not in self.optAll_d:
389                 err(name)
390
391         for seq in self.mutuallyExclusive:
392             for name in seq:
393                 if name not in self.optAll_d:
394                     err(name)
395
396     def excludeStr(self, long, buildShort=False):
397         """
398         Generate an "exclusion string" for the given option
399
400         @type long: C{str}
401         @param long: The long name of the option
402                      (i.e. "verbose" instead of "v")
403
404         @type buildShort: C{bool}
405         @param buildShort: May be True to indicate we're building an excludes
406                            string for the short option that correspondes to
407                            the given long opt
408
409         @return: The generated C{str}
410         """
411         if long in self.excludes:
412             exclusions = self.excludes[long][:]
413         else:
414             exclusions = []
415
416         # if long isn't a multiUse option (can't appear on the cmd line more
417         # than once), then we have to exclude the short option if we're
418         # building for the long option, and vice versa.
419         if long not in self.multiUse:
420             if buildShort is False:
421                 short = self.getShortOption(long)
422                 if short is not None:
423                     exclusions.append(short)
424             else:
425                 exclusions.append(long)
426
427         if not exclusions:
428             return ''
429
430         strings = []
431         for optName in exclusions:
432             if len(optName) == 1:
433                 # short option
434                 strings.append("-" + optName)
435             else:
436                 strings.append("--" + optName)
437         return "(%s)" % " ".join(strings)
438
439     def makeExcludesDict(self):
440         """
441         @return: A C{dict} that maps each option name appearing in
442         self.mutuallyExclusive to a list of those option names that
443         is it mutually exclusive with (can't appear on the cmd line with)
444         """
445
446         #create a mapping of long option name -> single character name
447         longToShort = {}
448         for optList in itertools.chain(self.optParams, self.optFlags):
449             try:
450                 if optList[1] != None:
451                     longToShort[optList[0]] = optList[1]
452             except IndexError:
453                 pass
454
455         excludes = {}
456         for lst in self.mutuallyExclusive:
457             for i, long in enumerate(lst):
458                 tmp = []
459                 tmp.extend(lst[:i])
460                 tmp.extend(lst[i+1:])
461                 for name in tmp[:]:
462                     if name in longToShort:
463                         tmp.append(longToShort[name])
464
465                 if long in excludes:
466                     excludes[long].extend(tmp)
467                 else:
468                     excludes[long] = tmp
469         return excludes
470
471     def writeOpt(self, long):
472         """
473         Write out the zsh code for the given argument. This is just part of the
474         one big call to _arguments
475
476         @type long: C{str}
477         @param long: The long name of the option
478                      (i.e. "verbose" instead of "v")
479
480         @return: C{None}
481         """
482         if long in self.optFlags_d:
483             # It's a flag option. Not one that takes a parameter.
484             long_field = "--%s" % long
485         else:
486             long_field = "--%s=" % long
487
488         short = self.getShortOption(long)
489         if short != None:
490             short_field = "-" + short
491         else:
492             short_field = ''
493
494         descr = self.getDescription(long)
495         descr_field = descr.replace("[", "\[")
496         descr_field = descr_field.replace("]", "\]")
497         descr_field = '[%s]' % descr_field
498
499         if long in self.actionDescr:
500             actionDescr_field = self.actionDescr[long]
501         else:
502             actionDescr_field = descr
503
504         action_field = self.getAction(long)
505         if long in self.multiUse:
506             multi_field = '*'
507         else:
508             multi_field = ''
509
510         longExclusions_field = self.excludeStr(long)
511
512         if short:
513             #we have to write an extra line for the short option if we have one
514             shortExclusions_field = self.excludeStr(long, buildShort=True)
515             self.file.write(escape('%s%s%s%s%s' % (shortExclusions_field,
516                 multi_field, short_field, descr_field, action_field)))
517             self.file.write(' \\\n')
518
519         self.file.write(escape('%s%s%s%s%s' % (longExclusions_field,
520             multi_field, long_field, descr_field, action_field)))
521         self.file.write(' \\\n')
522
523     def getAction(self, long):
524         """
525         Return a zsh "action" string for the given argument
526         @return: C{str}
527         """
528         if long in self.actions:
529             if callable(self.actions[long]):
530                 action = self.actions[long]()
531             else:
532                 action = self.actions[long]
533             return ":%s:%s" % (self.getActionDescr(long), action)
534         if long in self.optParams_d:
535             return ':%s:_files' % self.getActionDescr(long)
536         return ''
537
538     def getActionDescr(self, long):
539         """
540         Return the description to be used when this argument is completed
541         @return: C{str}
542         """
543         if long in self.actionDescr:
544             return self.actionDescr[long]
545         else:
546             return long
547
548     def getDescription(self, long):
549         """
550         Return the description to be used for this argument
551         @return: C{str}
552         """
553         #check if we have an alternate descr for this arg, and if so use it
554         if long in self.altArgDescr:
555             return self.altArgDescr[long]
556
557         #otherwise we have to get it from the optFlags or optParams
558         try:
559             descr = self.optFlags_d[long][1]
560         except KeyError:
561             try:
562                 descr = self.optParams_d[long][2]
563             except KeyError:
564                 descr = None
565
566         if descr is not None:
567             return descr
568
569         # lets try to get it from the opt_foo method doc string if there is one
570         longMangled = long.replace('-', '_') # this is what t.p.usage does
571         obj = getattr(self.options, 'opt_%s' % longMangled, None)
572         if obj:
573             descr = descrFromDoc(obj)
574             if descr is not None:
575                 return descr
576
577         return long # we really ought to have a good description to use
578
579     def getShortOption(self, long):
580         """
581         Return the short option letter or None
582         @return: C{str} or C{None}
583         """
584         optList = self.optAll_d[long]
585         try:
586             return optList[0] or None
587         except IndexError:
588             pass
589
590     def addAdditionalOptions(self):
591         """
592         Add additional options to the optFlags and optParams lists.
593         These will be defined by 'opt_foo' methods of the Options subclass
594         @return: C{None}
595         """
596         methodsDict = {}
597         reflect.accumulateMethods(self.options, methodsDict, 'opt_')
598         methodToShort = {}
599         for name in methodsDict.copy():
600             if len(name) == 1:
601                 methodToShort[methodsDict[name]] = name
602                 del methodsDict[name]
603
604         for methodName, methodObj in methodsDict.items():
605             long = methodName.replace('_', '-') # t.p.usage does this
606             # if this option is already defined by the optFlags or
607             # optParameters then we don't want to override that data
608             if long in self.optAll_d:
609                 continue
610
611             descr = self.getDescription(long)
612
613             short = None
614             if methodObj in methodToShort:
615                 short = methodToShort[methodObj]
616
617             reqArgs = methodObj.im_func.func_code.co_argcount
618             if reqArgs == 2:
619                 self.optParams.append([long, short, None, descr])
620                 self.optParams_d[long] = [short, None, descr]
621                 self.optAll_d[long] = [short, None, descr]
622             elif reqArgs == 1:
623                 self.optFlags.append([long, short, descr])
624                 self.optFlags_d[long] = [short, descr]
625                 self.optAll_d[long] = [short, None, descr]
626             else:
627                 raise TypeError, '%r has wrong number ' \
628                                  'of arguments' % (methodObj,)
629
630 def descrFromDoc(obj):
631     """
632     Generate an appropriate description from docstring of the given object
633     """
634     if obj.__doc__ is None:
635         return None
636
637     lines = obj.__doc__.split("\n")
638     descr = None
639     try:
640         if lines[0] != "" and not lines[0].isspace():
641             descr = lines[0].lstrip()
642         # skip first line if it's blank
643         elif lines[1] != "" and not lines[1].isspace():
644             descr = lines[1].lstrip()
645     except IndexError:
646         pass
647     return descr
648
649 def firstLine(s):
650     """
651     Return the first line of the given string
652     """
653     try:
654         i = s.index('\n')
655         return s[:i]
656     except ValueError:
657         return s
658
659 def escape(str):
660     """
661     Shell escape the given string
662     """
663     return commands.mkarg(str)[1:]
664
665 def siteFunctionsPath():
666     """
667     Return the path to the system-wide site-functions directory or
668     C{None} if it cannot be determined
669     """
670     try:
671         cmd = "zsh -f -c 'echo ${(M)fpath:#/*/site-functions}'"
672         output = commands.getoutput(cmd)
673         if os.path.isdir(output):
674             return output
675     except:
676         pass
677
678 generateFor = [('conch', 'twisted.conch.scripts.conch', 'ClientOptions'),
679                ('mktap', 'twisted.scripts.mktap', 'FirstPassOptions'),
680                ('trial', 'twisted.scripts.trial', 'Options'),
681                ('cftp', 'twisted.conch.scripts.cftp', 'ClientOptions'),
682                ('tapconvert', 'twisted.scripts.tapconvert', 'ConvertOptions'),
683                ('twistd', 'twisted.scripts.twistd', 'ServerOptions'),
684                ('ckeygen', 'twisted.conch.scripts.ckeygen', 'GeneralOptions'),
685                ('lore', 'twisted.lore.scripts.lore', 'Options'),
686                ('pyhtmlizer', 'twisted.scripts.htmlizer', 'Options'),
687                ('tap2deb', 'twisted.scripts.tap2deb', 'MyOptions'),
688                ('tkconch', 'twisted.conch.scripts.tkconch', 'GeneralOptions'),
689                ('manhole', 'twisted.scripts.manhole', 'MyOptions'),
690                ('tap2rpm', 'twisted.scripts.tap2rpm', 'MyOptions'),
691                ('websetroot', None, None),
692                ('tkmktap', None, None),
693                ]
694 # NOTE: the commands using None above are no longer included in Twisted.
695 # However due to limitations in zsh's completion system the version of
696 # _twisted_zsh_stub shipped with zsh contains a static list of Twisted's
697 # commands. It will display errors if completion functions for these missing
698 # commands are not found :( So we just include dummy (empty) completion
699 # function files
700
701 specialBuilders = {'mktap'  : MktapBuilder,
702                    'twistd' : TwistdBuilder}
703
704 def makeCompFunctionFiles(out_path, generateFor=generateFor,
705                           specialBuilders=specialBuilders):
706     """
707     Generate completion function files in the given directory for all
708     twisted commands
709
710     @type out_path: C{str}
711     @param out_path: The path to the directory to generate completion function
712                      fils in
713
714     @param generateFor: Sequence in the form of the 'generateFor' top-level
715                         variable as defined in this module. Indicates what
716                         commands to build completion files for.
717
718     @param specialBuilders: Sequence in the form of the 'specialBuilders'
719                             top-level variable as defined in this module.
720                             Indicates what commands require a special
721                             Builder class.
722
723     @return: C{list} of 2-tuples of the form (cmd_name, error) indicating
724              commands that we skipped building completions for. cmd_name
725              is the name of the skipped command, and error is the Exception
726              that was raised when trying to import the script module.
727              Commands are usually skipped due to a missing dependency,
728              e.g. Tkinter.
729     """
730     skips = []
731     for cmd_name, module_name, class_name in generateFor:
732         if module_name is None:
733             # create empty file
734             f = _openCmdFile(out_path, cmd_name)
735             f.close()
736             continue
737         try:
738             m = __import__('%s' % (module_name,), None, None, (class_name))
739             f = _openCmdFile(out_path, cmd_name)
740             o = getattr(m, class_name)() # instantiate Options class
741
742             if cmd_name in specialBuilders:
743                 b = specialBuilders[cmd_name](cmd_name, o, f)
744                 b.write()
745             else:
746                 b = Builder(cmd_name, o, f)
747                 b.write()
748         except Exception, e:
749             skips.append( (cmd_name, e) )
750             continue
751     return skips
752
753 def _openCmdFile(out_path, cmd_name):
754     return file(os.path.join(out_path, '_'+cmd_name), 'w')
755
756 def run():
757     options = MyOptions()
758     try:
759         options.parseOptions(sys.argv[1:])
760     except usage.UsageError, e:
761         print e
762         print options.getUsage()
763         sys.exit(2)
764
765     if options['install']:
766         import twisted
767         dir = os.path.join(os.path.dirname(twisted.__file__), "python", "zsh")
768         skips = makeCompFunctionFiles(dir)
769     else:
770         skips = makeCompFunctionFiles(options['directory'])
771
772     for cmd_name, error in skips:
773         sys.stderr.write("zshcomp: Skipped building for %s. Script module " \
774                          "could not be imported:\n" % (cmd_name,))
775         sys.stderr.write(str(error)+'\n')
776     if skips:
777         sys.exit(3)
778
779 if __name__ == '__main__':
780     run()
Note: See TracBrowser for help on using the browser.