~cbehrens/nova/lp844160-build-works-with-zones

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/python/usage.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.test.test_usage -*-
 
2
# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
 
 
6
"""
 
7
twisted.python.usage is a module for parsing/handling the
 
8
command line of your program.
 
9
 
 
10
For information on how to use it, see
 
11
U{http://twistedmatrix.com/projects/core/documentation/howto/options.html},
 
12
or doc/howto/options.html in your Twisted directory.
 
13
"""
 
14
 
 
15
# System Imports
 
16
import os
 
17
import sys
 
18
import getopt
 
19
from os import path
 
20
 
 
21
# Sibling Imports
 
22
from twisted.python import reflect, text, util
 
23
 
 
24
 
 
25
class UsageError(Exception):
 
26
    pass
 
27
 
 
28
 
 
29
error = UsageError
 
30
 
 
31
 
 
32
class CoerceParameter(object):
 
33
    """
 
34
    Utility class that can corce a parameter before storing it.
 
35
    """
 
36
    def __init__(self, options, coerce):
 
37
        """
 
38
        @param options: parent Options object
 
39
        @param coerce: callable used to coerce the value.
 
40
        """
 
41
        self.options = options
 
42
        self.coerce = coerce
 
43
        self.doc = getattr(self.coerce, 'coerceDoc', '')
 
44
 
 
45
    def dispatch(self, parameterName, value):
 
46
        """
 
47
        When called in dispatch, do the coerce for C{value} and save the
 
48
        returned value.
 
49
        """
 
50
        if value is None:
 
51
            raise UsageError("Parameter '%s' requires an argument."
 
52
                             % (parameterName,))
 
53
        try:
 
54
            value = self.coerce(value)
 
55
        except ValueError, e:
 
56
            raise UsageError("Parameter type enforcement failed: %s" % (e,))
 
57
 
 
58
        self.options.opts[parameterName] = value
 
59
 
 
60
 
 
61
class Options(dict):
 
62
    """
 
63
    An option list parser class
 
64
 
 
65
    C{optFlags} and C{optParameters} are lists of available parameters
 
66
    which your program can handle. The difference between the two
 
67
    is the 'flags' have an on(1) or off(0) state (off by default)
 
68
    whereas 'parameters' have an assigned value, with an optional
 
69
    default. (Compare '--verbose' and '--verbosity=2')
 
70
 
 
71
    optFlags is assigned a list of lists. Each list represents
 
72
    a flag parameter, as so::
 
73
 
 
74
    |    optFlags = [['verbose', 'v', 'Makes it tell you what it doing.'],
 
75
    |                ['quiet', 'q', 'Be vewy vewy quiet.']]
 
76
 
 
77
    As you can see, the first item is the long option name
 
78
    (prefixed with '--' on the command line), followed by the
 
79
    short option name (prefixed with '-'), and the description.
 
80
    The description is used for the built-in handling of the
 
81
    --help switch, which prints a usage summary.
 
82
 
 
83
    C{optParameters} is much the same, except the list also contains
 
84
    a default value::
 
85
 
 
86
    | optParameters = [['outfile', 'O', 'outfile.log', 'Description...']]
 
87
 
 
88
    A coerce function can also be specified as the last element: it will be
 
89
    called with the argument and should return the value that will be stored
 
90
    for the option. This function can have a C{coerceDoc} attribute which
 
91
    will be appended to the documentation of the option.
 
92
 
 
93
    subCommands is a list of 4-tuples of (command name, command shortcut,
 
94
    parser class, documentation).  If the first non-option argument found is
 
95
    one of the given command names, an instance of the given parser class is
 
96
    instantiated and given the remainder of the arguments to parse and
 
97
    self.opts[command] is set to the command name.  For example::
 
98
 
 
99
    | subCommands = [
 
100
    |      ['inquisition', 'inquest', InquisitionOptions,
 
101
    |           'Perform an inquisition'],
 
102
    |      ['holyquest', 'quest', HolyQuestOptions,
 
103
    |           'Embark upon a holy quest']
 
104
    |  ]
 
105
 
 
106
    In this case, C{"<program> holyquest --horseback --for-grail"} will cause
 
107
    C{HolyQuestOptions} to be instantiated and asked to parse
 
108
    C{['--horseback', '--for-grail']}.  Currently, only the first sub-command
 
109
    is parsed, and all options following it are passed to its parser.  If a
 
110
    subcommand is found, the subCommand attribute is set to its name and the
 
111
    subOptions attribute is set to the Option instance that parses the
 
112
    remaining options. If a subcommand is not given to parseOptions,
 
113
    the subCommand attribute will be None. You can also mark one of
 
114
    the subCommands to be the default.
 
115
 
 
116
    | defaultSubCommand = 'holyquest'
 
117
 
 
118
    In this case, the subCommand attribute will never be None, and
 
119
    the subOptions attribute will always be set.
 
120
 
 
121
    If you want to handle your own options, define a method named
 
122
    C{opt_paramname} that takes C{(self, option)} as arguments. C{option}
 
123
    will be whatever immediately follows the parameter on the
 
124
    command line. Options fully supports the mapping interface, so you
 
125
    can do things like C{'self["option"] = val'} in these methods.
 
126
 
 
127
    Advanced functionality is covered in the howto documentation,
 
128
    available at
 
129
    U{http://twistedmatrix.com/projects/core/documentation/howto/options.html},
 
130
    or doc/howto/options.html in your Twisted directory.
 
131
    """
 
