ref: a4ab99da40e0e86aa61d145e038066e8210c27a9
dir: /sys/src/cmd/python/Tools/pybench/CommandLine.py/
""" CommandLine - Get and parse command line options
    NOTE: This still is very much work in progress !!!
    Different version are likely to be incompatible.
    TODO:
    * Incorporate the changes made by (see Inbox)
    * Add number range option using srange()
"""
__copyright__ = """\
Copyright (c), 1997-2006, Marc-Andre Lemburg (mal@lemburg.com)
Copyright (c), 2000-2006, eGenix.com Software GmbH (info@egenix.com)
See the documentation for further information on copyrights,
or contact the author. All Rights Reserved.
"""
__version__ = '1.2'
import sys, getopt, string, glob, os, re, exceptions, traceback
### Helpers
def _getopt_flags(options):
    """ Convert the option list to a getopt flag string and long opt
        list
    """
    s = []
    l = []
    for o in options:
        if o.prefix == '-':
            # short option
            s.append(o.name)
            if o.takes_argument:
                s.append(':')
        else:
            # long option
            if o.takes_argument:
                l.append(o.name+'=')
            else:
                l.append(o.name)
    return string.join(s,''),l
def invisible_input(prompt='>>> '):
    """ Get raw input from a terminal without echoing the characters to
        the terminal, e.g. for password queries.
    """
    import getpass
    entry = getpass.getpass(prompt)
    if entry is None:
        raise KeyboardInterrupt
    return entry
def fileopen(name, mode='wb', encoding=None):
    """ Open a file using mode.
        Default mode is 'wb' meaning to open the file for writing in
        binary mode. If encoding is given, I/O to and from the file is
        transparently encoded using the given encoding.
        Files opened for writing are chmod()ed to 0600.
    """
    if name == 'stdout':
        return sys.stdout
    elif name == 'stderr':
        return sys.stderr
    elif name == 'stdin':
        return sys.stdin
    else:
        if encoding is not None:
            import codecs
            f = codecs.open(name, mode, encoding)
        else:
            f = open(name, mode)
        if 'w' in mode:
            os.chmod(name, 0600)
        return f
def option_dict(options):
    """ Return a dictionary mapping option names to Option instances.
    """
    d = {}
    for option in options:
        d[option.name] = option
    return d
# Alias
getpasswd = invisible_input
_integerRE = re.compile('\s*(-?\d+)\s*$')
_integerRangeRE = re.compile('\s*(-?\d+)\s*-\s*(-?\d+)\s*$')
def srange(s,
           split=string.split,integer=_integerRE,
           integerRange=_integerRangeRE):
    """ Converts a textual representation of integer numbers and ranges
        to a Python list.
        Supported formats: 2,3,4,2-10,-1 - -3, 5 - -2
        Values are appended to the created list in the order specified
        in the string.
    """
    l = []
    append = l.append
    for entry in split(s,','):
        m = integer.match(entry)
        if m:
            append(int(m.groups()[0]))
            continue
        m = integerRange.match(entry)
        if m:
            start,end = map(int,m.groups())
            l[len(l):] = range(start,end+1)
    return l
def abspath(path,
            expandvars=os.path.expandvars,expanduser=os.path.expanduser,
            join=os.path.join,getcwd=os.getcwd):
    """ Return the corresponding absolute path for path.
        path is expanded in the usual shell ways before
        joining it with the current working directory.
    """
    try:
        path = expandvars(path)
    except AttributeError:
        pass
    try:
        path = expanduser(path)
    except AttributeError:
        pass
    return join(getcwd(), path)
### Option classes
class Option:
    """ Option base class. Takes no argument.
    """
    default = None
    helptext = ''
    prefix = '-'
    takes_argument = 0
    has_default = 0
    tab = 15
    def __init__(self,name,help=None):
        if not name[:1] == '-':
            raise TypeError,'option names must start with "-"'
        if name[1:2] == '-':
            self.prefix = '--'
            self.name = name[2:]
        else:
            self.name = name[1:]
        if help:
            self.help = help
    def __str__(self):
        o = self
        name = o.prefix + o.name
        if o.takes_argument:
            name = name + ' arg'
        if len(name) > self.tab:
            name = name + '\n' + ' ' * (self.tab + 1 + len(o.prefix))
        else:
            name = '%-*s ' % (self.tab, name)
        description = o.help
        if o.has_default:
            description = description + ' (%s)' % o.default
        return '%s %s' % (name, description)
