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