1""" CommandLine - Get and parse command line options 2 3 NOTE: This still is very much work in progress !!! 4 5 Different version are likely to be incompatible. 6 7 TODO: 8 9 * Incorporate the changes made by (see Inbox) 10 * Add number range option using srange() 11 12""" 13 14__copyright__ = """\ 15Copyright (c), 1997-2006, Marc-Andre Lemburg (mal@lemburg.com) 16Copyright (c), 2000-2006, eGenix.com Software GmbH (info@egenix.com) 17See the documentation for further information on copyrights, 18or contact the author. All Rights Reserved. 19""" 20 21__version__ = '1.2' 22 23import sys, getopt, string, glob, os, re, exceptions, traceback 24 25### Helpers 26 27def _getopt_flags(options): 28 29 """ Convert the option list to a getopt flag string and long opt 30 list 31 32 """ 33 s = [] 34 l = [] 35 for o in options: 36 if o.prefix == '-': 37 # short option 38 s.append(o.name) 39 if o.takes_argument: 40 s.append(':') 41 else: 42 # long option 43 if o.takes_argument: 44 l.append(o.name+'=') 45 else: 46 l.append(o.name) 47 return string.join(s,''),l 48 49def invisible_input(prompt='>>> '): 50 51 """ Get raw input from a terminal without echoing the characters to 52 the terminal, e.g. for password queries. 53 54 """ 55 import getpass 56 entry = getpass.getpass(prompt) 57 if entry is None: 58 raise KeyboardInterrupt 59 return entry 60 61def fileopen(name, mode='wb', encoding=None): 62 63 """ Open a file using mode. 64 65 Default mode is 'wb' meaning to open the file for writing in 66 binary mode. If encoding is given, I/O to and from the file is 67 transparently encoded using the given encoding. 68 69 Files opened for writing are chmod()ed to 0600. 70 71 """ 72 if name == 'stdout': 73 return sys.stdout 74 elif name == 'stderr': 75 return sys.stderr 76 elif name == 'stdin': 77 return sys.stdin 78 else: 79 if encoding is not None: 80 import codecs 81 f = codecs.open(name, mode, encoding) 82 else: 83 f = open(name, mode) 84 if 'w' in mode: 85 os.chmod(name, 0600) 86 return f 87 88def option_dict(options): 89 90 """ Return a dictionary mapping option names to Option instances. 91 """ 92 d = {} 93 for option in options: 94 d[option.name] = option 95 return d 96 97# Alias 98getpasswd = invisible_input 99 100_integerRE = re.compile('\s*(-?\d+)\s*$') 101_integerRangeRE = re.compile('\s*(-?\d+)\s*-\s*(-?\d+)\s*$') 102 103def srange(s, 104 105 split=string.split,integer=_integerRE, 106 integerRange=_integerRangeRE): 107 108 """ Converts a textual representation of integer numbers and ranges 109 to a Python list. 110 111 Supported formats: 2,3,4,2-10,-1 - -3, 5 - -2 112 113 Values are appended to the created list in the order specified 114 in the string. 115 116 """ 117 l = [] 118 append = l.append 119 for entry in split(s,','): 120 m = integer.match(entry) 121 if m: 122 append(int(m.groups()[0])) 123 continue 124 m = integerRange.match(entry) 125 if m: 126 start,end = map(int,m.groups()) 127 l[len(l):] = range(start,end+1) 128 return l 129 130def abspath(path, 131 132 expandvars=os.path.expandvars,expanduser=os.path.expanduser, 133 join=os.path.join,getcwd=os.getcwd): 134 135 """ Return the corresponding absolute path for path. 136 137 path is expanded in the usual shell ways before 138 joining it with the current working directory. 139 140 """ 141 try: 142 path = expandvars(path) 143 except AttributeError: 144 pass 145 try: 146 path = expanduser(path) 147 except AttributeError: 148 pass 149 return join(getcwd(), path) 150 151### Option classes 152 153class Option: 154 155 """ Option base class. Takes no argument. 156 157 """ 158 default = None 159 helptext = '' 160 prefix = '-' 161 takes_argument = 0 162 has_default = 0 163 tab = 15 164 165 def __init__(self,name,help=None): 166 167 if not name[:1] == '-': 168 raise TypeError,'option names must start with "-"' 169 if name[1:2] == '-': 170 self.prefix = '--' 171 self.name = name[2:] 172 else: 173 self.name = name[1:] 174 if help: 175 self.help = help 176 177 def __str__(self): 178 179 o = self 180 name = o.prefix + o.name 181 if o.takes_argument: 182 name = name + ' arg' 183 if len(name) > self.tab: 184 name = name + '\n' + ' ' * (self.tab + 1 + len(o.prefix)) 185 else: 186 name = '%-*s ' % (self.tab, name) 187 description = o.help 188 if o.has_default: 189 description = description + ' (%s)' % o.default 190 return '%s %s' % (name, description) 191 192class ArgumentOption(Option): 193 194 """ Option that takes an argument. 195 196 An optional default argument can be given. 197 198 """ 199 def __init__(self,name,help=None,default=None): 200 201 # Basemethod 202 Option.__init__(self,name,help) 203 204 if default is not None: 205 self.default = default 206 self.has_default = 1 207 self.takes_argument = 1 208 209class SwitchOption(Option): 210 211 """ Options that can be on or off. Has an optional default value. 212 213 """ 214 def __init__(self,name,help=None,default=None): 215 216 # Basemethod 217 Option.__init__(self,name,help) 218 219 if default is not None: 220 self.default = default 221 self.has_default = 1 222 223### Application baseclass 224 225class Application: 226 227 """ Command line application interface with builtin argument 228 parsing. 229 230 """ 231 # Options the program accepts (Option instances) 232 options = [] 233 234 # Standard settings; these are appended to options in __init__ 235 preset_options = [SwitchOption('-v', 236 'generate verbose output'), 237 SwitchOption('-h', 238 'show this help text'), 239 SwitchOption('--help', 240 'show this help text'), 241 SwitchOption('--debug', 242 'enable debugging'), 243 SwitchOption('--copyright', 244 'show copyright'), 245 SwitchOption('--examples', 246 'show examples of usage')] 247 248 # The help layout looks like this: 249 # [header] - defaults to '' 250 # 251 # [synopsis] - formatted as '<self.name> %s' % self.synopsis 252 # 253 # options: 254 # [options] - formatted from self.options 255 # 256 # [version] - formatted as 'Version:\n %s' % self.version, if given 257 # 258 # [about] - defaults to '' 259 # 260 # Note: all fields that do not behave as template are formatted 261 # using the instances dictionary as substitution namespace, 262 # e.g. %(name)s will be replaced by the applications name. 263 # 264 265 # Header (default to program name) 266 header = '' 267 268 # Name (defaults to program name) 269 name = '' 270 271 # Synopsis (%(name)s is replaced by the program name) 272 synopsis = '%(name)s [option] files...' 273 274 # Version (optional) 275 version = '' 276 277 # General information printed after the possible options (optional) 278 about = '' 279 280 # Examples of usage to show when the --examples option is given (optional) 281 examples = '' 282 283 # Copyright to show 284 copyright = __copyright__ 285 286 # Apply file globbing ? 287 globbing = 1 288 289 # Generate debug output ? 290 debug = 0 291 292 # Generate verbose output ? 293 verbose = 0 294 295 # Internal errors to catch 296 InternalError = exceptions.Exception 297 298 # Instance variables: 299 values = None # Dictionary of passed options (or default values) 300 # indexed by the options name, e.g. '-h' 301 files = None # List of passed filenames 302 optionlist = None # List of passed options 303 304 def __init__(self,argv=None): 305 306 # Setup application specs 307 if argv is None: 308 argv = sys.argv 309 self.filename = os.path.split(argv[0])[1] 310 if not self.name: 311 self.name = os.path.split(self.filename)[1] 312 else: 313 self.name = self.name 314 if not self.header: 315 self.header = self.name 316 else: 317 self.header = self.header 318 319 # Init .arguments list 320 self.arguments = argv[1:] 321 322 # Setup Option mapping 323 self.option_map = option_dict(self.options) 324 325 # Append preset options 326 for option in self.preset_options: 327 if not self.option_map.has_key(option.name): 328 self.add_option(option) 329 330 # Init .files list 331 self.files = [] 332 333 # Start Application 334 try: 335 # Process startup 336 rc = self.startup() 337 if rc is not None: 338 raise SystemExit,rc 339 340 # Parse command line 341 rc = self.parse() 342 if rc is not None: 343 raise SystemExit,rc 344 345 # Start application 346 rc = self.main() 347 if rc is None: 348 rc = 0 349 350 except SystemExit,rc: 351 pass 352 353 except KeyboardInterrupt: 354 print 355 print '* User Break' 356 print 357 rc = 1 358 359 except self.InternalError: 360 print 361 print '* Internal Error (use --debug to display the traceback)' 362 if self.debug: 363 print 364 traceback.print_exc(20, sys.stdout) 365 elif self.verbose: 366 print ' %s: %s' % sys.exc_info()[:2] 367 print 368 rc = 1 369 370 raise SystemExit,rc 371 372 def add_option(self, option): 373 374 """ Add a new Option instance to the Application dynamically. 375 376 Note that this has to be done *before* .parse() is being 377 executed. 378 379 """ 380 self.options.append(option) 381 self.option_map[option.name] = option 382 383 def startup(self): 384 385 """ Set user defined instance variables. 386 387 If this method returns anything other than None, the 388 process is terminated with the return value as exit code. 389 390 """ 391 return None 392 393 def exit(self, rc=0): 394 395 """ Exit the program. 396 397 rc is used as exit code and passed back to the calling 398 program. It defaults to 0 which usually means: OK. 399 400 """ 401 raise SystemExit, rc 402 403 def parse(self): 404 405 """ Parse the command line and fill in self.values and self.files. 406 407 After having parsed the options, the remaining command line 408 arguments are interpreted as files and passed to .handle_files() 409 for processing. 410 411 As final step the option handlers are called in the order 412 of the options given on the command line. 413 414 """ 415 # Parse arguments 416 self.values = values = {} 417 for o in self.options: 418 if o.has_default: 419 values[o.prefix+o.name] = o.default 420 else: 421 values[o.prefix+o.name] = 0 422 flags,lflags = _getopt_flags(self.options) 423 try: 424 optlist,files = getopt.getopt(self.arguments,flags,lflags) 425 if self.globbing: 426 l = [] 427 for f in files: 428 gf = glob.glob(f) 429 if not gf: 430 l.append(f) 431 else: 432 l[len(l):] = gf 433 files = l 434 self.optionlist = optlist 435 self.files = files + self.files 436 except getopt.error,why: 437 self.help(why) 438 sys.exit(1) 439 440 # Call file handler 441 rc = self.handle_files(self.files) 442 if rc is not None: 443 sys.exit(rc) 444 445 # Call option handlers 446 for optionname, value in optlist: 447 448 # Try to convert value to integer 449 try: 450 value = string.atoi(value) 451 except ValueError: 452 pass 453 454 # Find handler and call it (or count the number of option 455 # instances on the command line) 456 handlername = 'handle' + string.replace(optionname, '-', '_') 457 try: 458 handler = getattr(self, handlername) 459 except AttributeError: 460 if value == '': 461 # count the number of occurances 462 if values.has_key(optionname): 463 values[optionname] = values[optionname] + 1 464 else: 465 values[optionname] = 1 466 else: 467 values[optionname] = value 468 else: 469 rc = handler(value) 470 if rc is not None: 471 raise SystemExit, rc 472 473 # Apply final file check (for backward compatibility) 474 rc = self.check_files(self.files) 475 if rc is not None: 476 sys.exit(rc) 477 478 def check_files(self,filelist): 479 480 """ Apply some user defined checks on the files given in filelist. 481 482 This may modify filelist in place. A typical application 483 is checking that at least n files are given. 484 485 If this method returns anything other than None, the 486 process is terminated with the return value as exit code. 487 488 """ 489 return None 490 491 def help(self,note=''): 492 493 self.print_header() 494 if self.synopsis: 495 print 'Synopsis:' 496 # To remain backward compatible: 497 try: 498 synopsis = self.synopsis % self.name 499 except (NameError, KeyError, TypeError): 500 synopsis = self.synopsis % self.__dict__ 501 print ' ' + synopsis 502 print 503 self.print_options() 504 if self.version: 505 print 'Version:' 506 print ' %s' % self.version 507 print 508 if self.about: 509 print string.strip(self.about % self.__dict__) 510 print 511 if note: 512 print '-'*72 513 print 'Note:',note 514 print 515 516 def notice(self,note): 517 518 print '-'*72 519 print 'Note:',note 520 print '-'*72 521 print 522 523 def print_header(self): 524 525 print '-'*72 526 print self.header % self.__dict__ 527 print '-'*72 528 print 529 530 def print_options(self): 531 532 options = self.options 533 print 'Options and default settings:' 534 if not options: 535 print ' None' 536 return 537 long = filter(lambda x: x.prefix == '--', options) 538 short = filter(lambda x: x.prefix == '-', options) 539 items = short + long 540 for o in options: 541 print ' ',o 542 print 543 544 # 545 # Example handlers: 546 # 547 # If a handler returns anything other than None, processing stops 548 # and the return value is passed to sys.exit() as argument. 549 # 550 551 # File handler 552 def handle_files(self,files): 553 554 """ This may process the files list in place. 555 """ 556 return None 557 558 # Short option handler 559 def handle_h(self,arg): 560 561 self.help() 562 return 0 563 564 def handle_v(self, value): 565 566 """ Turn on verbose output. 567 """ 568 self.verbose = 1 569 570 # Handlers for long options have two underscores in their name 571 def handle__help(self,arg): 572 573 self.help() 574 return 0 575 576 def handle__debug(self,arg): 577 578 self.debug = 1 579 # We don't want to catch internal errors: 580 self.InternalError = None 581 582 def handle__copyright(self,arg): 583 584 self.print_header() 585 print string.strip(self.copyright % self.__dict__) 586 print 587 return 0 588 589 def handle__examples(self,arg): 590 591 self.print_header() 592 if self.examples: 593 print 'Examples:' 594 print 595 print string.strip(self.examples % self.__dict__) 596 print 597 else: 598 print 'No examples available.' 599 print 600 return 0 601 602 def main(self): 603 604 """ Override this method as program entry point. 605 606 The return value is passed to sys.exit() as argument. If 607 it is None, 0 is assumed (meaning OK). Unhandled 608 exceptions are reported with exit status code 1 (see 609 __init__ for further details). 610 611 """ 612 return None 613 614# Alias 615CommandLine = Application 616 617def _test(): 618 619 class MyApplication(Application): 620 header = 'Test Application' 621 version = __version__ 622 options = [Option('-v','verbose')] 623 624 def handle_v(self,arg): 625 print 'VERBOSE, Yeah !' 626 627 cmd = MyApplication() 628 if not cmd.values['-h']: 629 cmd.help() 630 print 'files:',cmd.files 631 print 'Bye...' 632 633if __name__ == '__main__': 634 _test() 635