Viewing file: CommandLine.py (15.47 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
#-*- coding: latin-1 -*- """ 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 (c) 1997-2001, Marc-Andre Lemburg; mailto:mal@lemburg.com Copyright (c) 2000-2001, eGenix.com Software GmbH; mailto:info@egenix.com See the documentation for further information on copyrights, or contact the author. All Rights Reserved. """
__version__ = '1.0'
import sys,getopt,string,glob,os,traceback,re
### 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 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 (c) 1997-2001, Marc-Andre Lemburg; mailto:mal@lemburg.com\n' 'Copyright (c) 2000-2001, eGenix.com Software GmbH; mailto:info@egenix.com\n' 'See the documentation for further information on copyrights,\n' 'or contact the author. All Rights Reserved.\n' '*** UNAUTHORIZED COPYING, USAGE or DISTRIBUTION PROHIBITED. ***' )
# Apply file globbing ? globbing = 1
# Generate debug output ? debug = 0
# Generate verbose output ? verbose = 0
# 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
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' rc = 1
except: print print '* Internal Error' if self.debug: print traceback.print_exc(20) 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
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()
|