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