1
# -*- test-case-name: twisted.test.test_usage -*-
2
# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
3
# See LICENSE for details.
7
twisted.python.usage is a module for parsing/handling the
8
command line of your program.
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.
22
from twisted.python import reflect, text, util
25
class UsageError(Exception):
32
class CoerceParameter(object):
34
Utility class that can corce a parameter before storing it.
36
def __init__(self, options, coerce):
38
@param options: parent Options object
39
@param coerce: callable used to coerce the value.
41
self.options = options
43
self.doc = getattr(self.coerce, 'coerceDoc', '')
45
def dispatch(self, parameterName, value):
47
When called in dispatch, do the coerce for C{value} and save the
51
raise UsageError("Parameter '%s' requires an argument."
54
value = self.coerce(value)
56
raise UsageError("Parameter type enforcement failed: %s" % (e,))
58
self.options.opts[parameterName] = value
63
An option list parser class
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')
71
optFlags is assigned a list of lists. Each list represents
72
a flag parameter, as so::
74
| optFlags = [['verbose', 'v', 'Makes it tell you what it doing.'],
75
| ['quiet', 'q', 'Be vewy vewy quiet.']]
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.
83
C{optParameters} is much the same, except the list also contains
86
| optParameters = [['outfile', 'O', 'outfile.log', 'Description...']]
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.
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::
100
| ['inquisition', 'inquest', InquisitionOptions,
101
| 'Perform an inquisition'],
102
| ['holyquest', 'quest', HolyQuestOptions,
103
| 'Embark upon a holy quest']
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.
116
| defaultSubCommand = 'holyquest'
118
In this case, the subCommand attribute will never be None, and
119
the subOptions attribute will always be set.
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.
127
Advanced functionality is covered in the howto documentation,
129
U{http://twistedmatrix.com/projects/core/documentation/howto/options.html},
130
or doc/howto/options.html in your Twisted directory.
134
defaultSubCommand = None
137
super(Options, self).__init__()
142
# These are strings/lists we will pass to getopt
152
self._gather_parameters,
153
self._gather_handlers,
157
(longOpt, shortOpt, docs, settings, synonyms, dispatch) = c()
158
self.longOpt.extend(longOpt)
159
self.shortOpt = self.shortOpt + shortOpt
160
self.docs.update(docs)
162
self.opts.update(settings)
163
self.defaults.update(settings)
165
self.synonyms.update(synonyms)
166
self._dispatch.update(dispatch)
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.
174
return int(id(self) % sys.maxint)
178
Display this help and exit.
183
def opt_version(self):
184
from twisted import copyright
185
print "Twisted version:", copyright.version
188
#opt_h = opt_help # this conflicted with existing 'host' options.
190
def parseOptions(self, options=None):
192
The guts of the command-line parser.
196
options = sys.argv[1:]
198
opts, args = getopt.getopt(options,
199
self.shortOpt, self.longOpt)
200
except getopt.error, e:
201
raise UsageError(str(e))
203
for opt, arg in opts:
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,))
215
optMangled = self.synonyms[optMangled]
216
if isinstance(self._dispatch[optMangled], CoerceParameter):
217
self._dispatch[optMangled].dispatch(optMangled, arg)
219
self._dispatch[optMangled](optMangled, arg)
221
if (getattr(self, 'subCommands', None)
222
and (args or self.defaultSubCommand is not None)):
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)
234
raise UsageError("Unknown command: %s" % sub)
237
self.parseArgs(*args)
239
raise UsageError("Wrong number of arguments.")
243
def postOptions(self):
245
I am called after the options are parsed.
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.
254
I am called with any leftover arguments which were not options.
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.
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
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))
272
self.opts[flagName] = 1
274
def _gather_flags(self):
276
Gather up boolean (flag) options.
279
longOpt, shortOpt = [], ''
280
docs, settings, synonyms, dispatch = {}, {}, {}, {}
283
reflect.accumulateClassList(self.__class__, 'optFlags', flags)
286
long, short, doc = util.padTo(3, flag)
288
raise ValueError("A flag cannot be without a name.")
293
shortOpt = shortOpt + short
294
synonyms[short] = long
296
synonyms[long] = long
297
dispatch[long] = self._generic_flag
299
return longOpt, shortOpt, docs, settings, synonyms, dispatch
301
def _gather_parameters(self):
303
Gather options which take a value.
305
longOpt, shortOpt = [], ''
306
docs, settings, synonyms, dispatch = {}, {}, {}, {}
310
reflect.accumulateClassList(self.__class__, 'optStrings',
314
warnings.warn("Options.optStrings is deprecated, "
315
"please use optParameters instead.", stacklevel=2)
317
reflect.accumulateClassList(self.__class__, 'optParameters',
322
for parameter in parameters:
323
long, short, default, doc, paramType = util.padTo(5, parameter)
325
raise ValueError("A parameter cannot be without a name.")
328
settings[long] = default
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)
337
dispatch[long] = CoerceParameter(self, str)
339
return longOpt, shortOpt, docs, settings, synonyms, dispatch
342
def _gather_handlers(self):
344
Gather up options with their own handler methods.
347
longOpt, shortOpt = [], ''
348
docs, settings, synonyms, dispatch = {}, {}, {}, {}
351
reflect.addMethodNamesToDict(self.__class__, dct, "opt_")
353
for name in dct.keys():
354
method = getattr(self, 'opt_'+name)
356
takesArg = not flagFunction(method, name)
358
prettyName = name.replace('_', '-')
359
doc = getattr(method, '__doc__', None)
361
## Only use the first line.
362
#docs[name] = doc.split('\n')[0]
363
docs[prettyName] = doc
365
docs[prettyName] = self.docs.get(prettyName)
367
synonyms[prettyName] = prettyName
369
# A little slight-of-hand here makes dispatching much easier
370
# in parseOptions, as it makes all option-methods have the
373
fn = lambda name, value, m=method: m(value)
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()
379
dispatch[prettyName] = fn
382
shortOpt = shortOpt + name
384
shortOpt = shortOpt + ':'
387
prettyName = prettyName + '='
388
longOpt.append(prettyName)
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)
398
cmpLength = lambda a, b: cmp(len(a), len(b))
400
for method, names in reverse_dct.items():
404
names_.sort(cmpLength)
405
longest = names_.pop()
407
synonyms[name] = longest
409
return longOpt, shortOpt, docs, settings, synonyms, dispatch
413
return self.getSynopsis() + '\n' + self.getUsage(width=None)
415
def getSynopsis(self):
417
Returns a string containing a description of these options and how to
418
pass them to the executed file.
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 '')
427
default = '%s' % ((self.longOpt and "[options]") or '')
428
synopsis = getattr(self, "synopsis", default)
430
synopsis = synopsis.rstrip()
432
if self.parent is not None:
433
synopsis = ' '.join((self.parent.getSynopsis(),
434
self.parent.subCommand, synopsis))
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)
445
width = int(os.environ.get('COLUMNS', '80'))
447
if hasattr(self, 'subCommands'):
449
for (cmd, short, parser, desc) in self.subCommands:
454
'optType': 'command',
457
chunks = docMakeChunks(cmdDicts, width)
458
commands = 'Commands:\n' + ''.join(chunks)
463
for key, value in self.synonyms.items():
465
if (key != longname) and (len(key) == 1):
466
longToShort[longname] = key
468
if longname not in longToShort:
469
longToShort[longname] = None
474
for opt in self.longOpt:
476
optType = 'parameter'
483
'short': longToShort[opt],
484
'doc': self.docs[opt],
486
'default': self.defaults.get(opt, None),
487
'dispatch': self._dispatch.get(opt, None)
490
if not (getattr(self, "longdesc", None) is None):
491
longdesc = self.longdesc
494
if getattr(__main__, '__doc__', None):
495
longdesc = __main__.__doc__
501
'\n'.join(text.wordWrap(longdesc, width)).strip()
505
chunks = docMakeChunks(optDicts, width)
506
s = "Options:\n%s" % (''.join(chunks))
508
s = "Options: None\n"
510
return s + longdesc + commands
513
# XXX: It'd be cool if we could return a succinct representation
514
# of which flags and options are set here.
517
def docMakeChunks(optList, width=80):
519
Makes doc chunks for option declarations.
521
Takes a list of dictionaries, each of which may have one or more
522
of the keys 'long', 'short', 'doc', 'default', 'optType'.
524
Returns a list of strings.
525
The strings may be multiple lines,
526
all of them end with a newline.
529
# XXX: sanity check to make sure we have a sane combination of keys.
533
optLen = len(opt.get('long', ''))
535
if opt.get('optType', None) == "parameter":
536
# these take up an extra character
538
maxOptLen = max(optLen, maxOptLen)
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.
546
colFiller1 = " " * colWidth1
551
if opt.get('short', None) in seen or opt.get('long', None) in seen:
553
for x in opt.get('short', None), opt.get('long', None):
559
if opt.get('short', None):
560
short = "-%c" % (opt['short'],)
564
if opt.get('long', None):
566
if opt.get("optType", None) == "parameter":
569
long = "%-*s" % (maxOptLen, long)
573
long = " " * (maxOptLen + len('--'))
575
if opt.get('optType', None) == 'command':
576
column1 = ' %s ' % long
578
column1 = " %2s%c --%s " % (short, comma, long)
580
if opt.get('doc', ''):
581
doc = opt['doc'].strip()
585
if (opt.get("optType", None) == "parameter") \
586
and not (opt.get('default', None) is None):
587
doc = "%s [default: %s]" % (doc, opt['default'])
589
if (opt.get("optType", None) == "parameter") \
590
and opt.get('dispatch', None) is not None:
592
if isinstance(d, CoerceParameter) and d.doc:
593
doc = "%s. %s" % (doc, d.doc)
596
column2_l = text.wordWrap(doc, colWidth2)
600
optLines.append("%s%s\n" % (column1, column2_l.pop(0)))
602
for line in column2_l:
603
optLines.append("%s%s\n" % (colFiller1, line))
605
optChunks.append(''.join(optLines))
610
def flagFunction(method, name=None):
611
reqArgs = method.im_func.func_code.co_argcount
613
raise UsageError('Invalid Option function for %s' %
614
(name or method.func_name))
616
# argName = method.im_func.func_code.co_varnames[1]
621
def portCoerce(value):
623
Coerce a string value to an int port number, and checks the validity.
626
if value < 0 or value > 65535:
627
raise ValueError("Port number not in range: %s" % (value,))
629
portCoerce.coerceDoc = "Must be an int between 0 and 65535."