132
 
 
133
    subCommand = None
 
134
    defaultSubCommand = None
 
135
    parent = None
 
136
    def __init__(self):
 
137
        super(Options, self).__init__()
 
138
 
 
139
        self.opts = self
 
140
        self.defaults = {}
 
141
 
 
142
        # These are strings/lists we will pass to getopt
 
143
        self.longOpt = []
 
144
        self.shortOpt = ''
 
145
        self.docs = {}
 
146
        self.synonyms = {}
 
147
        self._dispatch = {}
 
148
 
 
149
 
 
150
        collectors = [
 
151
            self._gather_flags,
 
152
            self._gather_parameters,
 
153
            self._gather_handlers,
 
154
            ]
 
155
 
 
156
        for c in collectors:
 
157
            (longOpt, shortOpt, docs, settings, synonyms, dispatch) = c()
 
158
            self.longOpt.extend(longOpt)
 
159
            self.shortOpt = self.shortOpt + shortOpt
 
160
            self.docs.update(docs)
 
161
 
 
162
            self.opts.update(settings)
 
163
            self.defaults.update(settings)
 
164
 
 
165
            self.synonyms.update(synonyms)
 
166
            self._dispatch.update(dispatch)
 
167
 
 
168
    def __hash__(self):
 
169
        """
 
170
        Define a custom hash function so that Options instances can be used
 
171
        as dictionary keys.  This is an internal feature used to implement
 
172
        the parser.  Do not rely on it in application code.
 
173
        """
 
174
        return int(id(self) % sys.maxint)
 
175
 
 
176
    def opt_help(self):
 
177
        """
 
178
        Display this help and exit.
 
179
        """
 
180
        print self.__str__()
 
181
        sys.exit(0)
 
182
 
 
183
    def opt_version(self):
 
184
        from twisted import copyright
 
185
        print "Twisted version:", copyright.version
 
186
        sys.exit(0)
 
187
 
 
188
    #opt_h = opt_help # this conflicted with existing 'host' options.
 
189
 
 
190
    def parseOptions(self, options=None):
 
191
        """
 
192
        The guts of the command-line parser.
 
193
        """
 
194
 
 
195
        if options is None:
 
196
            options = sys.argv[1:]
 
197
        try:
 
198
            opts, args = getopt.getopt(options,
 
199
                                       self.shortOpt, self.longOpt)
 
200
        except getopt.error, e:
 
201
            raise UsageError(str(e))
 
202
 
 
203
        for opt, arg in opts:
 
204
            if opt[1] == '-':
 
205
                opt = opt[2:]
 
206
            else:
 
207
                opt = opt[1:]
 
208
 
 
209
            optMangled = opt
 
210
            if optMangled not in self.synonyms:
 
