| """ 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()
|