symbian-qemu-0.9.1-12/python-2.6.1/Lib/distutils/fancy_getopt.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     1 """distutils.fancy_getopt
       
     2 
       
     3 Wrapper around the standard getopt module that provides the following
       
     4 additional 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 60923 2008-02-21 18:18:37Z guido.van.rossum $"
       
    14 
       
    15 import sys, string, re
       
    16 from types import *
       
    17 import getopt
       
    18 from 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...
       
    24 longopt_pat = r'[a-zA-Z](?:[a-zA-Z0-9-]*)'
       
    25 longopt_re = re.compile(r'^%s$' % longopt_pat)
       
    26 
       
    27 # For recognizing "negative alias" options, eg. "quiet=!verbose"
       
    28 neg_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).
       
    32 longopt_xlate = string.maketrans('-', '_')
       
    33 
       
    34 class 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 long_option in self.option_index:
       
   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 long_option in self.option_index
       
   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 alias not in self.option_index:
       
   125                 raise DistutilsGetoptError, \
       
   126                       ("invalid %s '%s': "
       
   127                        "option '%s' not defined") % (what, alias, alias)
       
   128             if opt not in self.option_index:
       
   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 
       
   403 def 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 
       
   409 WS_TRANS = string.maketrans(string.whitespace, ' ' * len(string.whitespace))
       
   410 
       
   411 def 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 
       
   472 def 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 
       
   479 class 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 
       
   492 if __name__ == "__main__":
       
   493     text = """\
       
   494 Tra-la-la, supercalifragilisticexpialidocious.
       
   495 How *do* you spell that odd word, anyways?
       
   496 (Someone ask Mary -- she'll know [or she'll
       
   497 say, "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