211
                optMangled = opt.replace("-", "_")
 
212
                if optMangled not in self.synonyms:
 
213
                    raise UsageError("No such option '%s'" % (opt,))
 
214
 
 
215
            optMangled = self.synonyms[optMangled]
 
216
            if isinstance(self._dispatch[optMangled], CoerceParameter):
 
217
                self._dispatch[optMangled].dispatch(optMangled, arg)
 
218
            else:
 
219
                self._dispatch[optMangled](optMangled, arg)
 
220
 
 
221
        if (getattr(self, 'subCommands', None)
 
222
            and (args or self.defaultSubCommand is not None)):
 
223
            if not args:
 
224
                args = [self.defaultSubCommand]
 
225
            sub, rest = args[0], args[1:]
 
226
            for (cmd, short, parser, doc) in self.subCommands:
 
227
                if sub == cmd or sub == short:
 
228
                    self.subCommand = cmd
 
229
                    self.subOptions = parser()
 
230
                    self.subOptions.parent = self
 
231
                    self.subOptions.parseOptions(rest)
 
232
                    break
 
233
            else:
 
234
                raise UsageError("Unknown command: %s" % sub)
 
235
        else:
 
236
            try:
 
237
                self.parseArgs(*args)
 
238
            except TypeError:
 
239
                raise UsageError("Wrong number of arguments.")
 
240
 
 
241
        self.postOptions()
 
242
 
 
243
    def postOptions(self):
 
244
        """
 
245
        I am called after the options are parsed.
 
246
 
 
247
        Override this method in your subclass to do something after
 
248
        the options have been parsed and assigned, like validate that
 
249
        all options are sane.
 
250
        """
 
251
 
 
252
    def parseArgs(self):
 
253
        """
 
254
        I am called with any leftover arguments which were not options.
 
255
 
 
256
        Override me to do something with the remaining arguments on
 
257
        the command line, those which were not flags or options. e.g.
 
258
        interpret them as a list of files to operate on.
 
259
 
 
260
        Note that if there more arguments on the command line
 
261
        than this method accepts, parseArgs will blow up with
 
262
        a getopt.error.  This means if you don't override me,
 
263
        parseArgs will blow up if I am passed any arguments at
 
264
        all!
 
265
        """
 
266
 
 
267
    def _generic_flag(self, flagName, value=None):
 
268
        if value not in ('', None):
 
269
            raise UsageError("Flag '%s' takes no argument."
 
270
                             " Not even \"%s\"." % (flagName, value))
 
271
 
 
272
        self.opts[flagName] = 1
 
273
 
 
274
    def _gather_flags(self):
 
275
        """
 
276
        Gather up boolean (flag) options.
 
277
        """
 
278
 
 
279
        longOpt, shortOpt = [], ''
 
280
        docs, settings, synonyms, dispatch = {}, {}, {}, {}
 
281
 
 
282
        flags = []
 
283
        reflect.accumulateClassList(self.__class__, 'optFlags', flags)
 
284
 
 
285
        for flag in flags:
 
286
            long, short, doc = util.padTo(3, flag)
 
287
            if not long:
 
288
                raise ValueError("A flag cannot be without a name.")
 
289
 
 
290
            docs[long] = doc
 
291
            settings[long] = 0
 
292
            if short:
 
293
                shortOpt = shortOpt + short
 
294
                synonyms[short] = long
 
295
            longOpt.append(long)
 
296
            synonyms[long] = long
 
297
            dispatch[long] = self._generic_flag
 
298
 
 
299
        return longOpt, shortOpt, docs, settings, synonyms, dispatch
 
300
 
 
301
    def _gather_parameters(self):
 
302
        """
 
303
        Gather options which take a value.
 
304
        """
 
305
        longOpt, shortOpt = [], ''
 
306
        docs, settings, synonyms, dispatch = {}, {}, {}, {}
 
307
 
 
308
        parameters = []
 
309
 
 
310
        reflect.accumulateClassList(self.__class__, 'optStrings',
 
311
                                    parameters)
 
312
        if parameters:
 
313
            import warnings
 
