| """ 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() | 
 |  | 
 | """ | 
 |  | 
 | from __future__ import print_function | 
 |  | 
 | __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, glob, os, re, 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 ''.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, 0o600) | 
 |         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, | 
 |  | 
 |            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 s.split(','): | 
 |         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 = BaseException | 
 |  | 
 |     # 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 option.name in self.option_map: | 
 |                 self.add_option(option) | 
 |  | 
 |         # Init .files list | 
 |         self.files = [] | 
 |  | 
 |         # Start Application | 
 |         rc = 0 | 
 |         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 as rcException: | 
 |             rc = rcException | 
 |             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 as 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 = int(value) | 
 |             except ValueError: | 
 |                 pass | 
 |  | 
 |             # Find handler and call it (or count the number of option | 
 |             # instances on the command line) | 
 |             handlername = 'handle' + optionname.replace('-', '_') | 
 |             try: | 
 |                 handler = getattr(self, handlername) | 
 |             except AttributeError: | 
 |                 if value == '': | 
 |                     # count the number of occurrences | 
 |                     if optionname in values: | 
 |                         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: | 
 |             about = self.about % self.__dict__ | 
 |             print(about.strip()) | 
 |             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 | 
 |         int = [x for x in options if x.prefix == '--'] | 
 |         short = [x for x in options if x.prefix == '-'] | 
 |         items = short + int | 
 |         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: | 
 |         class NoErrorToCatch(Exception): pass | 
 |         self.InternalError = NoErrorToCatch | 
 |  | 
 |     def handle__copyright(self,arg): | 
 |  | 
 |         self.print_header() | 
 |         copyright = self.copyright % self.__dict__ | 
 |         print(copyright.strip()) | 
 |         print() | 
 |         return 0 | 
 |  | 
 |     def handle__examples(self,arg): | 
 |  | 
 |         self.print_header() | 
 |         if self.examples: | 
 |             print('Examples:') | 
 |             print() | 
 |             examples = self.examples % self.__dict__ | 
 |             print(examples.strip()) | 
 |             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() |