class ArgumentOption(Option):
    """ Option that takes an argument.
        An optional default argument can be given.
    """
    def __init__(self,name,help=None,default=None):
        # Basemethod
        Option.__init__(self,name,help)
        if default is not None:
            self.default = default
            self.has_default = 1
        self.takes_argument = 1
class SwitchOption(Option):
    """ Options that can be on or off. Has an optional default value.
    """
    def __init__(self,name,help=None,default=None):
        # Basemethod
        Option.__init__(self,name,help)
        if default is not None:
            self.default = default
            self.has_default = 1
### Application baseclass
class Application:
    """ Command line application interface with builtin argument
        parsing.
    """
    # Options the program accepts (Option instances)
    options = []
    # Standard settings; these are appended to options in __init__
    preset_options = [SwitchOption('-v',
                                   'generate verbose output'),
                      SwitchOption('-h',
                                   'show this help text'),
                      SwitchOption('--help',
                                   'show this help text'),
                      SwitchOption('--debug',
                                   'enable debugging'),
                      SwitchOption('--copyright',
                                   'show copyright'),
                      SwitchOption('--examples',
                                   'show examples of usage')]
    # The help layout looks like this:
    # [header]   - defaults to ''
    #
    # [synopsis] - formatted as '<self.name> %s' % self.synopsis
    #
    # options:
    # [options]  - formatted from self.options
    #
    # [version]  - formatted as 'Version:\n %s' % self.version, if given
    #
    # [about]    - defaults to ''
    #
    # Note: all fields that do not behave as template are formatted
    #       using the instances dictionary as substitution namespace,
    #       e.g. %(name)s will be replaced by the applications name.
    #
    # Header (default to program name)
    header = ''
    # Name (defaults to program name)
    name = ''
    # Synopsis (%(name)s is replaced by the program name)
    synopsis = '%(name)s [option] files...'
    # Version (optional)
    version = ''
    # General information printed after the possible options (optional)
    about = ''
    # Examples of usage to show when the --examples option is given (optional)
    examples = ''
    # Copyright to show
    copyright = __copyright__
    # Apply file globbing ?
    globbing = 1
    # Generate debug output ?
    debug = 0
    # Generate verbose output ?
    verbose = 0
    # Internal errors to catch
    InternalError = exceptions.Exception
    # Instance variables:
    values = None       # Dictionary of passed options (or default values)
                        # indexed by the options name, e.g. '-h'
    files = None        # List of passed filenames
    optionlist = None   # List of passed options
    def __init__(self,argv=None):
        # Setup application specs
        if argv is None:
            argv = sys.argv
        self.filename = os.path.split(argv[0])[1]
        if not self.name:
            self.name = os.path.split(self.filename)[1]
        else:
            self.name = self.name
        if not self.header:
            self.header = self.name
        else:
            self.header = self.header
        # Init .arguments list
        self.arguments = argv[1:]
        # Setup Option mapping
        self.option_map = option_dict(self.options)
        # Append preset options
        for option in self.preset_options:
            if not self.option_map.has_key(option.name):
                self.add_option(option)
        # Init .files list
        self.files = []
        # Start Application
        try:
            # Process startup
            rc = self.startup()
            if rc is not None:
                raise SystemExit,rc
            # Parse command line
            rc = self.parse()
            if rc is not None:
                raise SystemExit,rc
            # Start application
            rc = self.main()
            if rc is None:
                rc = 0
        except SystemExit,rc:
            pass
        except KeyboardInterrupt:
            print
            print '* User Break'
            print
            rc = 1
        except self.InternalError:
            print
            print '* Internal Error (use --debug to display the traceback)'
            if self.debug:
                print
                traceback.print_exc(20, sys.stdout)
            elif self.verbose:
                print '  %s: %s' % sys.exc_info()[:2]
            print
            rc = 1
        raise SystemExit,rc
    def add_option(self, option):
        """ Add a new Option instance to the Application dynamically.
            Note that this has to be done *before* .parse() is being
            executed.
        """
        self.options.append(option)
        self.option_map[option.name] = option
    def startup(self):
        """ Set user defined instance variables.
            If this method returns anything other than None, the
            process is terminated with the return value as exit code.
        """
        return None
    def exit(self, rc=0):
        """ Exit the program.
            rc is used as exit code and passed back to the calling
            program. It defaults to 0 which usually means: OK.
        """
        raise SystemExit, rc
    def parse(self):
        """ Parse the command line and fill in self.values and self.files.
            After having parsed the options, the remaining command line
            arguments are interpreted as files and passed to .handle_files()
            for processing.
            As final step the option handlers are called in the order
            of the options given on the command line.
        """
        # Parse arguments
        self.values = values = {}
        for o in self.options:
            if o.has_default:
                values[o.prefix+o.name] = o.default
            else:
                values[o.prefix+o.name] = 0
        flags,lflags = _getopt_flags(self.options)
        try:
            optlist,files = getopt.getopt(self.arguments,flags,lflags)
            if self.globbing:
                l = []
                for f in files:
                    gf = glob.glob(f)
                    if not gf:
                        l.append(f)
                    else:
                        l[len(l):] = gf
                files = l
            self.optionlist = optlist
            self.files = files + self.files
        except getopt.error,why:
            self.help(why)
            sys.exit(1)
        # Call file handler
        rc = self.handle_files(self.files)
        if rc is not None:
            sys.exit(rc)
        # Call option handlers
        for optionname, value in optlist:
            # Try to convert value to integer
            try:
                value = string.atoi(value)
            except ValueError:
                pass
            # Find handler and call it (or count the number of option
            # instances on the command line)
            handlername = 'handle' + string.replace(optionname, '-', '_')
            try:
                handler = getattr(self, handlername)
            except AttributeError:
                if value == '':
                    # count the number of occurances
                    if values.has_key(optionname):
                        values[optionname] = values[optionname] + 1
                    else:
                        values[optionname] = 1
                else:
                    values[optionname] = value
            else:
                rc = handler(value)
                if rc is not None:
                    raise SystemExit, rc
        # Apply final file check (for backward compatibility)
        rc = self.check_files(self.files)
        if rc is not None:
            sys.exit(rc)
    def check_files(self,filelist):
        """ Apply some user defined checks on the files given in filelist.
            This may modify filelist in place. A typical application
            is checking that at least n files are given.
            If this method returns anything other than None, the
            process is terminated with the return value as exit code.
        """
        return None
    def help(self,note=''):
        self.print_header()
        if self.synopsis:
            print 'Synopsis:'
            # To remain backward compatible:
            try:
                synopsis = self.synopsis % self.name
            except (NameError, KeyError, TypeError):
                synopsis = self.synopsis % self.__dict__
            print ' ' + synopsis
        print
        self.print_options()
        if self.version:
            print 'Version:'
            print ' %s' % self.version
            print
        if self.about:
            print string.strip(self.about % self.__dict__)
            print
        if note:
            print '-'*72
            print 'Note:',note
            print
    def notice(self,note):
        print '-'*72
        print 'Note:',note
        print '-'*72
        print
    def print_header(self):
        print '-'*72
        print self.header % self.__dict__
        print '-'*72
        print
    def print_options(self):
        options = self.options
        print 'Options and default settings:'
        if not options:
            print '  None'
            return
        long = filter(lambda x: x.prefix == '--', options)
        short = filter(lambda x: x.prefix == '-', options)
        items = short + long
        for o in options:
            print ' ',o
        print
    #
    # Example handlers:
    #
    # If a handler returns anything other than None, processing stops
    # and the return value is passed to sys.exit() as argument.
    #
    # File handler
    def handle_files(self,files):
        """ This may process the files list in place.
        """
        return None
    # Short option handler
    def handle_h(self,arg):
        self.help()
        return 0
    def handle_v(self, value):
        """ Turn on verbose output.
        """
        self.verbose = 1
    # Handlers for long options have two underscores in their name
    def handle__help(self,arg):
        self.help()
        return 0
    def handle__debug(self,arg):
        self.debug = 1
        # We don't want to catch internal errors:
        self.InternalError = None
    def handle__copyright(self,arg):
        self.print_header()
        print string.strip(self.copyright % self.__dict__)
        print
        return 0
    def handle__examples(self,arg):
        self.print_header()
        if self.examples:
            print 'Examples:'
            print
            print string.strip(self.examples % self.__dict__)
            print
        else:
            print 'No examples available.'
            print
        return 0
    def main(self):
        """ Override this method as program entry point.
            The return value is passed to sys.exit() as argument.  If
            it is None, 0 is assumed (meaning OK). Unhandled
            exceptions are reported with exit status code 1 (see
            __init__ for further details).
        """
        return None
# Alias
CommandLine = Application
def _test():
    class MyApplication(Application):
        header = 'Test Application'
        version = __version__
        options = [Option('-v','verbose')]
        def handle_v(self,arg):
            print 'VERBOSE, Yeah !'
    cmd = MyApplication()
    if not cmd.values['-h']:
        cmd.help()
    print 'files:',cmd.files
    print 'Bye...'
if __name__ == '__main__':
    _test()