314
            warnings.warn("Options.optStrings is deprecated, "
 
315
                          "please use optParameters instead.", stacklevel=2)
 
316
 
 
317
        reflect.accumulateClassList(self.__class__, 'optParameters',
 
318
                                    parameters)
 
319
 
 
320
        synonyms = {}
 
321
 
 
322
        for parameter in parameters:
 
323
            long, short, default, doc, paramType = util.padTo(5, parameter)
 
324
            if not long:
 
325
                raise ValueError("A parameter cannot be without a name.")
 
326
 
 
327
            docs[long] = doc
 
328
            settings[long] = default
 
329
            if short:
 
330
                shortOpt = shortOpt + short + ':'
 
331
                synonyms[short] = long
 
332
            longOpt.append(long + '=')
 
333
            synonyms[long] = long
 
334
            if paramType is not None:
 
335
                dispatch[long] = CoerceParameter(self, paramType)
 
336
            else:
 
337
                dispatch[long] = CoerceParameter(self, str)
 
338
 
 
339
        return longOpt, shortOpt, docs, settings, synonyms, dispatch
 
340
 
 
341
 
 
342
    def _gather_handlers(self):
 
343
        """
 
344
        Gather up options with their own handler methods.
 
345
        """
 
346
 
 
347
        longOpt, shortOpt = [], ''
 
348
        docs, settings, synonyms, dispatch = {}, {}, {}, {}
 
349
 
 
350
        dct = {}
 
351
        reflect.addMethodNamesToDict(self.__class__, dct, "opt_")
 
352
 
 
353
        for name in dct.keys():
 
354
            method = getattr(self, 'opt_'+name)
 
355
 
 
356
            takesArg = not flagFunction(method, name)
 
357
 
 
358
            prettyName = name.replace('_', '-')
 
359
            doc = getattr(method, '__doc__', None)
 
360
            if doc:
 
361
                ## Only use the first line.
 
362
                #docs[name] = doc.split('\n')[0]
 
363
                docs[prettyName] = doc
 
364
            else:
 
365
                docs[prettyName] = self.docs.get(prettyName)
 
366
 
 
367
            synonyms[prettyName] = prettyName
 
368
 
 
369
            # A little slight-of-hand here makes dispatching much easier
 
370
            # in parseOptions, as it makes all option-methods have the
 
371
            # same signature.
 
372
            if takesArg:
 
373
                fn = lambda name, value, m=method: m(value)
 
374
            else:
 
375
                # XXX: This won't raise a TypeError if it's called
 
376
                # with a value when it shouldn't be.
 
377
                fn = lambda name, value=None, m=method: m()
 
378
 
 
379
            dispatch[prettyName] = fn
 
380
 
 
381
            if len(name) == 1:
 
382
                shortOpt = shortOpt + name
 
383
                if takesArg:
 
384
                    shortOpt = shortOpt + ':'
 
385
            else:
 
386
                if takesArg:
 
387
                    prettyName = prettyName + '='
 
388
                longOpt.append(prettyName)
 
389
 
 
390
        reverse_dct = {}
 
391
        # Map synonyms
 
392
        for name in dct.keys():
 
393
            method = getattr(self, 'opt_' + name)
 
394
            if method not in reverse_dct:
 
395
                reverse_dct[method] = []
 
396
            reverse_dct[method].append(name)
 
397
 
 
398
        cmpLength = lambda a, b: cmp(len(a), len(b))
 
399
 
 
400
        for method, names in reverse_dct.items():
 
401
            if len(names) < 2:
 
402
                continue
 
403
            names_ = names[:]
 
404
            names_.sort(cmpLength)
 
405
            longest = names_.pop()
 
406
            for name in names_:
 
407
                synonyms[name] = longest
 
408
 
 
409
        return longOpt, shortOpt, docs, settings, synonyms, dispatch
 
410
 
 
411
 
 
412
    def __str__(self):
 
413
        return self.getSynopsis() + '\n' + self.getUsage(width=None)
 
414
 
 
415
    def getSynopsis(self):
 
416
        """
 
417
        Returns a string containing a description of these options and how to
 
418
        pass them to the executed file.
 
419
        """
 
