Home | History | Annotate | Download | only in pybench
      1 """ CommandLine - Get and parse command line options
      2 
      3     NOTE: This still is very much work in progress !!!
      4 
      5     Different version are likely to be incompatible.
      6 
      7     TODO:
      8 
      9     * Incorporate the changes made by (see Inbox)
     10     * Add number range option using srange()
     11 
     12 """
     13 
     14 __copyright__ = """\
     15 Copyright (c), 1997-2006, Marc-Andre Lemburg (mal (at] lemburg.com)
     16 Copyright (c), 2000-2006, eGenix.com Software GmbH (info (at] egenix.com)
     17 See the documentation for further information on copyrights,
     18 or contact the author. All Rights Reserved.
     19 """
     20 
     21 __version__ = '1.2'
     22 
     23 import sys, getopt, string, glob, os, re, exceptions, traceback
     24 
     25 ### Helpers
     26 
     27 def _getopt_flags(options):
     28 
     29     """ Convert the option list to a getopt flag string and long opt
     30         list
     31 
     32     """
     33     s = []
     34     l = []
     35     for o in options:
     36         if o.prefix == '-':
     37             # short option
     38             s.append(o.name)
     39             if o.takes_argument:
     40                 s.append(':')
     41         else:
     42             # long option
     43             if o.takes_argument:
     44                 l.append(o.name+'=')
     45             else:
     46                 l.append(o.name)
     47     return string.join(s,''),l
     48 
     49 def invisible_input(prompt='>>> '):
     50 
     51     """ Get raw input from a terminal without echoing the characters to
     52         the terminal, e.g. for password queries.
     53 
     54     """
     55     import getpass
     56     entry = getpass.getpass(prompt)
     57     if entry is None:
     58         raise KeyboardInterrupt
     59     return entry
     60 
     61 def fileopen(name, mode='wb', encoding=None):
     62 
     63     """ Open a file using mode.
     64 
     65         Default mode is 'wb' meaning to open the file for writing in
     66         binary mode. If encoding is given, I/O to and from the file is
     67         transparently encoded using the given encoding.
     68 
     69         Files opened for writing are chmod()ed to 0600.
     70 
     71     """
     72     if name == 'stdout':
     73         return sys.stdout
     74     elif name == 'stderr':
     75         return sys.stderr
     76     elif name == 'stdin':
     77         return sys.stdin
     78     else:
     79         if encoding is not None:
     80             import codecs
     81             f = codecs.open(name, mode, encoding)
     82         else:
     83             f = open(name, mode)
     84         if 'w' in mode:
     85             os.chmod(name, 0600)
     86         return f
     87 
     88 def option_dict(options):
     89 
     90     """ Return a dictionary mapping option names to Option instances.
     91     """
     92     d = {}
     93     for option in options:
     94         d[option.name] = option
     95     return d
     96 
     97 # Alias
     98 getpasswd = invisible_input
     99 
    100 _integerRE = re.compile('\s*(-?\d+)\s*$')
    101 _integerRangeRE = re.compile('\s*(-?\d+)\s*-\s*(-?\d+)\s*$')
    102 
    103 def srange(s,
    104 
    105            split=string.split,integer=_integerRE,
    106            integerRange=_integerRangeRE):
    107 
    108     """ Converts a textual representation of integer numbers and ranges
    109         to a Python list.
    110 
    111         Supported formats: 2,3,4,2-10,-1 - -3, 5 - -2
    112 
    113         Values are appended to the created list in the order specified
    114         in the string.
    115 
    116     """
    117     l = []
    118     append = l.append
    119     for entry in split(s,','):
    120         m = integer.match(entry)
    121         if m:
    122             append(int(m.groups()[0]))
    123             continue
    124         m = integerRange.match(entry)
    125         if m:
    126             start,end = map(int,m.groups())
    127             l[len(l):] = range(start,end+1)
    128     return l
    129 
    130 def abspath(path,
    131 
    132             expandvars=os.path.expandvars,expanduser=os.path.expanduser,
    133             join=os.path.join,getcwd=os.getcwd):
    134 
    135     """ Return the corresponding absolute path for path.
    136 
    137         path is expanded in the usual shell ways before
    138         joining it with the current working directory.
    139 
    140     """
    141     try:
    142         path = expandvars(path)
    143     except AttributeError:
    144         pass
    145     try:
    146         path = expanduser(path)
    147     except AttributeError:
    148         pass
    149     return join(getcwd(), path)
    150 
    151 ### Option classes
    152 
    153 class Option:
    154 
    155     """ Option base class. Takes no argument.
    156 
    157     """
    158     default = None
    159     helptext = ''
    160     prefix = '-'
    161     takes_argument = 0
    162     has_default = 0
    163     tab = 15
    164 
    165     def __init__(self,name,help=None):
    166 
    167         if not name[:1] == '-':
    168             raise TypeError,'option names must start with "-"'
    169         if name[1:2] == '-':
    170             self.prefix = '--'
    171             self.name = name[2:]
    172         else:
    173             self.name = name[1:]
    174         if help:
    175             self.help = help
    176 
    177     def __str__(self):
    178 
    179         o = self
    180         name = o.prefix + o.name
    181         if o.takes_argument:
    182             name = name + ' arg'
    183         if len(name) > self.tab:
    184             name = name + '\n' + ' ' * (self.tab + 1 + len(o.prefix))
    185         else:
    186             name = '%-*s ' % (self.tab, name)
    187         description = o.help
    188         if o.has_default:
    189             description = description + ' (%s)' % o.default
    190         return '%s %s' % (name, description)
    191 
    192 class ArgumentOption(Option):
    193 
    194     """ Option that takes an argument.
    195 
    196         An optional default argument can be given.
    197 
    198     """
    199     def __init__(self,name,help=None,default=None):
    200 
    201         # Basemethod
    202         Option.__init__(self,name,help)
    203 
    204         if default is not None:
    205             self.default = default
    206             self.has_default = 1
    207         self.takes_argument = 1
    208 
    209 class SwitchOption(Option):
    210 
    211     """ Options that can be on or off. Has an optional default value.
    212 
    213     """
    214     def __init__(self,name,help=None,default=None):
    215 
    216         # Basemethod
    217         Option.__init__(self,name,help)
    218 
    219         if default is not None:
    220             self.default = default
    221             self.has_default = 1
    222 
    223 ### Application baseclass
    224 
    225 class Application:
    226 
    227     """ Command line application interface with builtin argument
    228         parsing.
    229 
    230     """
    231     # Options the program accepts (Option instances)
    232     options = []
    233 
    234     # Standard settings; these are appended to options in __init__
    235     preset_options = [SwitchOption('-v',
    236                                    'generate verbose output'),
    237                       SwitchOption('-h',
    238                                    'show this help text'),
    239                       SwitchOption('--help',
    240                                    'show this help text'),
    241                       SwitchOption('--debug',
    242                                    'enable debugging'),
    243                       SwitchOption('--copyright',
    244                                    'show copyright'),
    245                       SwitchOption('--examples',
    246                                    'show examples of usage')]
    247 
    248     # The help layout looks like this:
    249     # [header]   - defaults to ''
    250     #
    251     # [synopsis] - formatted as '<self.name> %s' % self.synopsis
    252     #
    253     # options:
    254     # [options]  - formatted from self.options
    255     #
    256     # [version]  - formatted as 'Version:\n %s' % self.version, if given
    257     #
    258     # [about]    - defaults to ''
    259     #
    260     # Note: all fields that do not behave as template are formatted
    261     #       using the instances dictionary as substitution namespace,
    262     #       e.g. %(name)s will be replaced by the applications name.
    263     #
    264 
    265     # Header (default to program name)
    266     header = ''
    267 
    268     # Name (defaults to program name)
    269     name = ''
    270 
    271     # Synopsis (%(name)s is replaced by the program name)
    272     synopsis = '%(name)s [option] files...'
    273 
    274     # Version (optional)
    275     version = ''
    276 
    277     # General information printed after the possible options (optional)
    278     about = ''
    279 
    280     # Examples of usage to show when the --examples option is given (optional)
    281     examples = ''
    282 
    283     # Copyright to show
    284     copyright = __copyright__
    285 
    286     # Apply file globbing ?
    287     globbing = 1
    288 
    289     # Generate debug output ?
    290     debug = 0
    291 
    292     # Generate verbose output ?
    293     verbose = 0
    294 
    295     # Internal errors to catch
    296     InternalError = exceptions.Exception
    297 
    298     # Instance variables:
    299     values = None       # Dictionary of passed options (or default values)
    300                         # indexed by the options name, e.g. '-h'
    301     files = None        # List of passed filenames
    302     optionlist = None   # List of passed options
    303 
    304     def __init__(self,argv=None):
    305 
    306         # Setup application specs
    307         if argv is None:
    308             argv = sys.argv
    309         self.filename = os.path.split(argv[0])[1]
    310         if not self.name:
    311             self.name = os.path.split(self.filename)[1]
    312         else:
    313             self.name = self.name
    314         if not self.header:
    315             self.header = self.name
    316         else:
    317             self.header = self.header
    318 
    319         # Init .arguments list
    320         self.arguments = argv[1:]
    321 
    322         # Setup Option mapping
    323         self.option_map = option_dict(self.options)
    324 
    325         # Append preset options
    326         for option in self.preset_options:
    327             if not self.option_map.has_key(option.name):
    328                 self.add_option(option)
    329 
    330         # Init .files list
    331         self.files = []
    332 
    333         # Start Application
    334         try:
    335             # Process startup
    336             rc = self.startup()
    337             if rc is not None:
    338                 raise SystemExit,rc
    339 
    340             # Parse command line
    341             rc = self.parse()
    342             if rc is not None:
    343                 raise SystemExit,rc
    344 
    345             # Start application
    346             rc = self.main()
    347             if rc is None:
    348                 rc = 0
    349 
    350         except SystemExit,rc:
    351             pass
    352 
    353         except KeyboardInterrupt:
    354             print
    355             print '* User Break'
    356             print
    357             rc = 1
    358 
    359         except self.InternalError:
    360             print
    361             print '* Internal Error (use --debug to display the traceback)'
    362             if self.debug:
    363                 print
    364                 traceback.print_exc(20, sys.stdout)
    365             elif self.verbose:
    366                 print '  %s: %s' % sys.exc_info()[:2]
    367             print
    368             rc = 1
    369 
    370         raise SystemExit,rc
    371 
    372     def add_option(self, option):
    373 
    374         """ Add a new Option instance to the Application dynamically.
    375 
    376             Note that this has to be done *before* .parse() is being
    377             executed.
    378 
    379         """
    380         self.options.append(option)
    381         self.option_map[option.name] = option
    382 
    383     def startup(self):
    384 
    385         """ Set user defined instance variables.
    386 
    387             If this method returns anything other than None, the
    388             process is terminated with the return value as exit code.
    389 
    390         """
    391         return None
    392 
    393     def exit(self, rc=0):
    394 
    395         """ Exit the program.
    396 
    397             rc is used as exit code and passed back to the calling
    398             program. It defaults to 0 which usually means: OK.
    399 
    400         """
    401         raise SystemExit, rc
    402 
    403     def parse(self):
    404 
    405         """ Parse the command line and fill in self.values and self.files.
    406 
    407             After having parsed the options, the remaining command line
    408             arguments are interpreted as files and passed to .handle_files()
    409             for processing.
    410 
    411             As final step the option handlers are called in the order
    412             of the options given on the command line.
    413 
    414         """
    415         # Parse arguments
    416         self.values = values = {}
    417         for o in self.options:
    418             if o.has_default:
    419                 values[o.prefix+o.name] = o.default
    420             else:
    421                 values[o.prefix+o.name] = 0
    422         flags,lflags = _getopt_flags(self.options)
    423         try:
    424             optlist,files = getopt.getopt(self.arguments,flags,lflags)
    425             if self.globbing:
    426                 l = []
    427                 for f in files:
    428                     gf = glob.glob(f)
    429                     if not gf:
    430                         l.append(f)
    431                     else:
    432                         l[len(l):] = gf
    433                 files = l
    434             self.optionlist = optlist
    435             self.files = files + self.files
    436         except getopt.error,why:
    437             self.help(why)
    438             sys.exit(1)
    439 
    440         # Call file handler
    441         rc = self.handle_files(self.files)
    442         if rc is not None:
    443             sys.exit(rc)
    444 
    445         # Call option handlers
    446         for optionname, value in optlist:
    447 
    448             # Try to convert value to integer
    449             try:
    450                 value = string.atoi(value)
    451             except ValueError:
    452                 pass
    453 
    454             # Find handler and call it (or count the number of option
    455             # instances on the command line)
    456             handlername = 'handle' + string.replace(optionname, '-', '_')
    457             try:
    458                 handler = getattr(self, handlername)
    459             except AttributeError:
    460                 if value == '':
    461                     # count the number of occurrences
    462                     if values.has_key(optionname):
    463                         values[optionname] = values[optionname] + 1
    464                     else:
    465                         values[optionname] = 1
    466                 else:
    467                     values[optionname] = value
    468             else:
    469                 rc = handler(value)
    470                 if rc is not None:
    471                     raise SystemExit, rc
    472 
    473         # Apply final file check (for backward compatibility)
    474         rc = self.check_files(self.files)
    475         if rc is not None:
    476             sys.exit(rc)
    477 
    478     def check_files(self,filelist):
    479 
    480         """ Apply some user defined checks on the files given in filelist.
    481 
    482             This may modify filelist in place. A typical application
    483             is checking that at least n files are given.
    484 
    485             If this method returns anything other than None, the
    486             process is terminated with the return value as exit code.
    487 
    488         """
    489         return None
    490 
    491     def help(self,note=''):
    492 
    493         self.print_header()
    494         if self.synopsis:
    495             print 'Synopsis:'
    496             # To remain backward compatible:
    497             try:
    498                 synopsis = self.synopsis % self.name
    499             except (NameError, KeyError, TypeError):
    500                 synopsis = self.synopsis % self.__dict__
    501             print ' ' + synopsis
    502         print
    503         self.print_options()
    504         if self.version:
    505             print 'Version:'
    506             print ' %s' % self.version
    507             print
    508         if self.about:
    509             print string.strip(self.about % self.__dict__)
    510             print
    511         if note:
    512             print '-'*72
    513             print 'Note:',note
    514             print
    515 
    516     def notice(self,note):
    517 
    518         print '-'*72
    519         print 'Note:',note
    520         print '-'*72
    521         print
    522 
    523     def print_header(self):
    524 
    525         print '-'*72
    526         print self.header % self.__dict__
    527         print '-'*72
    528         print
    529 
    530     def print_options(self):
    531 
    532         options = self.options
    533         print 'Options and default settings:'
    534         if not options:
    535             print '  None'
    536             return
    537         long = filter(lambda x: x.prefix == '--', options)
    538         short = filter(lambda x: x.prefix == '-', options)
    539         items = short + long
    540         for o in options:
    541             print ' ',o
    542         print
    543 
    544     #
    545     # Example handlers:
    546     #
    547     # If a handler returns anything other than None, processing stops
    548     # and the return value is passed to sys.exit() as argument.
    549     #
    550 
    551     # File handler
    552     def handle_files(self,files):
    553 
    554         """ This may process the files list in place.
    555         """
    556         return None
    557 
    558     # Short option handler
    559     def handle_h(self,arg):
    560 
    561         self.help()
    562         return 0
    563 
    564     def handle_v(self, value):
    565 
    566         """ Turn on verbose output.
    567         """
    568         self.verbose = 1
    569 
    570     # Handlers for long options have two underscores in their name
    571     def handle__help(self,arg):
    572 
    573         self.help()
    574         return 0
    575 
    576     def handle__debug(self,arg):
    577 
    578         self.debug = 1
    579         # We don't want to catch internal errors:
    580         self.InternalError = None
    581 
    582     def handle__copyright(self,arg):
    583 
    584         self.print_header()
    585         print string.strip(self.copyright % self.__dict__)
    586         print
    587         return 0
    588 
    589     def handle__examples(self,arg):
    590 
    591         self.print_header()
    592         if self.examples:
    593             print 'Examples:'
    594             print
    595             print string.strip(self.examples % self.__dict__)
    596             print
    597         else:
    598             print 'No examples available.'
    599             print
    600         return 0
    601 
    602     def main(self):
    603 
    604         """ Override this method as program entry point.
    605 
    606             The return value is passed to sys.exit() as argument.  If
    607             it is None, 0 is assumed (meaning OK). Unhandled
    608             exceptions are reported with exit status code 1 (see
    609             __init__ for further details).
    610 
    611         """
    612         return None
    613 
    614 # Alias
    615 CommandLine = Application
    616 
    617 def _test():
    618 
    619     class MyApplication(Application):
    620         header = 'Test Application'
    621         version = __version__
    622         options = [Option('-v','verbose')]
    623 
    624         def handle_v(self,arg):
    625             print 'VERBOSE, Yeah !'
    626 
    627     cmd = MyApplication()
    628     if not cmd.values['-h']:
    629         cmd.help()
    630     print 'files:',cmd.files
    631     print 'Bye...'
    632 
    633 if __name__ == '__main__':
    634     _test()
    635