Initial commit of OpenSPARC T2 architecture model.
[OpenSPARC-T2-SAM] / sam-t2 / devtools / v8plus / lib / python2.4 / distutils / fancy_getopt.py
CommitLineData
920dae64
AT
1"""distutils.fancy_getopt
2
3Wrapper around the standard getopt module that provides the following
4additional features:
5 * short and long options are tied together
6 * options have help strings, so fancy_getopt could potentially
7 create a complete usage summary
8 * options set attributes of a passed-in object
9"""
10
11# This module should be kept compatible with Python 2.1.
12
13__revision__ = "$Id: fancy_getopt.py,v 1.30 2004/11/10 22:23:14 loewis Exp $"
14
15import sys, string, re
16from types import *
17import getopt
18from distutils.errors import *
19
20# Much like command_re in distutils.core, this is close to but not quite
21# the same as a Python NAME -- except, in the spirit of most GNU
22# utilities, we use '-' in place of '_'. (The spirit of LISP lives on!)
23# The similarities to NAME are again not a coincidence...
24longopt_pat = r'[a-zA-Z](?:[a-zA-Z0-9-]*)'
25longopt_re = re.compile(r'^%s$' % longopt_pat)
26
27# For recognizing "negative alias" options, eg. "quiet=!verbose"
28neg_alias_re = re.compile("^(%s)=!(%s)$" % (longopt_pat, longopt_pat))
29
30# This is used to translate long options to legitimate Python identifiers
31# (for use as attributes of some object).
32longopt_xlate = string.maketrans('-', '_')
33
34class FancyGetopt:
35 """Wrapper around the standard 'getopt()' module that provides some
36 handy extra functionality:
37 * short and long options are tied together
38 * options have help strings, and help text can be assembled
39 from them
40 * options set attributes of a passed-in object
41 * boolean options can have "negative aliases" -- eg. if
42 --quiet is the "negative alias" of --verbose, then "--quiet"
43 on the command line sets 'verbose' to false
44 """
45
46 def __init__ (self, option_table=None):
47
48 # The option table is (currently) a list of tuples. The
49 # tuples may have 3 or four values:
50 # (long_option, short_option, help_string [, repeatable])
51 # if an option takes an argument, its long_option should have '='
52 # appended; short_option should just be a single character, no ':'
53 # in any case. If a long_option doesn't have a corresponding
54 # short_option, short_option should be None. All option tuples
55 # must have long options.
56 self.option_table = option_table
57
58 # 'option_index' maps long option names to entries in the option
59 # table (ie. those 3-tuples).
60 self.option_index = {}
61 if self.option_table:
62 self._build_index()
63
64 # 'alias' records (duh) alias options; {'foo': 'bar'} means
65 # --foo is an alias for --bar
66 self.alias = {}
67
68 # 'negative_alias' keeps track of options that are the boolean
69 # opposite of some other option
70 self.negative_alias = {}
71
72 # These keep track of the information in the option table. We
73 # don't actually populate these structures until we're ready to
74 # parse the command-line, since the 'option_table' passed in here
75 # isn't necessarily the final word.
76 self.short_opts = []
77 self.long_opts = []
78 self.short2long = {}
79 self.attr_name = {}
80 self.takes_arg = {}
81
82 # And 'option_order' is filled up in 'getopt()'; it records the
83 # original order of options (and their values) on the command-line,
84 # but expands short options, converts aliases, etc.
85 self.option_order = []
86
87 # __init__ ()
88
89
90 def _build_index (self):
91 self.option_index.clear()
92 for option in self.option_table:
93 self.option_index[option[0]] = option
94
95 def set_option_table (self, option_table):
96 self.option_table = option_table
97 self._build_index()
98
99 def add_option (self, long_option, short_option=None, help_string=None):
100 if self.option_index.has_key(long_option):
101 raise DistutilsGetoptError, \
102 "option conflict: already an option '%s'" % long_option
103 else:
104 option = (long_option, short_option, help_string)
105 self.option_table.append(option)
106 self.option_index[long_option] = option
107
108
109 def has_option (self, long_option):
110 """Return true if the option table for this parser has an
111 option with long name 'long_option'."""
112 return self.option_index.has_key(long_option)
113
114 def get_attr_name (self, long_option):
115 """Translate long option name 'long_option' to the form it
116 has as an attribute of some object: ie., translate hyphens
117 to underscores."""
118 return string.translate(long_option, longopt_xlate)
119
120
121 def _check_alias_dict (self, aliases, what):
122 assert type(aliases) is DictionaryType
123 for (alias, opt) in aliases.items():
124 if not self.option_index.has_key(alias):
125 raise DistutilsGetoptError, \
126 ("invalid %s '%s': "
127 "option '%s' not defined") % (what, alias, alias)
128 if not self.option_index.has_key(opt):
129 raise DistutilsGetoptError, \
130 ("invalid %s '%s': "
131 "aliased option '%s' not defined") % (what, alias, opt)
132
133 def set_aliases (self, alias):
134 """Set the aliases for this option parser."""
135 self._check_alias_dict(alias, "alias")
136 self.alias = alias
137
138 def set_negative_aliases (self, negative_alias):
139 """Set the negative aliases for this option parser.
140 'negative_alias' should be a dictionary mapping option names to
141 option names, both the key and value must already be defined
142 in the option table."""
143 self._check_alias_dict(negative_alias, "negative alias")
144 self.negative_alias = negative_alias
145
146
147 def _grok_option_table (self):
148 """Populate the various data structures that keep tabs on the
149 option table. Called by 'getopt()' before it can do anything
150 worthwhile.
151 """
152 self.long_opts = []
153 self.short_opts = []
154 self.short2long.clear()
155 self.repeat = {}
156
157 for option in self.option_table:
158 if len(option) == 3:
159 long, short, help = option
160 repeat = 0
161 elif len(option) == 4:
162 long, short, help, repeat = option
163 else:
164 # the option table is part of the code, so simply
165 # assert that it is correct
166 raise ValueError, "invalid option tuple: %r" % (option,)
167
168 # Type- and value-check the option names
169 if type(long) is not StringType or len(long) < 2:
170 raise DistutilsGetoptError, \
171 ("invalid long option '%s': "
172 "must be a string of length >= 2") % long
173
174 if (not ((short is None) or
175 (type(short) is StringType and len(short) == 1))):
176 raise DistutilsGetoptError, \
177 ("invalid short option '%s': "
178 "must a single character or None") % short
179
180 self.repeat[long] = repeat
181 self.long_opts.append(long)
182
183 if long[-1] == '=': # option takes an argument?
184 if short: short = short + ':'
185 long = long[0:-1]
186 self.takes_arg[long] = 1
187 else:
188
189 # Is option is a "negative alias" for some other option (eg.
190 # "quiet" == "!verbose")?
191 alias_to = self.negative_alias.get(long)
192 if alias_to is not None:
193 if self.takes_arg[alias_to]:
194 raise DistutilsGetoptError, \
195 ("invalid negative alias '%s': "
196 "aliased option '%s' takes a value") % \
197 (long, alias_to)
198
199 self.long_opts[-1] = long # XXX redundant?!
200 self.takes_arg[long] = 0
201
202 else:
203 self.takes_arg[long] = 0
204
205 # If this is an alias option, make sure its "takes arg" flag is
206 # the same as the option it's aliased to.
207 alias_to = self.alias.get(long)
208 if alias_to is not None:
209 if self.takes_arg[long] != self.takes_arg[alias_to]:
210 raise DistutilsGetoptError, \
211 ("invalid alias '%s': inconsistent with "
212 "aliased option '%s' (one of them takes a value, "
213 "the other doesn't") % (long, alias_to)
214
215
216 # Now enforce some bondage on the long option name, so we can
217 # later translate it to an attribute name on some object. Have
218 # to do this a bit late to make sure we've removed any trailing
219 # '='.
220 if not longopt_re.match(long):
221 raise DistutilsGetoptError, \
222 ("invalid long option name '%s' " +
223 "(must be letters, numbers, hyphens only") % long
224
225 self.attr_name[long] = self.get_attr_name(long)
226 if short:
227 self.short_opts.append(short)
228 self.short2long[short[0]] = long
229
230 # for option_table
231
232 # _grok_option_table()
233
234
235 def getopt (self, args=None, object=None):
236 """Parse command-line options in args. Store as attributes on object.
237
238 If 'args' is None or not supplied, uses 'sys.argv[1:]'. If
239 'object' is None or not supplied, creates a new OptionDummy
240 object, stores option values there, and returns a tuple (args,
241 object). If 'object' is supplied, it is modified in place and
242 'getopt()' just returns 'args'; in both cases, the returned
243 'args' is a modified copy of the passed-in 'args' list, which
244 is left untouched.
245 """
246 if args is None:
247 args = sys.argv[1:]
248 if object is None:
249 object = OptionDummy()
250 created_object = 1
251 else:
252 created_object = 0
253
254 self._grok_option_table()
255
256 short_opts = string.join(self.short_opts)
257 try:
258 opts, args = getopt.getopt(args, short_opts, self.long_opts)
259 except getopt.error, msg:
260 raise DistutilsArgError, msg
261
262 for opt, val in opts:
263 if len(opt) == 2 and opt[0] == '-': # it's a short option
264 opt = self.short2long[opt[1]]
265 else:
266 assert len(opt) > 2 and opt[:2] == '--'
267 opt = opt[2:]
268
269 alias = self.alias.get(opt)
270 if alias:
271 opt = alias
272
273 if not self.takes_arg[opt]: # boolean option?
274 assert val == '', "boolean option can't have value"
275 alias = self.negative_alias.get(opt)
276 if alias:
277 opt = alias
278 val = 0
279 else:
280 val = 1
281
282 attr = self.attr_name[opt]
283 # The only repeating option at the moment is 'verbose'.
284 # It has a negative option -q quiet, which should set verbose = 0.
285 if val and self.repeat.get(attr) is not None:
286 val = getattr(object, attr, 0) + 1
287 setattr(object, attr, val)
288 self.option_order.append((opt, val))
289
290 # for opts
291 if created_object:
292 return args, object
293 else:
294 return args
295
296 # getopt()
297
298
299 def get_option_order (self):
300 """Returns the list of (option, value) tuples processed by the
301 previous run of 'getopt()'. Raises RuntimeError if
302 'getopt()' hasn't been called yet.
303 """
304 if self.option_order is None:
305 raise RuntimeError, "'getopt()' hasn't been called yet"
306 else:
307 return self.option_order
308
309
310 def generate_help (self, header=None):
311 """Generate help text (a list of strings, one per suggested line of
312 output) from the option table for this FancyGetopt object.
313 """
314 # Blithely assume the option table is good: probably wouldn't call
315 # 'generate_help()' unless you've already called 'getopt()'.
316
317 # First pass: determine maximum length of long option names
318 max_opt = 0
319 for option in self.option_table:
320 long = option[0]
321 short = option[1]
322 l = len(long)
323 if long[-1] == '=':
324 l = l - 1
325 if short is not None:
326 l = l + 5 # " (-x)" where short == 'x'
327 if l > max_opt:
328 max_opt = l
329
330 opt_width = max_opt + 2 + 2 + 2 # room for indent + dashes + gutter
331
332 # Typical help block looks like this:
333 # --foo controls foonabulation
334 # Help block for longest option looks like this:
335 # --flimflam set the flim-flam level
336 # and with wrapped text:
337 # --flimflam set the flim-flam level (must be between
338 # 0 and 100, except on Tuesdays)
339 # Options with short names will have the short name shown (but
340 # it doesn't contribute to max_opt):
341 # --foo (-f) controls foonabulation
342 # If adding the short option would make the left column too wide,
343 # we push the explanation off to the next line
344 # --flimflam (-l)
345 # set the flim-flam level
346 # Important parameters:
347 # - 2 spaces before option block start lines
348 # - 2 dashes for each long option name
349 # - min. 2 spaces between option and explanation (gutter)
350 # - 5 characters (incl. space) for short option name
351
352 # Now generate lines of help text. (If 80 columns were good enough
353 # for Jesus, then 78 columns are good enough for me!)
354 line_width = 78
355 text_width = line_width - opt_width
356 big_indent = ' ' * opt_width
357 if header:
358 lines = [header]
359 else:
360 lines = ['Option summary:']
361
362 for option in self.option_table:
363 long, short, help = option[:3]
364 text = wrap_text(help, text_width)
365 if long[-1] == '=':
366 long = long[0:-1]
367
368 # Case 1: no short option at all (makes life easy)
369 if short is None:
370 if text:
371 lines.append(" --%-*s %s" % (max_opt, long, text[0]))
372 else:
373 lines.append(" --%-*s " % (max_opt, long))
374
375 # Case 2: we have a short option, so we have to include it
376 # just after the long option
377 else:
378 opt_names = "%s (-%s)" % (long, short)
379 if text:
380 lines.append(" --%-*s %s" %
381 (max_opt, opt_names, text[0]))
382 else:
383 lines.append(" --%-*s" % opt_names)
384
385 for l in text[1:]:
386 lines.append(big_indent + l)
387
388 # for self.option_table
389
390 return lines
391
392 # generate_help ()
393
394 def print_help (self, header=None, file=None):
395 if file is None:
396 file = sys.stdout
397 for line in self.generate_help(header):
398 file.write(line + "\n")
399
400# class FancyGetopt
401
402
403def fancy_getopt (options, negative_opt, object, args):
404 parser = FancyGetopt(options)
405 parser.set_negative_aliases(negative_opt)
406 return parser.getopt(args, object)
407
408
409WS_TRANS = string.maketrans(string.whitespace, ' ' * len(string.whitespace))
410
411def wrap_text (text, width):
412 """wrap_text(text : string, width : int) -> [string]
413
414 Split 'text' into multiple lines of no more than 'width' characters
415 each, and return the list of strings that results.
416 """
417
418 if text is None:
419 return []
420 if len(text) <= width:
421 return [text]
422
423 text = string.expandtabs(text)
424 text = string.translate(text, WS_TRANS)
425 chunks = re.split(r'( +|-+)', text)
426 chunks = filter(None, chunks) # ' - ' results in empty strings
427 lines = []
428
429 while chunks:
430
431 cur_line = [] # list of chunks (to-be-joined)
432 cur_len = 0 # length of current line
433
434 while chunks:
435 l = len(chunks[0])
436 if cur_len + l <= width: # can squeeze (at least) this chunk in
437 cur_line.append(chunks[0])
438 del chunks[0]
439 cur_len = cur_len + l
440 else: # this line is full
441 # drop last chunk if all space
442 if cur_line and cur_line[-1][0] == ' ':
443 del cur_line[-1]
444 break
445
446 if chunks: # any chunks left to process?
447
448 # if the current line is still empty, then we had a single
449 # chunk that's too big too fit on a line -- so we break
450 # down and break it up at the line width
451 if cur_len == 0:
452 cur_line.append(chunks[0][0:width])
453 chunks[0] = chunks[0][width:]
454
455 # all-whitespace chunks at the end of a line can be discarded
456 # (and we know from the re.split above that if a chunk has
457 # *any* whitespace, it is *all* whitespace)
458 if chunks[0][0] == ' ':
459 del chunks[0]
460
461 # and store this line in the list-of-all-lines -- as a single
462 # string, of course!
463 lines.append(string.join(cur_line, ''))
464
465 # while chunks
466
467 return lines
468
469# wrap_text ()
470
471
472def translate_longopt (opt):
473 """Convert a long option name to a valid Python identifier by
474 changing "-" to "_".
475 """
476 return string.translate(opt, longopt_xlate)
477
478
479class OptionDummy:
480 """Dummy class just used as a place to hold command-line option
481 values as instance attributes."""
482
483 def __init__ (self, options=[]):
484 """Create a new OptionDummy instance. The attributes listed in
485 'options' will be initialized to None."""
486 for opt in options:
487 setattr(self, opt, None)
488
489# class OptionDummy
490
491
492if __name__ == "__main__":
493 text = """\
494Tra-la-la, supercalifragilisticexpialidocious.
495How *do* you spell that odd word, anyways?
496(Someone ask Mary -- she'll know [or she'll
497say, "How should I know?"].)"""
498
499 for w in (10, 20, 30, 40):
500 print "width: %d" % w
501 print string.join(wrap_text(text, w), "\n")
502 print