420
 
 
421
        default = "%s%s" % (path.basename(sys.argv[0]),
 
422
                            (self.longOpt and " [options]") or '')
 
423
        if self.parent is None:
 
424
            default = "Usage: %s%s" % (path.basename(sys.argv[0]),
 
425
                                       (self.longOpt and " [options]") or '')
 
426
        else:
 
427
            default = '%s' % ((self.longOpt and "[options]") or '')
 
428
        synopsis = getattr(self, "synopsis", default)
 
429
 
 
430
        synopsis = synopsis.rstrip()
 
431
 
 
432
        if self.parent is not None:
 
433
            synopsis = ' '.join((self.parent.getSynopsis(),
 
434
                                 self.parent.subCommand, synopsis))
 
435
 
 
436
        return synopsis
 
437
 
 
438
    def getUsage(self, width=None):
 
439
        # If subOptions exists by now, then there was probably an error while
 
440
        # parsing its options.
 
441
        if hasattr(self, 'subOptions'):
 
442
            return self.subOptions.getUsage(width=width)
 
443
 
 
444
        if not width:
 
445
            width = int(os.environ.get('COLUMNS', '80'))
 
446
 
 
447
        if hasattr(self, 'subCommands'):
 
448
            cmdDicts = []
 
449
            for (cmd, short, parser, desc) in self.subCommands:
 
450
                cmdDicts.append(
 
451
                    {'long': cmd,
 
452
                     'short': short,
 
453
                     'doc': desc,
 
454
                     'optType': 'command',
 
455
                     'default': None
 
456
                    })
 
457
            chunks = docMakeChunks(cmdDicts, width)
 
458
            commands = 'Commands:\n' + ''.join(chunks)
 
459
        else:
 
460
            commands = ''
 
461
 
 
462
        longToShort = {}
 
463
        for key, value in self.synonyms.items():
 
464
            longname = value
 
465
            if (key != longname) and (len(key) == 1):
 
466
                longToShort[longname] = key
 
467
            else:
 
468
                if longname not in longToShort:
 
469
                    longToShort[longname] = None
 
470
                else:
 
471
                    pass
 
472
 
 
473
        optDicts = []
 
474
        for opt in self.longOpt:
 
475
            if opt[-1] == '=':
 
476
                optType = 'parameter'
 
477
                opt = opt[:-1]
 
478
            else:
 
479
                optType = 'flag'
 
480
 
 
481
            optDicts.append(
 
482
                {'long': opt,
 
483
                 'short': longToShort[opt],
 
484
                 'doc': self.docs[opt],
 
485
                 'optType': optType,
 
486
                 'default': self.defaults.get(opt, None),
 
487
                 'dispatch': self._dispatch.get(opt, None)
 
488
                 })
 
489
 
 
490
        if not (getattr(self, "longdesc", None) is None):
 
491
            longdesc = self.longdesc
 
492
        else:
 
493
            import __main__
 
494
            if getattr(__main__, '__doc__', None):
 
495
                longdesc = __main__.__doc__
 
496
            else:
 
497
                longdesc = ''
 
498
 
 
499
        if longdesc:
 
500
            longdesc = ('\n' +
 
501
                        '\n'.join(text.wordWrap(longdesc, width)).strip()
 
502
                        + '\n')
 
503
 
 
504
        if optDicts:
 
505
            chunks = docMakeChunks(optDicts, width)
 
506
            s = "Options:\n%s" % (''.join(chunks))
 
507
        else:
 
508
            s = "Options: None\n"
 
509
 
 
510
        return s + longdesc + commands
 
511
 
 
512
    #def __repr__(self):
 
513
    #    XXX: It'd be cool if we could return a succinct representation
 
514
    #        of which flags and options are set here.
 
515
 
 
516
 
 
517
def docMakeChunks(optList, width=80):
 
518
    """
 
519
    Makes doc chunks for option declarations.
 
520
 
 
521
    Takes a list of dictionaries, each of which may have one or more
 
522
    of the keys 'long', 'short', 'doc', 'default', 'optType'.
 
523
 
 
524
    Returns a list of strings.
 
525
    The strings may be multiple lines,
 
526
    all of them end with a newline.
 
527
    """
 
