1# Copyright (c) 2002-2005 ActiveState Corp. 2# License: MIT (see LICENSE.txt for license details) 3# Author: Trent Mick (TrentM@ActiveState.com) 4# Home: http://trentm.com/projects/cmdln/ 5 6from __future__ import print_function 7 8"""An improvement on Python's standard cmd.py module. 9 10As with cmd.py, this module provides "a simple framework for writing 11line-oriented command intepreters." This module provides a 'RawCmdln' 12class that fixes some design flaws in cmd.Cmd, making it more scalable 13and nicer to use for good 'cvs'- or 'svn'-style command line interfaces 14or simple shells. And it provides a 'Cmdln' class that add 15optparse-based option processing. Basically you use it like this: 16 17 import cmdln 18 19 class MySVN(cmdln.Cmdln): 20 name = "svn" 21 22 @cmdln.alias('stat', 'st') 23 @cmdln.option('-v', '--verbose', action='store_true' 24 help='print verbose information') 25 def do_status(self, subcmd, opts, *paths): 26 print "handle 'svn status' command" 27 28 #... 29 30 if __name__ == "__main__": 31 shell = MySVN() 32 retval = shell.main() 33 sys.exit(retval) 34 35See the README.txt or <http://trentm.com/projects/cmdln/> for more 36details. 37""" 38 39__revision__ = "$Id: cmdln.py 1666 2007-05-09 03:13:03Z trentm $" 40__version_info__ = (1, 0, 0) 41__version__ = '.'.join(map(str, __version_info__)) 42 43import os 44import re 45import cmd 46import optparse 47import sys 48import time 49from pprint import pprint 50from datetime import datetime 51 52# this is python 2.x style 53def introspect_handler_2(handler): 54 # Extract the introspection bits we need. 55 func = handler.im_func 56 if func.func_defaults: 57 func_defaults = func.func_defaults 58 else: 59 func_defaults = [] 60 return \ 61 func_defaults, \ 62 func.func_code.co_argcount, \ 63 func.func_code.co_varnames, \ 64 func.func_code.co_flags, \ 65 func 66 67def introspect_handler_3(handler): 68 defaults = handler.__defaults__ 69 if not defaults: 70 defaults = [] 71 else: 72 defaults = list(handler.__defaults__) 73 return \ 74 defaults, \ 75 handler.__code__.co_argcount, \ 76 handler.__code__.co_varnames, \ 77 handler.__code__.co_flags, \ 78 handler.__func__ 79 80if sys.version_info[0] == 2: 81 introspect_handler = introspect_handler_2 82 bytes = lambda x, *args: x 83else: 84 introspect_handler = introspect_handler_3 85 86 87#---- globals 88 89LOOP_ALWAYS, LOOP_NEVER, LOOP_IF_EMPTY = range(3) 90 91# An unspecified optional argument when None is a meaningful value. 92_NOT_SPECIFIED = ("Not", "Specified") 93 94# Pattern to match a TypeError message from a call that 95# failed because of incorrect number of arguments (see 96# Python/getargs.c). 97_INCORRECT_NUM_ARGS_RE = re.compile( 98 r"(takes [\w ]+ )(\d+)( arguments? \()(\d+)( given\))") 99 100_INCORRECT_NUM_ARGS_RE_PY3 = re.compile( 101 r"(missing\s+\d+.*)") 102 103# Static bits of man page 104MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands" 105.SH NAME 106%(name)s \- Program to do useful things. 107.SH SYNOPSIS 108.B %(name)s 109[\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...] 110.br 111.B %(name)s 112\fIhelp SUBCOMMAND\fR 113.SH DESCRIPTION 114""" 115MAN_COMMANDS_HEADER = r""" 116.SS COMMANDS 117""" 118MAN_OPTIONS_HEADER = r""" 119.SS GLOBAL OPTIONS 120""" 121MAN_FOOTER = r""" 122.SH AUTHOR 123This man page is automatically generated. 124""" 125 126#---- exceptions 127 128class CmdlnError(Exception): 129 """A cmdln.py usage error.""" 130 def __init__(self, msg): 131 self.msg = msg 132 def __str__(self): 133 return self.msg 134 135class CmdlnUserError(Exception): 136 """An error by a user of a cmdln-based tool/shell.""" 137 pass 138 139 140 141#---- public methods and classes 142 143def alias(*aliases): 144 """Decorator to add aliases for Cmdln.do_* command handlers. 145 146 Example: 147 class MyShell(cmdln.Cmdln): 148 @cmdln.alias("!", "sh") 149 def do_shell(self, argv): 150 #...implement 'shell' command 151 """ 152 def decorate(f): 153 if not hasattr(f, "aliases"): 154 f.aliases = [] 155 f.aliases += aliases 156 return f 157 return decorate 158 159MAN_REPLACES = [ 160 (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3\-\4\-\5\-\6'), 161 (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3\-\4\-\5'), 162 (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3\-\4'), 163 (re.compile(r'(^|[ \t\[\'\|])-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\2\-\3\-\4'), 164 (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3'), 165 (re.compile(r'(^|[ \t\[\'\|])-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\2\-\3'), 166 (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2'), 167 (re.compile(r'(^|[ \t\[\'\|])-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\2'), 168 (re.compile(r"^'"), r" '"), 169 ] 170 171def man_escape(text): 172 ''' 173 Escapes text to be included in man page. 174 175 For now it only escapes dashes in command line options. 176 ''' 177 for repl in MAN_REPLACES: 178 text = repl[0].sub(repl[1], text) 179 return text 180 181class RawCmdln(cmd.Cmd): 182 """An improved (on cmd.Cmd) framework for building multi-subcommand 183 scripts (think "svn" & "cvs") and simple shells (think "pdb" and 184 "gdb"). 185 186 A simple example: 187 188 import cmdln 189 190 class MySVN(cmdln.RawCmdln): 191 name = "svn" 192 193 @cmdln.aliases('stat', 'st') 194 def do_status(self, argv): 195 print "handle 'svn status' command" 196 197 if __name__ == "__main__": 198 shell = MySVN() 199 retval = shell.main() 200 sys.exit(retval) 201 202 See <http://trentm.com/projects/cmdln> for more information. 203 """ 204 name = None # if unset, defaults basename(sys.argv[0]) 205 prompt = None # if unset, defaults to self.name+"> " 206 version = None # if set, default top-level options include --version 207 208 # Default messages for some 'help' command error cases. 209 # They are interpolated with one arg: the command. 210 nohelp = "no help on '%s'" 211 unknowncmd = "unknown command: '%s'" 212 213 helpindent = '' # string with which to indent help output 214 215 # Default man page parts, please change them in subclass 216 man_header = MAN_HEADER 217 man_commands_header = MAN_COMMANDS_HEADER 218 man_options_header = MAN_OPTIONS_HEADER 219 man_footer = MAN_FOOTER 220 221 def __init__(self, completekey='tab', 222 stdin=None, stdout=None, stderr=None): 223 """Cmdln(completekey='tab', stdin=None, stdout=None, stderr=None) 224 225 The optional argument 'completekey' is the readline name of a 226 completion key; it defaults to the Tab key. If completekey is 227 not None and the readline module is available, command completion 228 is done automatically. 229 230 The optional arguments 'stdin', 'stdout' and 'stderr' specify 231 alternate input, output and error output file objects; if not 232 specified, sys.* are used. 233 234 If 'stdout' but not 'stderr' is specified, stdout is used for 235 error output. This is to provide least surprise for users used 236 to only the 'stdin' and 'stdout' options with cmd.Cmd. 237 """ 238 if self.name is None: 239 self.name = os.path.basename(sys.argv[0]) 240 if self.prompt is None: 241 self.prompt = self.name+"> " 242 self._name_str = self._str(self.name) 243 self._prompt_str = self._str(self.prompt) 244 if stdin is not None: 245 self.stdin = stdin 246 else: 247 self.stdin = sys.stdin 248 if stdout is not None: 249 self.stdout = stdout 250 else: 251 self.stdout = sys.stdout 252 if stderr is not None: 253 self.stderr = stderr 254 elif stdout is not None: 255 self.stderr = stdout 256 else: 257 self.stderr = sys.stderr 258 self.cmdqueue = [] 259 self.completekey = completekey 260 self.cmdlooping = False 261 262 def get_optparser(self): 263 """Hook for subclasses to set the option parser for the 264 top-level command/shell. 265 266 This option parser is used retrieved and used by `.main()' to 267 handle top-level options. 268 269 The default implements a single '-h|--help' option. Sub-classes 270 can return None to have no options at the top-level. Typically 271 an instance of CmdlnOptionParser should be returned. 272 """ 273 version = (self.version is not None 274 and "%s %s" % (self._name_str, self.version) 275 or None) 276 return CmdlnOptionParser(self, version=version) 277 278 def get_version(self): 279 """ 280 Returns version of program. To be replaced in subclass. 281 """ 282 return __version__ 283 284 def postoptparse(self): 285 """Hook method executed just after `.main()' parses top-level 286 options. 287 288 When called `self.values' holds the results of the option parse. 289 """ 290 pass 291 292 def main(self, argv=None, loop=LOOP_NEVER): 293 """A possible mainline handler for a script, like so: 294 295 import cmdln 296 class MyCmd(cmdln.Cmdln): 297 name = "mycmd" 298 ... 299 300 if __name__ == "__main__": 301 MyCmd().main() 302 303 By default this will use sys.argv to issue a single command to 304 'MyCmd', then exit. The 'loop' argument can be use to control 305 interactive shell behaviour. 306 307 Arguments: 308 "argv" (optional, default sys.argv) is the command to run. 309 It must be a sequence, where the first element is the 310 command name and subsequent elements the args for that 311 command. 312 "loop" (optional, default LOOP_NEVER) is a constant 313 indicating if a command loop should be started (i.e. an 314 interactive shell). Valid values (constants on this module): 315 LOOP_ALWAYS start loop and run "argv", if any 316 LOOP_NEVER run "argv" (or .emptyline()) and exit 317 LOOP_IF_EMPTY run "argv", if given, and exit; 318 otherwise, start loop 319 """ 320 if argv is None: 321 argv = sys.argv 322 else: 323 argv = argv[:] # don't modify caller's list 324 325 self.optparser = self.get_optparser() 326 if self.optparser: # i.e. optparser=None means don't process for opts 327 try: 328 self.options, args = self.optparser.parse_args(argv[1:]) 329 except CmdlnUserError as ex: 330 msg = "%s: %s\nTry '%s help' for info.\n"\ 331 % (self.name, ex, self.name) 332 self.stderr.write(self._str(msg)) 333 self.stderr.flush() 334 return 1 335 except StopOptionProcessing as ex: 336 return 0 337 else: 338 self.options, args = None, argv[1:] 339 self.postoptparse() 340 341 if loop == LOOP_ALWAYS: 342 if args: 343 self.cmdqueue.append(args) 344 return self.cmdloop() 345 elif loop == LOOP_NEVER: 346 if args: 347 return self.cmd(args) 348 else: 349 return self.emptyline() 350 elif loop == LOOP_IF_EMPTY: 351 if args: 352 return self.cmd(args) 353 else: 354 return self.cmdloop() 355 356 def cmd(self, argv): 357 """Run one command and exit. 358 359 "argv" is the arglist for the command to run. argv[0] is the 360 command to run. If argv is an empty list then the 361 'emptyline' handler is run. 362 363 Returns the return value from the command handler. 364 """ 365 assert isinstance(argv, (list, tuple)), \ 366 "'argv' is not a sequence: %r" % argv 367 retval = None 368 try: 369 argv = self.precmd(argv) 370 retval = self.onecmd(argv) 371 self.postcmd(argv) 372 except: 373 if not self.cmdexc(argv): 374 raise 375 retval = 1 376 return retval 377 378 def _str(self, s): 379 """Safely convert the given str/unicode to a string for printing.""" 380 try: 381 return str(s) 382 except UnicodeError: 383 #XXX What is the proper encoding to use here? 'utf-8' seems 384 # to work better than "getdefaultencoding" (usually 385 # 'ascii'), on OS X at least. 386 #return s.encode(sys.getdefaultencoding(), "replace") 387 return s.encode("utf-8", "replace") 388 389 def cmdloop(self, intro=None): 390 """Repeatedly issue a prompt, accept input, parse into an argv, and 391 dispatch (via .precmd(), .onecmd() and .postcmd()), passing them 392 the argv. In other words, start a shell. 393 394 "intro" (optional) is a introductory message to print when 395 starting the command loop. This overrides the class 396 "intro" attribute, if any. 397 """ 398 self.cmdlooping = True 399 self.preloop() 400 if self.use_rawinput and self.completekey: 401 try: 402 import readline 403 self.old_completer = readline.get_completer() 404 readline.set_completer(self.complete) 405 readline.parse_and_bind(self.completekey+": complete") 406 except ImportError: 407 pass 408 try: 409 if intro is None: 410 intro = self.intro 411 if intro: 412 intro_str = self._str(intro) 413 self.stdout.write(intro_str+'\n') 414 self.stop = False 415 retval = None 416 while not self.stop: 417 if self.cmdqueue: 418 argv = self.cmdqueue.pop(0) 419 assert isinstance(argv, (list, tuple)), \ 420 "item on 'cmdqueue' is not a sequence: %r" % argv 421 else: 422 if self.use_rawinput: 423 try: 424 try: 425 #python 2.x 426 line = raw_input(self._prompt_str) 427 except NameError: 428 line = input(self._prompt_str) 429 except EOFError: 430 line = 'EOF' 431 else: 432 self.stdout.write(self._prompt_str) 433 self.stdout.flush() 434 line = self.stdin.readline() 435 if not len(line): 436 line = 'EOF' 437 else: 438 line = line[:-1] # chop '\n' 439 argv = line2argv(line) 440 try: 441 argv = self.precmd(argv) 442 retval = self.onecmd(argv) 443 self.postcmd(argv) 444 except: 445 if not self.cmdexc(argv): 446 raise 447 retval = 1 448 self.lastretval = retval 449 self.postloop() 450 finally: 451 if self.use_rawinput and self.completekey: 452 try: 453 import readline 454 readline.set_completer(self.old_completer) 455 except ImportError: 456 pass 457 self.cmdlooping = False 458 return retval 459 460 def precmd(self, argv): 461 """Hook method executed just before the command argv is 462 interpreted, but after the input prompt is generated and issued. 463 464 "argv" is the cmd to run. 465 466 Returns an argv to run (i.e. this method can modify the command 467 to run). 468 """ 469 return argv 470 471 def postcmd(self, argv): 472 """Hook method executed just after a command dispatch is finished. 473 474 "argv" is the command that was run. 475 """ 476 pass 477 478 def cmdexc(self, argv): 479 """Called if an exception is raised in any of precmd(), onecmd(), 480 or postcmd(). If True is returned, the exception is deemed to have 481 been dealt with. Otherwise, the exception is re-raised. 482 483 The default implementation handles CmdlnUserError's, which 484 typically correspond to user error in calling commands (as 485 opposed to programmer error in the design of the script using 486 cmdln.py). 487 """ 488 exc_type, exc, traceback = sys.exc_info() 489 if isinstance(exc, CmdlnUserError): 490 msg = "%s %s: %s\nTry '%s help %s' for info.\n"\ 491 % (self.name, argv[0], exc, self.name, argv[0]) 492 self.stderr.write(self._str(msg)) 493 self.stderr.flush() 494 return True 495 496 def onecmd(self, argv): 497 if not argv: 498 return self.emptyline() 499 self.lastcmd = argv 500 cmdname = self._get_canonical_cmd_name(argv[0]) 501 if cmdname: 502 handler = self._get_cmd_handler(cmdname) 503 if handler: 504 return self._dispatch_cmd(handler, argv) 505 return self.default(argv) 506 507 def _dispatch_cmd(self, handler, argv): 508 return handler(argv) 509 510 def default(self, argv): 511 """Hook called to handle a command for which there is no handler. 512 513 "argv" is the command and arguments to run. 514 515 The default implementation writes and error message to stderr 516 and returns an error exit status. 517 518 Returns a numeric command exit status. 519 """ 520 errmsg = self._str(self.unknowncmd % (argv[0],)) 521 if self.cmdlooping: 522 self.stderr.write(errmsg+"\n") 523 else: 524 self.stderr.write("%s: %s\nTry '%s help' for info.\n" 525 % (self._name_str, errmsg, self._name_str)) 526 self.stderr.flush() 527 return 1 528 529 def parseline(self, line): 530 # This is used by Cmd.complete (readline completer function) to 531 # massage the current line buffer before completion processing. 532 # We override to drop special '!' handling. 533 line = line.strip() 534 if not line: 535 return None, None, line 536 elif line[0] == '?': 537 line = 'help ' + line[1:] 538 i, n = 0, len(line) 539 while i < n and line[i] in self.identchars: 540 i = i+1 541 cmd, arg = line[:i], line[i:].strip() 542 return cmd, arg, line 543 544 def helpdefault(self, cmd, known): 545 """Hook called to handle help on a command for which there is no 546 help handler. 547 548 "cmd" is the command name on which help was requested. 549 "known" is a boolean indicating if this command is known 550 (i.e. if there is a handler for it). 551 552 Returns a return code. 553 """ 554 if known: 555 msg = self._str(self.nohelp % (cmd,)) 556 if self.cmdlooping: 557 self.stderr.write(msg + '\n') 558 else: 559 self.stderr.write("%s: %s\n" % (self.name, msg)) 560 else: 561 msg = self.unknowncmd % (cmd,) 562 if self.cmdlooping: 563 self.stderr.write(msg + '\n') 564 else: 565 self.stderr.write("%s: %s\n" 566 "Try '%s help' for info.\n" 567 % (self.name, msg, self.name)) 568 self.stderr.flush() 569 return 1 570 571 572 def do_help(self, argv): 573 """${cmd_name}: give detailed help on a specific sub-command 574 575 usage: 576 ${name} help [SUBCOMMAND] 577 """ 578 if len(argv) > 1: # asking for help on a particular command 579 doc = None 580 cmdname = self._get_canonical_cmd_name(argv[1]) or argv[1] 581 if not cmdname: 582 return self.helpdefault(argv[1], False) 583 else: 584 helpfunc = getattr(self, "help_"+cmdname, None) 585 if helpfunc: 586 doc = helpfunc() 587 else: 588 handler = self._get_cmd_handler(cmdname) 589 if handler: 590 doc = handler.__doc__ 591 if doc is None: 592 return self.helpdefault(argv[1], handler != None) 593 else: # bare "help" command 594 doc = self.__class__.__doc__ # try class docstring 595 if doc is None: 596 # Try to provide some reasonable useful default help. 597 if self.cmdlooping: 598 prefix = "" 599 else: 600 prefix = self.name+' ' 601 doc = """usage: 602 %sSUBCOMMAND [ARGS...] 603 %shelp [SUBCOMMAND] 604 605 ${option_list} 606 ${command_list} 607 ${help_list} 608 """ % (prefix, prefix) 609 cmdname = None 610 611 if doc: # *do* have help content, massage and print that 612 doc = self._help_reindent(doc) 613 doc = self._help_preprocess(doc, cmdname) 614 doc = doc.rstrip() + '\n' # trim down trailing space 615 self.stdout.write(self._str(doc)) 616 self.stdout.flush() 617 do_help.aliases = ["?"] 618 619 620 def do_man(self, argv): 621 """${cmd_name}: generates a man page 622 623 usage: 624 ${name} man 625 """ 626 mandate = datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))) 627 self.stdout.write( 628 self.man_header % { 629 'date': mandate.strftime('%b %Y'), 630 'version': self.get_version(), 631 'name': self.name, 632 'ucname': self.name.upper() 633 } 634 ) 635 636 self.stdout.write(self.man_commands_header) 637 commands = self._help_get_command_list() 638 for command, doc in commands: 639 cmdname = command.split(' ')[0] 640 text = self._help_preprocess(doc, cmdname) 641 lines = [] 642 for line in text.splitlines(False): 643 if line[:8] == ' ' * 8: 644 line = line[8:] 645 lines.append(man_escape(line)) 646 647 self.stdout.write( 648 '.TP\n\\fB%s\\fR\n%s\n' % (command, '\n'.join(lines))) 649 650 self.stdout.write(self.man_options_header) 651 self.stdout.write( 652 man_escape(self._help_preprocess('${option_list}', None))) 653 654 self.stdout.write(self.man_footer) 655 656 self.stdout.flush() 657 658 def _help_reindent(self, help, indent=None): 659 """Hook to re-indent help strings before writing to stdout. 660 661 "help" is the help content to re-indent 662 "indent" is a string with which to indent each line of the 663 help content after normalizing. If unspecified or None 664 then the default is use: the 'self.helpindent' class 665 attribute. By default this is the empty string, i.e. 666 no indentation. 667 668 By default, all common leading whitespace is removed and then 669 the lot is indented by 'self.helpindent'. When calculating the 670 common leading whitespace the first line is ignored -- hence 671 help content for Conan can be written as follows and have the 672 expected indentation: 673 674 def do_crush(self, ...): 675 '''${cmd_name}: crush your enemies, see them driven before you... 676 677 c.f. Conan the Barbarian''' 678 """ 679 if indent is None: 680 indent = self.helpindent 681 lines = help.splitlines(0) 682 _dedentlines(lines, skip_first_line=True) 683 lines = [(indent+line).rstrip() for line in lines] 684 return '\n'.join(lines) 685 686 def _help_preprocess(self, help, cmdname): 687 """Hook to preprocess a help string before writing to stdout. 688 689 "help" is the help string to process. 690 "cmdname" is the canonical sub-command name for which help 691 is being given, or None if the help is not specific to a 692 command. 693 694 By default the following template variables are interpolated in 695 help content. (Note: these are similar to Python 2.4's 696 string.Template interpolation but not quite.) 697 698 ${name} 699 The tool's/shell's name, i.e. 'self.name'. 700 ${option_list} 701 A formatted table of options for this shell/tool. 702 ${command_list} 703 A formatted table of available sub-commands. 704 ${help_list} 705 A formatted table of additional help topics (i.e. 'help_*' 706 methods with no matching 'do_*' method). 707 ${cmd_name} 708 The name (and aliases) for this sub-command formatted as: 709 "NAME (ALIAS1, ALIAS2, ...)". 710 ${cmd_usage} 711 A formatted usage block inferred from the command function 712 signature. 713 ${cmd_option_list} 714 A formatted table of options for this sub-command. (This is 715 only available for commands using the optparse integration, 716 i.e. using @cmdln.option decorators or manually setting the 717 'optparser' attribute on the 'do_*' method.) 718 719 Returns the processed help. 720 """ 721 preprocessors = { 722 "${name}": self._help_preprocess_name, 723 "${option_list}": self._help_preprocess_option_list, 724 "${command_list}": self._help_preprocess_command_list, 725 "${help_list}": self._help_preprocess_help_list, 726 "${cmd_name}": self._help_preprocess_cmd_name, 727 "${cmd_usage}": self._help_preprocess_cmd_usage, 728 "${cmd_option_list}": self._help_preprocess_cmd_option_list, 729 } 730 731 for marker, preprocessor in preprocessors.items(): 732 if marker in help: 733 help = preprocessor(help, cmdname) 734 return help 735 736 def _help_preprocess_name(self, help, cmdname=None): 737 return help.replace("${name}", self.name) 738 739 def _help_preprocess_option_list(self, help, cmdname=None): 740 marker = "${option_list}" 741 indent, indent_width = _get_indent(marker, help) 742 suffix = _get_trailing_whitespace(marker, help) 743 744 if self.optparser: 745 # Setup formatting options and format. 746 # - Indentation of 4 is better than optparse default of 2. 747 # C.f. Damian Conway's discussion of this in Perl Best 748 # Practices. 749 self.optparser.formatter.indent_increment = 4 750 self.optparser.formatter.current_indent = indent_width 751 block = self.optparser.format_option_help() + '\n' 752 else: 753 block = "" 754 755 help_msg = help.replace(indent+marker+suffix, block, 1) 756 return help_msg 757 758 def _help_get_command_list(self): 759 # Find any aliases for commands. 760 token2canonical = self._get_canonical_map() 761 aliases = {} 762 for token, cmdname in token2canonical.items(): 763 if token == cmdname: 764 continue 765 aliases.setdefault(cmdname, []).append(token) 766 767 # Get the list of (non-hidden) commands and their 768 # documentation, if any. 769 cmdnames = {} # use a dict to strip duplicates 770 for attr in self.get_names(): 771 if attr.startswith("do_"): 772 cmdnames[attr[3:]] = True 773 linedata = [] 774 for cmdname in sorted(cmdnames.keys()): 775 if aliases.get(cmdname): 776 a = sorted(aliases[cmdname]) 777 cmdstr = "%s (%s)" % (cmdname, ", ".join(a)) 778 else: 779 cmdstr = cmdname 780 doc = None 781 try: 782 helpfunc = getattr(self, 'help_'+cmdname) 783 except AttributeError: 784 handler = self._get_cmd_handler(cmdname) 785 if handler: 786 doc = handler.__doc__ 787 else: 788 doc = helpfunc() 789 790 # Strip "${cmd_name}: " from the start of a command's doc. Best 791 # practice dictates that command help strings begin with this, but 792 # it isn't at all wanted for the command list. 793 to_strip = "${cmd_name}:" 794 if doc and doc.startswith(to_strip): 795 #log.debug("stripping %r from start of %s's help string", 796 # to_strip, cmdname) 797 doc = doc[len(to_strip):].lstrip() 798 if not getattr(self._get_cmd_handler(cmdname), "hidden", None): 799 linedata.append( (cmdstr, doc) ) 800 801 return linedata 802 803 def _help_preprocess_command_list(self, help, cmdname=None): 804 marker = "${command_list}" 805 indent, indent_width = _get_indent(marker, help) 806 suffix = _get_trailing_whitespace(marker, help) 807 808 linedata = self._help_get_command_list() 809 810 if linedata: 811 subindent = indent + ' '*4 812 lines = _format_linedata(linedata, subindent, indent_width+4) 813 block = indent + "commands:\n" \ 814 + '\n'.join(lines) + "\n\n" 815 help = help.replace(indent+marker+suffix, block, 1) 816 return help 817 818 def _help_preprocess_help_list(self, help, cmdname=None): 819 marker = "${help_list}" 820 indent, indent_width = _get_indent(marker, help) 821 suffix = _get_trailing_whitespace(marker, help) 822 823 # Determine the additional help topics, if any. 824 helpnames = {} 825 token2cmdname = self._get_canonical_map() 826 for attr in self.get_names(): 827 if not attr.startswith("help_"): 828 continue 829 helpname = attr[5:] 830 if helpname not in token2cmdname: 831 helpnames[helpname] = True 832 833 if helpnames: 834 helpnames = sorted(helpnames.keys()) 835 linedata = [(self.name+" help "+n, "") for n in helpnames] 836 837 subindent = indent + ' '*4 838 lines = _format_linedata(linedata, subindent, indent_width+4) 839 block = indent + "additional help topics:\n" \ 840 + '\n'.join(lines) + "\n\n" 841 else: 842 block = '' 843 help_msg = help.replace(indent+marker+suffix, block, 1) 844 return help_msg 845 846 def _help_preprocess_cmd_name(self, help, cmdname=None): 847 marker = "${cmd_name}" 848 handler = self._get_cmd_handler(cmdname) 849 if not handler: 850 raise CmdlnError("cannot preprocess '%s' into help string: " 851 "could not find command handler for %r" 852 % (marker, cmdname)) 853 s = cmdname 854 if hasattr(handler, "aliases"): 855 s += " (%s)" % (", ".join(handler.aliases)) 856 help_msg = help.replace(marker, s) 857 return help_msg 858 859 #TODO: this only makes sense as part of the Cmdln class. 860 # Add hooks to add help preprocessing template vars and put 861 # this one on that class. 862 def _help_preprocess_cmd_usage(self, help, cmdname=None): 863 marker = "${cmd_usage}" 864 handler = self._get_cmd_handler(cmdname) 865 if not handler: 866 raise CmdlnError("cannot preprocess '%s' into help string: " 867 "could not find command handler for %r" 868 % (marker, cmdname)) 869 indent, indent_width = _get_indent(marker, help) 870 suffix = _get_trailing_whitespace(marker, help) 871 872 func_defaults, co_argcount, co_varnames, co_flags, _ = introspect_handler(handler) 873 CO_FLAGS_ARGS = 4 874 CO_FLAGS_KWARGS = 8 875 876 # Adjust argcount for possible *args and **kwargs arguments. 877 argcount = co_argcount 878 if co_flags & CO_FLAGS_ARGS: 879 argcount += 1 880 if co_flags & CO_FLAGS_KWARGS: 881 argcount += 1 882 883 # Determine the usage string. 884 usage = "%s %s" % (self.name, cmdname) 885 if argcount <= 2: # handler ::= do_FOO(self, argv) 886 usage += " [ARGS...]" 887 elif argcount >= 3: # handler ::= do_FOO(self, subcmd, opts, ...) 888 argnames = list(co_varnames[3:argcount]) 889 tail = "" 890 if co_flags & CO_FLAGS_KWARGS: 891 name = argnames.pop(-1) 892 import warnings 893 # There is no generally accepted mechanism for passing 894 # keyword arguments from the command line. Could 895 # *perhaps* consider: arg=value arg2=value2 ... 896 warnings.warn("argument '**%s' on '%s.%s' command " 897 "handler will never get values" 898 % (name, self.__class__.__name__, 899 getattr(func, "__name__", getattr(func, "func_name")))) 900 if co_flags & CO_FLAGS_ARGS: 901 name = argnames.pop(-1) 902 tail = "[%s...]" % name.upper() 903 while func_defaults: 904 func_defaults.pop(-1) 905 name = argnames.pop(-1) 906 tail = "[%s%s%s]" % (name.upper(), (tail and ' ' or ''), tail) 907 while argnames: 908 name = argnames.pop(-1) 909 tail = "%s %s" % (name.upper(), tail) 910 usage += ' ' + tail 911 912 block_lines = [ 913 self.helpindent + "Usage:", 914 self.helpindent + ' '*4 + usage 915 ] 916 block = '\n'.join(block_lines) + '\n\n' 917 918 help_msg = help.replace(indent+marker+suffix, block, 1) 919 return help_msg 920 921 #TODO: this only makes sense as part of the Cmdln class. 922 # Add hooks to add help preprocessing template vars and put 923 # this one on that class. 924 def _help_preprocess_cmd_option_list(self, help, cmdname=None): 925 marker = "${cmd_option_list}" 926 handler = self._get_cmd_handler(cmdname) 927 if not handler: 928 raise CmdlnError("cannot preprocess '%s' into help string: " 929 "could not find command handler for %r" 930 % (marker, cmdname)) 931 indent, indent_width = _get_indent(marker, help) 932 suffix = _get_trailing_whitespace(marker, help) 933 if hasattr(handler, "optparser"): 934 # Setup formatting options and format. 935 # - Indentation of 4 is better than optparse default of 2. 936 # C.f. Damian Conway's discussion of this in Perl Best 937 # Practices. 938 handler.optparser.formatter.indent_increment = 4 939 handler.optparser.formatter.current_indent = indent_width 940 block = handler.optparser.format_option_help() + '\n' 941 else: 942 block = "" 943 944 help_msg = help.replace(indent+marker+suffix, block, 1) 945 return help_msg 946 947 def _get_canonical_cmd_name(self, token): 948 c_map = self._get_canonical_map() 949 return c_map.get(token, None) 950 951 def _get_canonical_map(self): 952 """Return a mapping of available command names and aliases to 953 their canonical command name. 954 """ 955 cacheattr = "_token2canonical" 956 if not hasattr(self, cacheattr): 957 # Get the list of commands and their aliases, if any. 958 token2canonical = {} 959 cmd2funcname = {} # use a dict to strip duplicates 960 for attr in self.get_names(): 961 if attr.startswith("do_"): 962 cmdname = attr[3:] 963 elif attr.startswith("_do_"): 964 cmdname = attr[4:] 965 else: 966 continue 967 cmd2funcname[cmdname] = attr 968 token2canonical[cmdname] = cmdname 969 for cmdname, funcname in cmd2funcname.items(): # add aliases 970 func = getattr(self, funcname) 971 aliases = getattr(func, "aliases", []) 972 for alias in aliases: 973 if alias in cmd2funcname: 974 import warnings 975 warnings.warn("'%s' alias for '%s' command conflicts " 976 "with '%s' handler" 977 % (alias, cmdname, cmd2funcname[alias])) 978 continue 979 token2canonical[alias] = cmdname 980 setattr(self, cacheattr, token2canonical) 981 return getattr(self, cacheattr) 982 983 def _get_cmd_handler(self, cmdname): 984 handler = None 985 try: 986 handler = getattr(self, 'do_' + cmdname) 987 except AttributeError: 988 try: 989 # Private command handlers begin with "_do_". 990 handler = getattr(self, '_do_' + cmdname) 991 except AttributeError: 992 pass 993 return handler 994 995 def _do_EOF(self, argv): 996 # Default EOF handler 997 # Note: an actual EOF is redirected to this command. 998 #TODO: separate name for this. Currently it is available from 999 # command-line. Is that okay? 1000 self.stdout.write('\n') 1001 self.stdout.flush() 1002 self.stop = True 1003 1004 def emptyline(self): 1005 # Different from cmd.Cmd: don't repeat the last command for an 1006 # emptyline. 1007 if self.cmdlooping: 1008 pass 1009 else: 1010 return self.do_help(["help"]) 1011 1012 1013#---- optparse.py extension to fix (IMO) some deficiencies 1014# 1015# See the class _OptionParserEx docstring for details. 1016# 1017 1018class StopOptionProcessing(Exception): 1019 """Indicate that option *and argument* processing should stop 1020 cleanly. This is not an error condition. It is similar in spirit to 1021 StopIteration. This is raised by _OptionParserEx's default "help" 1022 and "version" option actions and can be raised by custom option 1023 callbacks too. 1024 1025 Hence the typical CmdlnOptionParser (a subclass of _OptionParserEx) 1026 usage is: 1027 1028 parser = CmdlnOptionParser(mycmd) 1029 parser.add_option("-f", "--force", dest="force") 1030 ... 1031 try: 1032 opts, args = parser.parse_args() 1033 except StopOptionProcessing: 1034 # normal termination, "--help" was probably given 1035 sys.exit(0) 1036 """ 1037 1038class _OptionParserEx(optparse.OptionParser): 1039 """An optparse.OptionParser that uses exceptions instead of sys.exit. 1040 1041 This class is an extension of optparse.OptionParser that differs 1042 as follows: 1043 - Correct (IMO) the default OptionParser error handling to never 1044 sys.exit(). Instead OptParseError exceptions are passed through. 1045 - Add the StopOptionProcessing exception (a la StopIteration) to 1046 indicate normal termination of option processing. 1047 See StopOptionProcessing's docstring for details. 1048 1049 I'd also like to see the following in the core optparse.py, perhaps 1050 as a RawOptionParser which would serve as a base class for the more 1051 generally used OptionParser (that works as current): 1052 - Remove the implicit addition of the -h|--help and --version 1053 options. They can get in the way (e.g. if want '-?' and '-V' for 1054 these as well) and it is not hard to do: 1055 optparser.add_option("-h", "--help", action="help") 1056 optparser.add_option("--version", action="version") 1057 These are good practices, just not valid defaults if they can 1058 get in the way. 1059 """ 1060 def error(self, msg): 1061 raise optparse.OptParseError(msg) 1062 1063 def exit(self, status=0, msg=None): 1064 if status == 0: 1065 raise StopOptionProcessing(msg) 1066 else: 1067 #TODO: don't lose status info here 1068 raise optparse.OptParseError(msg) 1069 1070 1071 1072#---- optparse.py-based option processing support 1073 1074class CmdlnOptionParser(_OptionParserEx): 1075 """An optparse.OptionParser class more appropriate for top-level 1076 Cmdln options. For parsing of sub-command options, see 1077 SubCmdOptionParser. 1078 1079 Changes: 1080 - disable_interspersed_args() by default, because a Cmdln instance 1081 has sub-commands which may themselves have options. 1082 - Redirect print_help() to the Cmdln.do_help() which is better 1083 equiped to handle the "help" action. 1084 - error() will raise a CmdlnUserError: OptionParse.error() is meant 1085 to be called for user errors. Raising a well-known error here can 1086 make error handling clearer. 1087 - Also see the changes in _OptionParserEx. 1088 """ 1089 def __init__(self, cmdln, **kwargs): 1090 self.cmdln = cmdln 1091 kwargs["prog"] = self.cmdln.name 1092 _OptionParserEx.__init__(self, **kwargs) 1093 self.disable_interspersed_args() 1094 1095 def print_help(self, file=None): 1096 self.cmdln.onecmd(["help"]) 1097 1098 def error(self, msg): 1099 raise CmdlnUserError(msg) 1100 1101 1102class SubCmdOptionParser(_OptionParserEx): 1103 def set_cmdln_info(self, cmdln, subcmd): 1104 """Called by Cmdln to pass relevant info about itself needed 1105 for print_help(). 1106 """ 1107 self.cmdln = cmdln 1108 self.subcmd = subcmd 1109 1110 def print_help(self, file=None): 1111 self.cmdln.onecmd(["help", self.subcmd]) 1112 1113 def error(self, msg): 1114 raise CmdlnUserError(msg) 1115 1116 1117def option(*args, **kwargs): 1118 """Decorator to add an option to the optparser argument of a Cmdln 1119 subcommand. 1120 1121 Example: 1122 class MyShell(cmdln.Cmdln): 1123 @cmdln.option("-f", "--force", help="force removal") 1124 def do_remove(self, subcmd, opts, *args): 1125 #... 1126 """ 1127 #XXX Is there a possible optimization for many options to not have a 1128 # large stack depth here? 1129 def decorate(f): 1130 if not hasattr(f, "optparser"): 1131 f.optparser = SubCmdOptionParser() 1132 f.optparser.add_option(*args, **kwargs) 1133 return f 1134 return decorate 1135 1136def hide(*args): 1137 """For obsolete calls, hide them in help listings. 1138 1139 Example: 1140 class MyShell(cmdln.Cmdln): 1141 @cmdln.hide() 1142 def do_shell(self, argv): 1143 #...implement 'shell' command 1144 """ 1145 def decorate(f): 1146 f.hidden = 1 1147 return f 1148 return decorate 1149 1150 1151class Cmdln(RawCmdln): 1152 """An improved (on cmd.Cmd) framework for building multi-subcommand 1153 scripts (think "svn" & "cvs") and simple shells (think "pdb" and 1154 "gdb"). 1155 1156 A simple example: 1157 1158 import cmdln 1159 1160 class MySVN(cmdln.Cmdln): 1161 name = "svn" 1162 1163 @cmdln.aliases('stat', 'st') 1164 @cmdln.option('-v', '--verbose', action='store_true' 1165 help='print verbose information') 1166 def do_status(self, subcmd, opts, *paths): 1167 print "handle 'svn status' command" 1168 1169 #... 1170 1171 if __name__ == "__main__": 1172 shell = MySVN() 1173 retval = shell.main() 1174 sys.exit(retval) 1175 1176 'Cmdln' extends 'RawCmdln' by providing optparse option processing 1177 integration. See this class' _dispatch_cmd() docstring and 1178 <http://trentm.com/projects/cmdln> for more information. 1179 """ 1180 def _dispatch_cmd(self, handler, argv): 1181 """Introspect sub-command handler signature to determine how to 1182 dispatch the command. The raw handler provided by the base 1183 'RawCmdln' class is still supported: 1184 1185 def do_foo(self, argv): 1186 # 'argv' is the vector of command line args, argv[0] is 1187 # the command name itself (i.e. "foo" or an alias) 1188 pass 1189 1190 In addition, if the handler has more than 2 arguments option 1191 processing is automatically done (using optparse): 1192 1193 @cmdln.option('-v', '--verbose', action='store_true') 1194 def do_bar(self, subcmd, opts, *args): 1195 # subcmd = <"bar" or an alias> 1196 # opts = <an optparse.Values instance> 1197 if opts.verbose: 1198 print "lots of debugging output..." 1199 # args = <tuple of arguments> 1200 for arg in args: 1201 bar(arg) 1202 1203 TODO: explain that "*args" can be other signatures as well. 1204 1205 The `cmdln.option` decorator corresponds to an `add_option()` 1206 method call on an `optparse.OptionParser` instance. 1207 1208 You can declare a specific number of arguments: 1209 1210 @cmdln.option('-v', '--verbose', action='store_true') 1211 def do_bar2(self, subcmd, opts, bar_one, bar_two): 1212 #... 1213 1214 and an appropriate error message will be raised/printed if the 1215 command is called with a different number of args. 1216 """ 1217 co_argcount = introspect_handler(handler)[1] 1218 if co_argcount == 2: # handler ::= do_foo(self, argv) 1219 return handler(argv) 1220 elif co_argcount >= 3: # handler ::= do_foo(self, subcmd, opts, ...) 1221 try: 1222 optparser = handler.optparser 1223 except AttributeError: 1224 optparser = introspect_handler(handler)[4].optparser = SubCmdOptionParser() 1225 assert isinstance(optparser, SubCmdOptionParser) 1226 optparser.set_cmdln_info(self, argv[0]) 1227 try: 1228 opts, args = optparser.parse_args(argv[1:]) 1229 except StopOptionProcessing: 1230 #TODO: this doesn't really fly for a replacement of 1231 # optparse.py behaviour, does it? 1232 return 0 # Normal command termination 1233 1234 try: 1235 return handler(argv[0], opts, *args) 1236 except TypeError as ex: 1237 # Some TypeError's are user errors: 1238 # do_foo() takes at least 4 arguments (3 given) 1239 # do_foo() takes at most 5 arguments (6 given) 1240 # do_foo() takes exactly 5 arguments (6 given) 1241 # Raise CmdlnUserError for these with a suitably 1242 # massaged error message. 1243 tb = sys.exc_info()[2] # the traceback object 1244 if tb.tb_next is not None: 1245 # If the traceback is more than one level deep, then the 1246 # TypeError do *not* happen on the "handler(...)" call 1247 # above. In that we don't want to handle it specially 1248 # here: it would falsely mask deeper code errors. 1249 raise 1250 msg = ex.args[0] 1251 match = _INCORRECT_NUM_ARGS_RE.search(msg) 1252 match_py3 = _INCORRECT_NUM_ARGS_RE_PY3.search(msg) 1253 if match: 1254 msg = list(match.groups()) 1255 msg[1] = int(msg[1]) - 3 1256 if msg[1] == 1: 1257 msg[2] = msg[2].replace("arguments", "argument") 1258 msg[3] = int(msg[3]) - 3 1259 msg = ''.join(map(str, msg)) 1260 raise CmdlnUserError(msg) 1261 elif match_py3: 1262 raise CmdlnUserError(match_py3.group(1)) 1263 else: 1264 raise 1265 else: 1266 raise CmdlnError("incorrect argcount for %s(): takes %d, must " 1267 "take 2 for 'argv' signature or 3+ for 'opts' " 1268 "signature" % (handler.__name__, co_argcount)) 1269 1270 1271 1272#---- internal support functions 1273 1274def _format_linedata(linedata, indent, indent_width): 1275 """Format specific linedata into a pleasant layout. 1276 1277 "linedata" is a list of 2-tuples of the form: 1278 (<item-display-string>, <item-docstring>) 1279 "indent" is a string to use for one level of indentation 1280 "indent_width" is a number of columns by which the 1281 formatted data will be indented when printed. 1282 1283 The <item-display-string> column is held to 15 columns. 1284 """ 1285 lines = [] 1286 WIDTH = 78 - indent_width 1287 SPACING = 3 1288 MAX_NAME_WIDTH = 15 1289 1290 NAME_WIDTH = min(max([len(s) for s, d in linedata]), MAX_NAME_WIDTH) 1291 DOC_WIDTH = WIDTH - NAME_WIDTH - SPACING 1292 for namestr, doc in linedata: 1293 line = indent + namestr 1294 if len(namestr) <= NAME_WIDTH: 1295 line += ' ' * (NAME_WIDTH + SPACING - len(namestr)) 1296 else: 1297 lines.append(line) 1298 line = indent + ' ' * (NAME_WIDTH + SPACING) 1299 line += _summarize_doc(doc, DOC_WIDTH) 1300 lines.append(line.rstrip()) 1301 return lines 1302 1303def _summarize_doc(doc, length=60): 1304 r"""Parse out a short one line summary from the given doclines. 1305 1306 "doc" is the doc string to summarize. 1307 "length" is the max length for the summary 1308 1309 >>> _summarize_doc("this function does this") 1310 'this function does this' 1311 >>> _summarize_doc("this function does this", 10) 1312 'this fu...' 1313 >>> _summarize_doc("this function does this\nand that") 1314 'this function does this and that' 1315 >>> _summarize_doc("this function does this\n\nand that") 1316 'this function does this' 1317 """ 1318 if doc is None: 1319 return "" 1320 assert length > 3, "length <= 3 is absurdly short for a doc summary" 1321 doclines = doc.strip().splitlines(0) 1322 if not doclines: 1323 return "" 1324 1325 summlines = [] 1326 for i, line in enumerate(doclines): 1327 stripped = line.strip() 1328 if not stripped: 1329 break 1330 summlines.append(stripped) 1331 if len(''.join(summlines)) >= length: 1332 break 1333 1334 summary = ' '.join(summlines) 1335 if len(summary) > length: 1336 summary = summary[:length-3] + "..." 1337 return summary 1338 1339 1340def line2argv(line): 1341 r"""Parse the given line into an argument vector. 1342 1343 "line" is the line of input to parse. 1344 1345 This may get niggly when dealing with quoting and escaping. The 1346 current state of this parsing may not be completely thorough/correct 1347 in this respect. 1348 1349 >>> from cmdln import line2argv 1350 >>> line2argv("foo") 1351 ['foo'] 1352 >>> line2argv("foo bar") 1353 ['foo', 'bar'] 1354 >>> line2argv("foo bar ") 1355 ['foo', 'bar'] 1356 >>> line2argv(" foo bar") 1357 ['foo', 'bar'] 1358 1359 Quote handling: 1360 1361 >>> line2argv("'foo bar'") 1362 ['foo bar'] 1363 >>> line2argv('"foo bar"') 1364 ['foo bar'] 1365 >>> line2argv(r'"foo\"bar"') 1366 ['foo"bar'] 1367 >>> line2argv("'foo bar' spam") 1368 ['foo bar', 'spam'] 1369 >>> line2argv("'foo 'bar spam") 1370 ['foo bar', 'spam'] 1371 >>> line2argv("'foo") 1372 Traceback (most recent call last): 1373 ... 1374 ValueError: command line is not terminated: unfinished single-quoted segment 1375 >>> line2argv('"foo') 1376 Traceback (most recent call last): 1377 ... 1378 ValueError: command line is not terminated: unfinished double-quoted segment 1379 >>> line2argv('some\tsimple\ttests') 1380 ['some', 'simple', 'tests'] 1381 >>> line2argv('a "more complex" test') 1382 ['a', 'more complex', 'test'] 1383 >>> line2argv('a more="complex test of " quotes') 1384 ['a', 'more=complex test of ', 'quotes'] 1385 >>> line2argv('a more" complex test of " quotes') 1386 ['a', 'more complex test of ', 'quotes'] 1387 >>> line2argv('an "embedded \\"quote\\""') 1388 ['an', 'embedded "quote"'] 1389 """ 1390 import string 1391 line = line.strip() 1392 argv = [] 1393 state = "default" 1394 arg = None # the current argument being parsed 1395 i = -1 1396 while True: 1397 i += 1 1398 if i >= len(line): 1399 break 1400 ch = line[i] 1401 1402 if ch == "\\": # escaped char always added to arg, regardless of state 1403 if arg is None: 1404 arg = "" 1405 i += 1 1406 arg += line[i] 1407 continue 1408 1409 if state == "single-quoted": 1410 if ch == "'": 1411 state = "default" 1412 else: 1413 arg += ch 1414 elif state == "double-quoted": 1415 if ch == '"': 1416 state = "default" 1417 else: 1418 arg += ch 1419 elif state == "default": 1420 if ch == '"': 1421 if arg is None: 1422 arg = "" 1423 state = "double-quoted" 1424 elif ch == "'": 1425 if arg is None: 1426 arg = "" 1427 state = "single-quoted" 1428 elif ch in string.whitespace: 1429 if arg is not None: 1430 argv.append(arg) 1431 arg = None 1432 else: 1433 if arg is None: 1434 arg = "" 1435 arg += ch 1436 if arg is not None: 1437 argv.append(arg) 1438 if state != "default": 1439 raise ValueError("command line is not terminated: unfinished %s " 1440 "segment" % state) 1441 return argv 1442 1443 1444def argv2line(argv): 1445 r"""Put together the given argument vector into a command line. 1446 1447 "argv" is the argument vector to process. 1448 1449 >>> from cmdln import argv2line 1450 >>> argv2line(['foo']) 1451 'foo' 1452 >>> argv2line(['foo', 'bar']) 1453 'foo bar' 1454 >>> argv2line(['foo', 'bar baz']) 1455 'foo "bar baz"' 1456 >>> argv2line(['foo"bar']) 1457 'foo"bar' 1458 >>> print argv2line(['foo" bar']) 1459 'foo" bar' 1460 >>> print argv2line(["foo' bar"]) 1461 "foo' bar" 1462 >>> argv2line(["foo'bar"]) 1463 "foo'bar" 1464 """ 1465 escapedArgs = [] 1466 for arg in argv: 1467 if ' ' in arg and '"' not in arg: 1468 arg = '"'+arg+'"' 1469 elif ' ' in arg and "'" not in arg: 1470 arg = "'"+arg+"'" 1471 elif ' ' in arg: 1472 arg = arg.replace('"', r'\"') 1473 arg = '"'+arg+'"' 1474 escapedArgs.append(arg) 1475 return ' '.join(escapedArgs) 1476 1477 1478# Recipe: dedent (0.1) in /Users/trentm/tm/recipes/cookbook 1479def _dedentlines(lines, tabsize=8, skip_first_line=False): 1480 """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines 1481 1482 "lines" is a list of lines to dedent. 1483 "tabsize" is the tab width to use for indent width calculations. 1484 "skip_first_line" is a boolean indicating if the first line should 1485 be skipped for calculating the indent width and for dedenting. 1486 This is sometimes useful for docstrings and similar. 1487 1488 Same as dedent() except operates on a sequence of lines. Note: the 1489 lines list is modified **in-place**. 1490 """ 1491 DEBUG = False 1492 if DEBUG: 1493 print("dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\ 1494 % (tabsize, skip_first_line)) 1495 indents = [] 1496 margin = None 1497 for i, line in enumerate(lines): 1498 if i == 0 and skip_first_line: 1499 continue 1500 indent = 0 1501 for ch in line: 1502 if ch == ' ': 1503 indent += 1 1504 elif ch == '\t': 1505 indent += tabsize - (indent % tabsize) 1506 elif ch in '\r\n': 1507 continue # skip all-whitespace lines 1508 else: 1509 break 1510 else: 1511 continue # skip all-whitespace lines 1512 if DEBUG: 1513 print("dedent: indent=%d: %r" % (indent, line)) 1514 if margin is None: 1515 margin = indent 1516 else: 1517 margin = min(margin, indent) 1518 if DEBUG: 1519 print("dedent: margin=%r" % margin) 1520 1521 if margin is not None and margin > 0: 1522 for i, line in enumerate(lines): 1523 if i == 0 and skip_first_line: 1524 continue 1525 removed = 0 1526 for j, ch in enumerate(line): 1527 if ch == ' ': 1528 removed += 1 1529 elif ch == '\t': 1530 removed += tabsize - (removed % tabsize) 1531 elif ch in '\r\n': 1532 if DEBUG: 1533 print("dedent: %r: EOL -> strip up to EOL" % line) 1534 lines[i] = lines[i][j:] 1535 break 1536 else: 1537 raise ValueError("unexpected non-whitespace char %r in " 1538 "line %r while removing %d-space margin" 1539 % (ch, line, margin)) 1540 if DEBUG: 1541 print("dedent: %r: %r -> removed %d/%d"\ 1542 % (line, ch, removed, margin)) 1543 if removed == margin: 1544 lines[i] = lines[i][j+1:] 1545 break 1546 elif removed > margin: 1547 lines[i] = ' '*(removed-margin) + lines[i][j+1:] 1548 break 1549 return lines 1550 1551def _dedent(text, tabsize=8, skip_first_line=False): 1552 """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text 1553 1554 "text" is the text to dedent. 1555 "tabsize" is the tab width to use for indent width calculations. 1556 "skip_first_line" is a boolean indicating if the first line should 1557 be skipped for calculating the indent width and for dedenting. 1558 This is sometimes useful for docstrings and similar. 1559 1560 textwrap.dedent(s), but don't expand tabs to spaces 1561 """ 1562 lines = text.splitlines(1) 1563 _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line) 1564 return ''.join(lines) 1565 1566 1567def _get_indent(marker, s, tab_width=8): 1568 """_get_indent(marker, s, tab_width=8) -> 1569 (<indentation-of-'marker'>, <indentation-width>)""" 1570 # Figure out how much the marker is indented. 1571 INDENT_CHARS = tuple(' \t') 1572 start = s.index(marker) 1573 i = start 1574 while i > 0: 1575 if s[i-1] not in INDENT_CHARS: 1576 break 1577 i -= 1 1578 indent = s[i:start] 1579 indent_width = 0 1580 for ch in indent: 1581 if ch == ' ': 1582 indent_width += 1 1583 elif ch == '\t': 1584 indent_width += tab_width - (indent_width % tab_width) 1585 return indent, indent_width 1586 1587def _get_trailing_whitespace(marker, s): 1588 """Return the whitespace content trailing the given 'marker' in string 's', 1589 up to and including a newline. 1590 """ 1591 suffix = '' 1592 start = s.index(marker) + len(marker) 1593 i = start 1594 while i < len(s): 1595 if s[i] in ' \t': 1596 suffix += s[i] 1597 elif s[i] in '\r\n': 1598 suffix += s[i] 1599 if s[i] == '\r' and i+1 < len(s) and s[i+1] == '\n': 1600 suffix += s[i+1] 1601 break 1602 else: 1603 break 1604 i += 1 1605 return suffix 1606 1607 1608# vim: sw=4 et 1609