#!/usr/bin/env python # # COPYRIGHT # # Permission to use, copy, modify, and distribute this software and # its documentation for any purpose and without fee is hereby # granted, provided that the above copyright notice appear in all # copies and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of Doug # Hellmann not be used in advertising or publicity pertaining to # distribution of the software without specific, written prior # permission. # # DISCLAIMER # # DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN # NO EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # """Base class for building command line applications. The CommandLineApp class makes creating command line applications as simple as defining callbacks to handle options when they appear in 'sys.argv'. To do - enhance intelligence of option handling - boolean options should not need to be implemented as functions - enable/disable with/without options - type checking for option arguments """ __rcs_info__ = { # # Creation Information # 'module_name':'$RCSfile: CommandLineApp.py,v $', 'creator':'Doug Hellmann ', 'project':'Open Source', 'created':'Tue, 23-May-2000 07:11:43 EDT', # # Current Information # 'author':'$Author: doughellmann $', 'version':'$Revision: 1.1.1.1 $', 'date':'$Date: 2002/11/17 00:26:14 $', } try: __version__ = __rcs_info__['version'].split(' ')[1] except: __version__ = '0.0' # # Import system modules # import getopt import pprint import sys import string import unittest # # Import Local modules # # # Module # class CommandLineApp: """Base class for building command line applications. Define a __doc__ string for the class to explain what the program does. When the argumentsDescription field is not empty, it will be printed appropriately in the help message. When the examplesDescription field is not empty, it will be printed last in the help message when the user asks for help. """ argumentsDescription = '' shortArgumentsDescription = '' examplesDescription = '' # # Exception names # ReservedOptionName = 'Reserved option name' HelpRequested='Help requested' InvalidOptionValue='Invalid value for option' InvalidArgument='Invalid argument to program' # # Globally useful configuration stuff. # optionHandlerPrefix = 'optionHandler_' optTypeLong = 'optTypeLong' optTypeShort = 'optTypeShort' optTakesParam = 'optTakesParam' optNoParam = 'optNoParam' verboseLevel = 1 _app_version = '0.0' def __init__(self, commandLineOptions=sys.argv[1:]): "Initialize CommandLineApp." self.commandLineOptions = commandLineOptions self.__learnValidOpts__() self.__init_getopt__() self.appInit() def appInit(self): """Override this method to perform application initialization. This hook may be easier to override than the __init__ method, since it takes no parameters. """ pass def shortOptionsStringGet(self): """Given the information learned through self.__learnValidOpts__, construct a string to be passed to getopt to set the valid single character option syntax. """ sas = '' for (optName, optTakesValue, optLongOrShort, ignore, ignore) in self.shortOptions: sas = sas + optName if optTakesValue == self.optTakesParam: sas = sas + ':' return sas def longOptionsListGet(self): """Given the information learned through self.__learnValidOpts__, construct a list to be passed to getopt to set the valid long form option syntax. """ lal = [] for (optName, optTakesValue, optLongOrShort, ignore, ignore) in self.longOptions: if optTakesValue == self.optTakesParam: opt = '%s=' % optName else: opt = optName lal.append(opt) if opt.find('-') >= 0: new_opt = opt.replace('-', '_') lal.append(new_opt) return lal def __init_getopt__(self): "Parse the command line options." shortOptionsString = self.shortOptionsStringGet() + 'h' longOptionList = self.longOptionsListGet() longOptionList.append('help') try: self.parsedOptions, self.remainingOpts = getopt.getopt( self.commandLineOptions, shortOptionsString, longOptionList) except getopt.error, message: self.showHelp(message) raise getopt.error, message return def constructOptionInfo(self, methodName, methodRef): """Return an info tuple for an option handler method. Given a method name, return the information tuple for that option to the program. The tuple contains: (option name, flag showing whether the option takes a value, flag indicating long or short form option, help string for option) """ optionName = methodName[len(self.optionHandlerPrefix):] if len(methodRef.func_code.co_varnames) > 1: optionTakesValue = self.optTakesParam else: optionTakesValue = self.optNoParam if len(optionName) > 1: optionLongForm = self.optTypeLong optionName = optionName.replace('_', '-') else: optionLongForm = self.optTypeShort return (optionName, optionTakesValue, optionLongForm, methodName, methodRef) def methodNameFromOption(self, option): """Given the name of an option, construct and return the name of the method to handle it. """ methodName = '%s%s' % (self.optionHandlerPrefix, option) return methodName def scanClassForOptions(self, cRef): "Scan through the inheritence hierarchy to find option handlers." for parentClass in cRef.__bases__: self.scanClassForOptions(parentClass) for componentName in cRef.__dict__.keys(): component = cRef.__dict__[componentName] if componentName[:len(self.optionHandlerPrefix)] == self.optionHandlerPrefix and \ type(component).__name__ == 'function': optionInfo = self.constructOptionInfo(componentName, component) if optionInfo[0] == 'h': raise CommandLineApp.ReservedOptionName, 'h' self.allOptions[ optionInfo[0] ] = optionInfo self.allMethods[ componentName ] = optionInfo def __learnValidOpts__(self): """Derive the options which are valid for this application. Examine the methods defined for this class to learn what options the developer wants to use. Options can be added by defining an optionHandler method with a name like optionHandler_