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