528
 
 
529
    # XXX: sanity check to make sure we have a sane combination of keys.
 
530
 
 
531
    maxOptLen = 0
 
532
    for opt in optList:
 
533
        optLen = len(opt.get('long', ''))
 
534
        if optLen:
 
535
            if opt.get('optType', None) == "parameter":
 
536
                # these take up an extra character
 
537
                optLen = optLen + 1
 
538
            maxOptLen = max(optLen, maxOptLen)
 
539
 
 
540
    colWidth1 = maxOptLen + len("  -s, --  ")
 
541
    colWidth2 = width - colWidth1
 
542
    # XXX - impose some sane minimum limit.
 
543
    # Then if we don't have enough room for the option and the doc
 
544
    # to share one line, they can take turns on alternating lines.
 
545
 
 
546
    colFiller1 = " " * colWidth1
 
547
 
 
548
    optChunks = []
 
549
    seen = {}
 
550
    for opt in optList:
 
551
        if opt.get('short', None) in seen or opt.get('long', None) in seen:
 
552
            continue
 
553
        for x in opt.get('short', None), opt.get('long', None):
 
554
            if x is not None:
 
555
                seen[x] = 1
 
556
 
 
557
        optLines = []
 
558
        comma = " "
 
559
        if opt.get('short', None):
 
560
            short = "-%c" % (opt['short'],)
 
561
        else:
 
562
            short = ''
 
563
 
 
564
        if opt.get('long', None):
 
565
            long = opt['long']
 
566
            if opt.get("optType", None) == "parameter":
 
567
                long = long + '='
 
568
 
 
569
            long = "%-*s" % (maxOptLen, long)
 
570
            if short:
 
571
                comma = ","
 
572
        else:
 
573
            long = " " * (maxOptLen + len('--'))
 
574
 
 
575
        if opt.get('optType', None) == 'command':
 
576
            column1 = '    %s      ' % long
 
577
        else:
 
578
            column1 = "  %2s%c --%s  " % (short, comma, long)
 
579
 
 
580
        if opt.get('doc', ''):
 
581
            doc = opt['doc'].strip()
 
582
        else:
 
583
            doc = ''
 
584
 
 
585
        if (opt.get("optType", None) == "parameter") \
 
586
           and not (opt.get('default', None) is None):
 
587
            doc = "%s [default: %s]" % (doc, opt['default'])
 
588
 
 
589
        if (opt.get("optType", None) == "parameter") \
 
590
           and opt.get('dispatch', None) is not None:
 
591
            d = opt['dispatch']
 
592
            if isinstance(d, CoerceParameter) and d.doc:
 
593
                doc = "%s. %s" % (doc, d.doc)
 
594
 
 
595
        if doc:
 
596
            column2_l = text.wordWrap(doc, colWidth2)
 
597
        else:
 
598
            column2_l = ['']
 
599
 
 
600
        optLines.append("%s%s\n" % (column1, column2_l.pop(0)))
 
601
 
 
602
        for line in column2_l:
 
603
            optLines.append("%s%s\n" % (colFiller1, line))
 
604
 
 
605
        optChunks.append(''.join(optLines))
 
606
 
 
607
    return optChunks
 
608
 
 
609
 
 
610
def flagFunction(method, name=None):
 
611
    reqArgs = method.im_func.func_code.co_argcount
 
612
    if reqArgs > 2:
 
613
        raise UsageError('Invalid Option function for %s' %
 
614
                         (name or method.func_name))
 
615
    if reqArgs == 2:
 
616
        # argName = method.im_func.func_code.co_varnames[1]
 
617
        return 0
 
618
    return 1
 
619
 
 
620
 
 
621
def portCoerce(value):
 
622
    """
 
623
    Coerce a string value to an int port number, and checks the validity.
 
624
    """
 
625
    value = int(value)
 
626
    if value < 0 or value > 65535:
 
627
        raise ValueError("Port number not in range: %s" % (value,))
 
628
    return value
 
629
portCoerce.coerceDoc = "Must be an int between 0 and 65535."
 
630
 
 
631