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/
6 """An improvement on Python's standard cmd.py module.
8 As with cmd.py, this module provides "a simple framework for writing
9 line-oriented command intepreters." This module provides a 'RawCmdln'
10 class that fixes some design flaws in cmd.Cmd, making it more scalable
11 and nicer to use for good 'cvs'- or 'svn'-style command line interfaces
12 or simple shells. And it provides a 'Cmdln' class that add
13 optparse-based option processing. Basically you use it like this:
17 class MySVN(cmdln.Cmdln):
20 @cmdln.alias('stat', 'st')
21 @cmdln.option('-v', '--verbose', action='store_true'
22 help='print verbose information')
23 def do_status(self, subcmd, opts, *paths):
24 print "handle 'svn status' command"
28 if __name__ == "__main__":
33 See the README.txt or <http://trentm.com/projects/cmdln/> for more
37 __revision__ = "$Id: cmdln.py 1666 2007-05-09 03:13:03Z trentm $"
38 __version_info__ = (1, 0, 0)
39 __version__ = '.'.join(map(str, __version_info__))
45 from pprint import pprint
46 from datetime import date
53 LOOP_ALWAYS, LOOP_NEVER, LOOP_IF_EMPTY = range(3)
55 # An unspecified optional argument when None is a meaningful value.
56 _NOT_SPECIFIED = ("Not", "Specified")
58 # Pattern to match a TypeError message from a call that
59 # failed because of incorrect number of arguments (see
61 _INCORRECT_NUM_ARGS_RE = re.compile(
62 r"(takes [\w ]+ )(\d+)( arguments? \()(\d+)( given\))")
64 # Static bits of man page
65 MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands"
67 %(name)s \- Program to do useful things.
70 [\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...]
76 MAN_COMMANDS_HEADER = r"""
79 MAN_OPTIONS_HEADER = r"""
84 This man page is automatically generated.
89 class CmdlnError(Exception):
90 """A cmdln.py usage error."""
91 def __init__(self, msg):
96 class CmdlnUserError(Exception):
97 """An error by a user of a cmdln-based tool/shell."""
102 #---- public methods and classes
105 """Decorator to add aliases for Cmdln.do_* command handlers.
108 class MyShell(cmdln.Cmdln):
109 @cmdln.alias("!", "sh")
110 def do_shell(self, argv):
111 #...implement 'shell' command
114 if not hasattr(f, "aliases"):
121 (re.compile(r'(^|[ \t\[\'])--([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,-]*)(?=$|[ \t=\]\'/,])'), r'\1\-\-\2\-\3\-\4'),
122 (re.compile(r'(^|[ \t\[\'])-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,-]*)(?=$|[ \t=\]\'/,])'), r'\1\-\2\-\3\-\4'),
123 (re.compile(r'(^|[ \t\[\'])--([^/ \t/,-]*)-([^/ \t/,-]*)(?=$|[ \t=\]\'/,])'), r'\1\-\-\2\-\3'),
124 (re.compile(r'(^|[ \t\[\'])-([^/ \t/,-]*)-([^/ \t/,-]*)(?=$|[ \t=\]\'/,])'), r'\1\-\2\-\3'),
125 (re.compile(r'(^|[ \t\[\'])--([^/ \t/,-]*)(?=$|[ \t=\]\'/,])'), r'\1\-\-\2'),
126 (re.compile(r'(^|[ \t\[\'])-([^/ \t/,-]*)(?=$|[ \t=\]\'/,])'), r'\1\-\2'),
127 (re.compile(r"^'"), r" '"),
130 def man_escape(text):
132 Escapes text to be included in man page.
134 For now it only escapes dashes in command line options.
136 for repl in MAN_REPLACES:
137 text = repl[0].sub(repl[1], text)
140 class RawCmdln(cmd.Cmd):
141 """An improved (on cmd.Cmd) framework for building multi-subcommand
142 scripts (think "svn" & "cvs") and simple shells (think "pdb" and
149 class MySVN(cmdln.RawCmdln):
152 @cmdln.aliases('stat', 'st')
153 def do_status(self, argv):
154 print "handle 'svn status' command"
156 if __name__ == "__main__":
158 retval = shell.main()
161 See <http://trentm.com/projects/cmdln> for more information.
163 name = None # if unset, defaults basename(sys.argv[0])
164 prompt = None # if unset, defaults to self.name+"> "
165 version = None # if set, default top-level options include --version
167 # Default messages for some 'help' command error cases.
168 # They are interpolated with one arg: the command.
169 nohelp = "no help on '%s'"
170 unknowncmd = "unknown command: '%s'"
172 helpindent = '' # string with which to indent help output
174 # Default man page parts, please change them in subclass
175 man_header = MAN_HEADER
176 man_commands_header = MAN_COMMANDS_HEADER
177 man_options_header = MAN_OPTIONS_HEADER
178 man_footer = MAN_FOOTER
180 def __init__(self, completekey='tab',
181 stdin=None, stdout=None, stderr=None):
182 """Cmdln(completekey='tab', stdin=None, stdout=None, stderr=None)
184 The optional argument 'completekey' is the readline name of a
185 completion key; it defaults to the Tab key. If completekey is
186 not None and the readline module is available, command completion
187 is done automatically.
189 The optional arguments 'stdin', 'stdout' and 'stderr' specify
190 alternate input, output and error output file objects; if not
191 specified, sys.* are used.
193 If 'stdout' but not 'stderr' is specified, stdout is used for
194 error output. This is to provide least surprise for users used
195 to only the 'stdin' and 'stdout' options with cmd.Cmd.
198 if self.name is None:
199 self.name = os.path.basename(sys.argv[0])
200 if self.prompt is None:
201 self.prompt = self.name+"> "
202 self._name_str = self._str(self.name)
203 self._prompt_str = self._str(self.prompt)
204 if stdin is not None:
207 self.stdin = sys.stdin
208 if stdout is not None:
211 self.stdout = sys.stdout
212 if stderr is not None:
214 elif stdout is not None:
217 self.stderr = sys.stderr
219 self.completekey = completekey
220 self.cmdlooping = False
222 def get_optparser(self):
223 """Hook for subclasses to set the option parser for the
224 top-level command/shell.
226 This option parser is used retrieved and used by `.main()' to
227 handle top-level options.
229 The default implements a single '-h|--help' option. Sub-classes
230 can return None to have no options at the top-level. Typically
231 an instance of CmdlnOptionParser should be returned.
233 version = (self.version is not None
234 and "%s %s" % (self._name_str, self.version)
236 return CmdlnOptionParser(self, version=version)
238 def get_version(self):
240 Returns version of program. To be replaced in subclass.
244 def postoptparse(self):
245 """Hook method executed just after `.main()' parses top-level
248 When called `self.values' holds the results of the option parse.
252 def main(self, argv=None, loop=LOOP_NEVER):
253 """A possible mainline handler for a script, like so:
256 class MyCmd(cmdln.Cmdln):
260 if __name__ == "__main__":
263 By default this will use sys.argv to issue a single command to
264 'MyCmd', then exit. The 'loop' argument can be use to control
265 interactive shell behaviour.
268 "argv" (optional, default sys.argv) is the command to run.
269 It must be a sequence, where the first element is the
270 command name and subsequent elements the args for that
272 "loop" (optional, default LOOP_NEVER) is a constant
273 indicating if a command loop should be started (i.e. an
274 interactive shell). Valid values (constants on this module):
275 LOOP_ALWAYS start loop and run "argv", if any
276 LOOP_NEVER run "argv" (or .emptyline()) and exit
277 LOOP_IF_EMPTY run "argv", if given, and exit;
278 otherwise, start loop
284 argv = argv[:] # don't modify caller's list
286 self.optparser = self.get_optparser()
287 if self.optparser: # i.e. optparser=None means don't process for opts
289 self.options, args = self.optparser.parse_args(argv[1:])
290 except CmdlnUserError, ex:
291 msg = "%s: %s\nTry '%s help' for info.\n"\
292 % (self.name, ex, self.name)
293 self.stderr.write(self._str(msg))
296 except StopOptionProcessing, ex:
299 self.options, args = None, argv[1:]
302 if loop == LOOP_ALWAYS:
304 self.cmdqueue.append(args)
305 return self.cmdloop()
306 elif loop == LOOP_NEVER:
308 return self.cmd(args)
310 return self.emptyline()
311 elif loop == LOOP_IF_EMPTY:
313 return self.cmd(args)
315 return self.cmdloop()
318 """Run one command and exit.
320 "argv" is the arglist for the command to run. argv[0] is the
321 command to run. If argv is an empty list then the
322 'emptyline' handler is run.
324 Returns the return value from the command handler.
326 assert isinstance(argv, (list, tuple)), \
327 "'argv' is not a sequence: %r" % argv
330 argv = self.precmd(argv)
331 retval = self.onecmd(argv)
334 if not self.cmdexc(argv):
340 """Safely convert the given str/unicode to a string for printing."""
344 #XXX What is the proper encoding to use here? 'utf-8' seems
345 # to work better than "getdefaultencoding" (usually
346 # 'ascii'), on OS X at least.
348 #return s.encode(sys.getdefaultencoding(), "replace")
349 return s.encode("utf-8", "replace")
351 def cmdloop(self, intro=None):
352 """Repeatedly issue a prompt, accept input, parse into an argv, and
353 dispatch (via .precmd(), .onecmd() and .postcmd()), passing them
354 the argv. In other words, start a shell.
356 "intro" (optional) is a introductory message to print when
357 starting the command loop. This overrides the class
358 "intro" attribute, if any.
360 self.cmdlooping = True
365 intro_str = self._str(intro)
366 self.stdout.write(intro_str+'\n')
371 argv = self.cmdqueue.pop(0)
372 assert isinstance(argv, (list, tuple)), \
373 "item on 'cmdqueue' is not a sequence: %r" % argv
375 if self.use_rawinput:
377 line = raw_input(self._prompt_str)
381 self.stdout.write(self._prompt_str)
383 line = self.stdin.readline()
387 line = line[:-1] # chop '\n'
388 argv = line2argv(line)
390 argv = self.precmd(argv)
391 retval = self.onecmd(argv)
394 if not self.cmdexc(argv):
397 self.lastretval = retval
399 self.cmdlooping = False
402 def precmd(self, argv):
403 """Hook method executed just before the command argv is
404 interpreted, but after the input prompt is generated and issued.
406 "argv" is the cmd to run.
408 Returns an argv to run (i.e. this method can modify the command
413 def postcmd(self, argv):
414 """Hook method executed just after a command dispatch is finished.
416 "argv" is the command that was run.
420 def cmdexc(self, argv):
421 """Called if an exception is raised in any of precmd(), onecmd(),
422 or postcmd(). If True is returned, the exception is deemed to have
423 been dealt with. Otherwise, the exception is re-raised.
425 The default implementation handles CmdlnUserError's, which
426 typically correspond to user error in calling commands (as
427 opposed to programmer error in the design of the script using
431 type, exc, traceback = sys.exc_info()
432 if isinstance(exc, CmdlnUserError):
433 msg = "%s %s: %s\nTry '%s help %s' for info.\n"\
434 % (self.name, argv[0], exc, self.name, argv[0])
435 self.stderr.write(self._str(msg))
439 def onecmd(self, argv):
441 return self.emptyline()
443 cmdname = self._get_canonical_cmd_name(argv[0])
445 handler = self._get_cmd_handler(cmdname)
447 return self._dispatch_cmd(handler, argv)
448 return self.default(argv)
450 def _dispatch_cmd(self, handler, argv):
453 def default(self, argv):
454 """Hook called to handle a command for which there is no handler.
456 "argv" is the command and arguments to run.
458 The default implementation writes and error message to stderr
459 and returns an error exit status.
461 Returns a numeric command exit status.
463 errmsg = self._str(self.unknowncmd % (argv[0],))
465 self.stderr.write(errmsg+"\n")
467 self.stderr.write("%s: %s\nTry '%s help' for info.\n"
468 % (self._name_str, errmsg, self._name_str))
472 def parseline(self, line):
473 # This is used by Cmd.complete (readline completer function) to
474 # massage the current line buffer before completion processing.
475 # We override to drop special '!' handling.
478 return None, None, line
480 line = 'help ' + line[1:]
482 while i < n and line[i] in self.identchars: i = i+1
483 cmd, arg = line[:i], line[i:].strip()
484 return cmd, arg, line
486 def helpdefault(self, cmd, known):
487 """Hook called to handle help on a command for which there is no
490 "cmd" is the command name on which help was requested.
491 "known" is a boolean indicating if this command is known
492 (i.e. if there is a handler for it).
494 Returns a return code.
497 msg = self._str(self.nohelp % (cmd,))
499 self.stderr.write(msg + '\n')
501 self.stderr.write("%s: %s\n" % (self.name, msg))
503 msg = self.unknowncmd % (cmd,)
505 self.stderr.write(msg + '\n')
507 self.stderr.write("%s: %s\n"
508 "Try '%s help' for info.\n"
509 % (self.name, msg, self.name))
514 def do_help(self, argv):
515 """${cmd_name}: give detailed help on a specific sub-command
518 ${name} help [SUBCOMMAND]
520 if len(argv) > 1: # asking for help on a particular command
522 cmdname = self._get_canonical_cmd_name(argv[1]) or argv[1]
524 return self.helpdefault(argv[1], False)
526 helpfunc = getattr(self, "help_"+cmdname, None)
530 handler = self._get_cmd_handler(cmdname)
532 doc = handler.__doc__
534 return self.helpdefault(argv[1], handler != None)
535 else: # bare "help" command
536 doc = self.__class__.__doc__ # try class docstring
538 # Try to provide some reasonable useful default help.
539 if self.cmdlooping: prefix = ""
540 else: prefix = self.name+' '
542 %sSUBCOMMAND [ARGS...]
548 """ % (prefix, prefix)
551 if doc: # *do* have help content, massage and print that
552 doc = self._help_reindent(doc)
553 doc = self._help_preprocess(doc, cmdname)
554 doc = doc.rstrip() + '\n' # trim down trailing space
555 self.stdout.write(self._str(doc))
557 do_help.aliases = ["?"]
560 def do_man(self, argv):
561 """${cmd_name}: generates a man page
566 self.stdout.write(self.man_header % {
567 'date': date.today().strftime('%b %Y'),
568 'version': self.get_version(),
570 'ucname': self.name.upper()
574 self.stdout.write(self.man_commands_header)
575 commands = self._help_get_command_list()
576 for command, doc in commands:
577 cmdname = command.split(' ')[0]
578 text = self._help_preprocess(doc, cmdname)
580 for line in text.splitlines(False):
581 if line[:8] == ' ' * 8:
583 lines.append(man_escape(line))
585 self.stdout.write('.TP\n\\fB%s\\fR\n%s\n' % (command, '\n'.join(lines)))
587 self.stdout.write(self.man_options_header)
588 self.stdout.write(man_escape(self._help_preprocess('${option_list}', None)))
590 self.stdout.write(self.man_footer)
594 def _help_reindent(self, help, indent=None):
595 """Hook to re-indent help strings before writing to stdout.
597 "help" is the help content to re-indent
598 "indent" is a string with which to indent each line of the
599 help content after normalizing. If unspecified or None
600 then the default is use: the 'self.helpindent' class
601 attribute. By default this is the empty string, i.e.
604 By default, all common leading whitespace is removed and then
605 the lot is indented by 'self.helpindent'. When calculating the
606 common leading whitespace the first line is ignored -- hence
607 help content for Conan can be written as follows and have the
608 expected indentation:
610 def do_crush(self, ...):
611 '''${cmd_name}: crush your enemies, see them driven before you...
613 c.f. Conan the Barbarian'''
616 indent = self.helpindent
617 lines = help.splitlines(0)
618 _dedentlines(lines, skip_first_line=True)
619 lines = [(indent+line).rstrip() for line in lines]
620 return '\n'.join(lines)
622 def _help_preprocess(self, help, cmdname):
623 """Hook to preprocess a help string before writing to stdout.
625 "help" is the help string to process.
626 "cmdname" is the canonical sub-command name for which help
627 is being given, or None if the help is not specific to a
630 By default the following template variables are interpolated in
631 help content. (Note: these are similar to Python 2.4's
632 string.Template interpolation but not quite.)
635 The tool's/shell's name, i.e. 'self.name'.
637 A formatted table of options for this shell/tool.
639 A formatted table of available sub-commands.
641 A formatted table of additional help topics (i.e. 'help_*'
642 methods with no matching 'do_*' method).
644 The name (and aliases) for this sub-command formatted as:
645 "NAME (ALIAS1, ALIAS2, ...)".
647 A formatted usage block inferred from the command function
650 A formatted table of options for this sub-command. (This is
651 only available for commands using the optparse integration,
652 i.e. using @cmdln.option decorators or manually setting the
653 'optparser' attribute on the 'do_*' method.)
655 Returns the processed help.
658 "${name}": self._help_preprocess_name,
659 "${option_list}": self._help_preprocess_option_list,
660 "${command_list}": self._help_preprocess_command_list,
661 "${help_list}": self._help_preprocess_help_list,
662 "${cmd_name}": self._help_preprocess_cmd_name,
663 "${cmd_usage}": self._help_preprocess_cmd_usage,
664 "${cmd_option_list}": self._help_preprocess_cmd_option_list,
667 for marker, preprocessor in preprocessors.items():
669 help = preprocessor(help, cmdname)
672 def _help_preprocess_name(self, help, cmdname=None):
673 return help.replace("${name}", self.name)
675 def _help_preprocess_option_list(self, help, cmdname=None):
676 marker = "${option_list}"
677 indent, indent_width = _get_indent(marker, help)
678 suffix = _get_trailing_whitespace(marker, help)
681 # Setup formatting options and format.
682 # - Indentation of 4 is better than optparse default of 2.
683 # C.f. Damian Conway's discussion of this in Perl Best
685 self.optparser.formatter.indent_increment = 4
686 self.optparser.formatter.current_indent = indent_width
687 block = self.optparser.format_option_help() + '\n'
691 help = help.replace(indent+marker+suffix, block, 1)
694 def _help_get_command_list(self):
695 # Find any aliases for commands.
696 token2canonical = self._get_canonical_map()
698 for token, cmdname in token2canonical.items():
699 if token == cmdname: continue
700 aliases.setdefault(cmdname, []).append(token)
702 # Get the list of (non-hidden) commands and their
703 # documentation, if any.
704 cmdnames = {} # use a dict to strip duplicates
705 for attr in self.get_names():
706 if attr.startswith("do_"):
707 cmdnames[attr[3:]] = True
708 cmdnames = cmdnames.keys()
709 cmdnames.remove("help")
710 cmdnames.remove("man")
713 for cmdname in cmdnames:
714 if aliases.get(cmdname):
717 cmdstr = "%s (%s)" % (cmdname, ", ".join(a))
722 helpfunc = getattr(self, 'help_'+cmdname)
723 except AttributeError:
724 handler = self._get_cmd_handler(cmdname)
726 doc = handler.__doc__
730 # Strip "${cmd_name}: " from the start of a command's doc. Best
731 # practice dictates that command help strings begin with this, but
732 # it isn't at all wanted for the command list.
733 to_strip = "${cmd_name}:"
734 if doc and doc.startswith(to_strip):
735 #log.debug("stripping %r from start of %s's help string",
737 doc = doc[len(to_strip):].lstrip()
738 if not getattr(self._get_cmd_handler(cmdname), "hidden", None):
739 linedata.append( (cmdstr, doc) )
743 def _help_preprocess_command_list(self, help, cmdname=None):
744 marker = "${command_list}"
745 indent, indent_width = _get_indent(marker, help)
746 suffix = _get_trailing_whitespace(marker, help)
748 linedata = self._help_get_command_list()
751 subindent = indent + ' '*4
752 lines = _format_linedata(linedata, subindent, indent_width+4)
753 block = indent + "commands:\n" \
754 + '\n'.join(lines) + "\n\n"
755 help = help.replace(indent+marker+suffix, block, 1)
758 def _help_preprocess_help_list(self, help, cmdname=None):
759 marker = "${help_list}"
760 indent, indent_width = _get_indent(marker, help)
761 suffix = _get_trailing_whitespace(marker, help)
763 # Determine the additional help topics, if any.
765 token2cmdname = self._get_canonical_map()
766 for attr in self.get_names():
767 if not attr.startswith("help_"): continue
769 if helpname not in token2cmdname:
770 helpnames[helpname] = True
773 helpnames = helpnames.keys()
775 linedata = [(self.name+" help "+n, "") for n in helpnames]
777 subindent = indent + ' '*4
778 lines = _format_linedata(linedata, subindent, indent_width+4)
779 block = indent + "additional help topics:\n" \
780 + '\n'.join(lines) + "\n\n"
783 help = help.replace(indent+marker+suffix, block, 1)
786 def _help_preprocess_cmd_name(self, help, cmdname=None):
787 marker = "${cmd_name}"
788 handler = self._get_cmd_handler(cmdname)
790 raise CmdlnError("cannot preprocess '%s' into help string: "
791 "could not find command handler for %r"
794 if hasattr(handler, "aliases"):
795 s += " (%s)" % (", ".join(handler.aliases))
796 help = help.replace(marker, s)
799 #TODO: this only makes sense as part of the Cmdln class.
800 # Add hooks to add help preprocessing template vars and put
801 # this one on that class.
802 def _help_preprocess_cmd_usage(self, help, cmdname=None):
803 marker = "${cmd_usage}"
804 handler = self._get_cmd_handler(cmdname)
806 raise CmdlnError("cannot preprocess '%s' into help string: "
807 "could not find command handler for %r"
809 indent, indent_width = _get_indent(marker, help)
810 suffix = _get_trailing_whitespace(marker, help)
812 # Extract the introspection bits we need.
813 func = handler.im_func
814 if func.func_defaults:
815 func_defaults = list(func.func_defaults)
818 co_argcount = func.func_code.co_argcount
819 co_varnames = func.func_code.co_varnames
820 co_flags = func.func_code.co_flags
824 # Adjust argcount for possible *args and **kwargs arguments.
825 argcount = co_argcount
826 if co_flags & CO_FLAGS_ARGS: argcount += 1
827 if co_flags & CO_FLAGS_KWARGS: argcount += 1
829 # Determine the usage string.
830 usage = "%s %s" % (self.name, cmdname)
831 if argcount <= 2: # handler ::= do_FOO(self, argv)
832 usage += " [ARGS...]"
833 elif argcount >= 3: # handler ::= do_FOO(self, subcmd, opts, ...)
834 argnames = list(co_varnames[3:argcount])
836 if co_flags & CO_FLAGS_KWARGS:
837 name = argnames.pop(-1)
839 # There is no generally accepted mechanism for passing
840 # keyword arguments from the command line. Could
841 # *perhaps* consider: arg=value arg2=value2 ...
842 warnings.warn("argument '**%s' on '%s.%s' command "
843 "handler will never get values"
844 % (name, self.__class__.__name__,
846 if co_flags & CO_FLAGS_ARGS:
847 name = argnames.pop(-1)
848 tail = "[%s...]" % name.upper()
850 func_defaults.pop(-1)
851 name = argnames.pop(-1)
852 tail = "[%s%s%s]" % (name.upper(), (tail and ' ' or ''), tail)
854 name = argnames.pop(-1)
855 tail = "%s %s" % (name.upper(), tail)
859 self.helpindent + "usage:",
860 self.helpindent + ' '*4 + usage
862 block = '\n'.join(block_lines) + '\n\n'
864 help = help.replace(indent+marker+suffix, block, 1)
867 #TODO: this only makes sense as part of the Cmdln class.
868 # Add hooks to add help preprocessing template vars and put
869 # this one on that class.
870 def _help_preprocess_cmd_option_list(self, help, cmdname=None):
871 marker = "${cmd_option_list}"
872 handler = self._get_cmd_handler(cmdname)
874 raise CmdlnError("cannot preprocess '%s' into help string: "
875 "could not find command handler for %r"
877 indent, indent_width = _get_indent(marker, help)
878 suffix = _get_trailing_whitespace(marker, help)
879 if hasattr(handler, "optparser"):
880 # Setup formatting options and format.
881 # - Indentation of 4 is better than optparse default of 2.
882 # C.f. Damian Conway's discussion of this in Perl Best
884 handler.optparser.formatter.indent_increment = 4
885 handler.optparser.formatter.current_indent = indent_width
886 block = handler.optparser.format_option_help() + '\n'
890 help = help.replace(indent+marker+suffix, block, 1)
893 def _get_canonical_cmd_name(self, token):
894 map = self._get_canonical_map()
895 return map.get(token, None)
897 def _get_canonical_map(self):
898 """Return a mapping of available command names and aliases to
899 their canonical command name.
901 cacheattr = "_token2canonical"
902 if not hasattr(self, cacheattr):
903 # Get the list of commands and their aliases, if any.
905 cmd2funcname = {} # use a dict to strip duplicates
906 for attr in self.get_names():
907 if attr.startswith("do_"): cmdname = attr[3:]
908 elif attr.startswith("_do_"): cmdname = attr[4:]
911 cmd2funcname[cmdname] = attr
912 token2canonical[cmdname] = cmdname
913 for cmdname, funcname in cmd2funcname.items(): # add aliases
914 func = getattr(self, funcname)
915 aliases = getattr(func, "aliases", [])
916 for alias in aliases:
917 if alias in cmd2funcname:
919 warnings.warn("'%s' alias for '%s' command conflicts "
921 % (alias, cmdname, cmd2funcname[alias]))
923 token2canonical[alias] = cmdname
924 setattr(self, cacheattr, token2canonical)
925 return getattr(self, cacheattr)
927 def _get_cmd_handler(self, cmdname):
930 handler = getattr(self, 'do_' + cmdname)
931 except AttributeError:
933 # Private command handlers begin with "_do_".
934 handler = getattr(self, '_do_' + cmdname)
935 except AttributeError:
939 def _do_EOF(self, argv):
940 # Default EOF handler
941 # Note: an actual EOF is redirected to this command.
942 #TODO: separate name for this. Currently it is available from
943 # command-line. Is that okay?
944 self.stdout.write('\n')
949 # Different from cmd.Cmd: don't repeat the last command for an
954 return self.do_help(["help"])
957 #---- optparse.py extension to fix (IMO) some deficiencies
959 # See the class _OptionParserEx docstring for details.
962 class StopOptionProcessing(Exception):
963 """Indicate that option *and argument* processing should stop
964 cleanly. This is not an error condition. It is similar in spirit to
965 StopIteration. This is raised by _OptionParserEx's default "help"
966 and "version" option actions and can be raised by custom option
969 Hence the typical CmdlnOptionParser (a subclass of _OptionParserEx)
972 parser = CmdlnOptionParser(mycmd)
973 parser.add_option("-f", "--force", dest="force")
976 opts, args = parser.parse_args()
977 except StopOptionProcessing:
978 # normal termination, "--help" was probably given
982 class _OptionParserEx(optparse.OptionParser):
983 """An optparse.OptionParser that uses exceptions instead of sys.exit.
985 This class is an extension of optparse.OptionParser that differs
987 - Correct (IMO) the default OptionParser error handling to never
988 sys.exit(). Instead OptParseError exceptions are passed through.
989 - Add the StopOptionProcessing exception (a la StopIteration) to
990 indicate normal termination of option processing.
991 See StopOptionProcessing's docstring for details.
993 I'd also like to see the following in the core optparse.py, perhaps
994 as a RawOptionParser which would serve as a base class for the more
995 generally used OptionParser (that works as current):
996 - Remove the implicit addition of the -h|--help and --version
997 options. They can get in the way (e.g. if want '-?' and '-V' for
998 these as well) and it is not hard to do:
999 optparser.add_option("-h", "--help", action="help")
1000 optparser.add_option("--version", action="version")
1001 These are good practices, just not valid defaults if they can
1004 def error(self, msg):
1005 raise optparse.OptParseError(msg)
1007 def exit(self, status=0, msg=None):
1009 raise StopOptionProcessing(msg)
1011 #TODO: don't lose status info here
1012 raise optparse.OptParseError(msg)
1016 #---- optparse.py-based option processing support
1018 class CmdlnOptionParser(_OptionParserEx):
1019 """An optparse.OptionParser class more appropriate for top-level
1020 Cmdln options. For parsing of sub-command options, see
1024 - disable_interspersed_args() by default, because a Cmdln instance
1025 has sub-commands which may themselves have options.
1026 - Redirect print_help() to the Cmdln.do_help() which is better
1027 equiped to handle the "help" action.
1028 - error() will raise a CmdlnUserError: OptionParse.error() is meant
1029 to be called for user errors. Raising a well-known error here can
1030 make error handling clearer.
1031 - Also see the changes in _OptionParserEx.
1033 def __init__(self, cmdln, **kwargs):
1035 kwargs["prog"] = self.cmdln.name
1036 _OptionParserEx.__init__(self, **kwargs)
1037 self.disable_interspersed_args()
1039 def print_help(self, file=None):
1040 self.cmdln.onecmd(["help"])
1042 def error(self, msg):
1043 raise CmdlnUserError(msg)
1046 class SubCmdOptionParser(_OptionParserEx):
1047 def set_cmdln_info(self, cmdln, subcmd):
1048 """Called by Cmdln to pass relevant info about itself needed
1052 self.subcmd = subcmd
1054 def print_help(self, file=None):
1055 self.cmdln.onecmd(["help", self.subcmd])
1057 def error(self, msg):
1058 raise CmdlnUserError(msg)
1061 def option(*args, **kwargs):
1062 """Decorator to add an option to the optparser argument of a Cmdln
1066 class MyShell(cmdln.Cmdln):
1067 @cmdln.option("-f", "--force", help="force removal")
1068 def do_remove(self, subcmd, opts, *args):
1071 #XXX Is there a possible optimization for many options to not have a
1072 # large stack depth here?
1074 if not hasattr(f, "optparser"):
1075 f.optparser = SubCmdOptionParser()
1076 f.optparser.add_option(*args, **kwargs)
1081 """For obsolete calls, hide them in help listings.
1084 class MyShell(cmdln.Cmdln):
1086 def do_shell(self, argv):
1087 #...implement 'shell' command
1095 class Cmdln(RawCmdln):
1096 """An improved (on cmd.Cmd) framework for building multi-subcommand
1097 scripts (think "svn" & "cvs") and simple shells (think "pdb" and
1104 class MySVN(cmdln.Cmdln):
1107 @cmdln.aliases('stat', 'st')
1108 @cmdln.option('-v', '--verbose', action='store_true'
1109 help='print verbose information')
1110 def do_status(self, subcmd, opts, *paths):
1111 print "handle 'svn status' command"
1115 if __name__ == "__main__":
1117 retval = shell.main()
1120 'Cmdln' extends 'RawCmdln' by providing optparse option processing
1121 integration. See this class' _dispatch_cmd() docstring and
1122 <http://trentm.com/projects/cmdln> for more information.
1124 def _dispatch_cmd(self, handler, argv):
1125 """Introspect sub-command handler signature to determine how to
1126 dispatch the command. The raw handler provided by the base
1127 'RawCmdln' class is still supported:
1129 def do_foo(self, argv):
1130 # 'argv' is the vector of command line args, argv[0] is
1131 # the command name itself (i.e. "foo" or an alias)
1134 In addition, if the handler has more than 2 arguments option
1135 processing is automatically done (using optparse):
1137 @cmdln.option('-v', '--verbose', action='store_true')
1138 def do_bar(self, subcmd, opts, *args):
1139 # subcmd = <"bar" or an alias>
1140 # opts = <an optparse.Values instance>
1142 print "lots of debugging output..."
1143 # args = <tuple of arguments>
1147 TODO: explain that "*args" can be other signatures as well.
1149 The `cmdln.option` decorator corresponds to an `add_option()`
1150 method call on an `optparse.OptionParser` instance.
1152 You can declare a specific number of arguments:
1154 @cmdln.option('-v', '--verbose', action='store_true')
1155 def do_bar2(self, subcmd, opts, bar_one, bar_two):
1158 and an appropriate error message will be raised/printed if the
1159 command is called with a different number of args.
1161 co_argcount = handler.im_func.func_code.co_argcount
1162 if co_argcount == 2: # handler ::= do_foo(self, argv)
1163 return handler(argv)
1164 elif co_argcount >= 3: # handler ::= do_foo(self, subcmd, opts, ...)
1166 optparser = handler.optparser
1167 except AttributeError:
1168 optparser = handler.im_func.optparser = SubCmdOptionParser()
1169 assert isinstance(optparser, SubCmdOptionParser)
1170 optparser.set_cmdln_info(self, argv[0])
1172 opts, args = optparser.parse_args(argv[1:])
1173 except StopOptionProcessing:
1174 #TODO: this doesn't really fly for a replacement of
1175 # optparse.py behaviour, does it?
1176 return 0 # Normal command termination
1179 return handler(argv[0], opts, *args)
1180 except TypeError, ex:
1181 # Some TypeError's are user errors:
1182 # do_foo() takes at least 4 arguments (3 given)
1183 # do_foo() takes at most 5 arguments (6 given)
1184 # do_foo() takes exactly 5 arguments (6 given)
1185 # Raise CmdlnUserError for these with a suitably
1186 # massaged error message.
1188 tb = sys.exc_info()[2] # the traceback object
1189 if tb.tb_next is not None:
1190 # If the traceback is more than one level deep, then the
1191 # TypeError do *not* happen on the "handler(...)" call
1192 # above. In that we don't want to handle it specially
1193 # here: it would falsely mask deeper code errors.
1196 match = _INCORRECT_NUM_ARGS_RE.search(msg)
1198 msg = list(match.groups())
1199 msg[1] = int(msg[1]) - 3
1201 msg[2] = msg[2].replace("arguments", "argument")
1202 msg[3] = int(msg[3]) - 3
1203 msg = ''.join(map(str, msg))
1204 raise CmdlnUserError(msg)
1208 raise CmdlnError("incorrect argcount for %s(): takes %d, must "
1209 "take 2 for 'argv' signature or 3+ for 'opts' "
1210 "signature" % (handler.__name__, co_argcount))
1214 #---- internal support functions
1216 def _format_linedata(linedata, indent, indent_width):
1217 """Format specific linedata into a pleasant layout.
1219 "linedata" is a list of 2-tuples of the form:
1220 (<item-display-string>, <item-docstring>)
1221 "indent" is a string to use for one level of indentation
1222 "indent_width" is a number of columns by which the
1223 formatted data will be indented when printed.
1225 The <item-display-string> column is held to 15 columns.
1228 WIDTH = 78 - indent_width
1232 NAME_WIDTH = min(max([len(s) for s,d in linedata]), MAX_NAME_WIDTH)
1233 DOC_WIDTH = WIDTH - NAME_WIDTH - SPACING
1234 for namestr, doc in linedata:
1235 line = indent + namestr
1236 if len(namestr) <= NAME_WIDTH:
1237 line += ' ' * (NAME_WIDTH + SPACING - len(namestr))
1240 line = indent + ' ' * (NAME_WIDTH + SPACING)
1241 line += _summarize_doc(doc, DOC_WIDTH)
1242 lines.append(line.rstrip())
1245 def _summarize_doc(doc, length=60):
1246 r"""Parse out a short one line summary from the given doclines.
1248 "doc" is the doc string to summarize.
1249 "length" is the max length for the summary
1251 >>> _summarize_doc("this function does this")
1252 'this function does this'
1253 >>> _summarize_doc("this function does this", 10)
1255 >>> _summarize_doc("this function does this\nand that")
1256 'this function does this and that'
1257 >>> _summarize_doc("this function does this\n\nand that")
1258 'this function does this'
1263 assert length > 3, "length <= 3 is absurdly short for a doc summary"
1264 doclines = doc.strip().splitlines(0)
1269 for i, line in enumerate(doclines):
1270 stripped = line.strip()
1273 summlines.append(stripped)
1274 if len(''.join(summlines)) >= length:
1277 summary = ' '.join(summlines)
1278 if len(summary) > length:
1279 summary = summary[:length-3] + "..."
1283 def line2argv(line):
1284 r"""Parse the given line into an argument vector.
1286 "line" is the line of input to parse.
1288 This may get niggly when dealing with quoting and escaping. The
1289 current state of this parsing may not be completely thorough/correct
1292 >>> from cmdln import line2argv
1293 >>> line2argv("foo")
1295 >>> line2argv("foo bar")
1297 >>> line2argv("foo bar ")
1299 >>> line2argv(" foo bar")
1304 >>> line2argv("'foo bar'")
1306 >>> line2argv('"foo bar"')
1308 >>> line2argv(r'"foo\"bar"')
1310 >>> line2argv("'foo bar' spam")
1312 >>> line2argv("'foo 'bar spam")
1314 >>> line2argv("'foo")
1315 Traceback (most recent call last):
1317 ValueError: command line is not terminated: unfinished single-quoted segment
1318 >>> line2argv('"foo')
1319 Traceback (most recent call last):
1321 ValueError: command line is not terminated: unfinished double-quoted segment
1322 >>> line2argv('some\tsimple\ttests')
1323 ['some', 'simple', 'tests']
1324 >>> line2argv('a "more complex" test')
1325 ['a', 'more complex', 'test']
1326 >>> line2argv('a more="complex test of " quotes')
1327 ['a', 'more=complex test of ', 'quotes']
1328 >>> line2argv('a more" complex test of " quotes')
1329 ['a', 'more complex test of ', 'quotes']
1330 >>> line2argv('an "embedded \\"quote\\""')
1331 ['an', 'embedded "quote"']
1337 arg = None # the current argument being parsed
1341 if i >= len(line): break
1344 if ch == "\\": # escaped char always added to arg, regardless of state
1345 if arg is None: arg = ""
1350 if state == "single-quoted":
1355 elif state == "double-quoted":
1360 elif state == "default":
1362 if arg is None: arg = ""
1363 state = "double-quoted"
1365 if arg is None: arg = ""
1366 state = "single-quoted"
1367 elif ch in string.whitespace:
1372 if arg is None: arg = ""
1376 if state != "default":
1377 raise ValueError("command line is not terminated: unfinished %s "
1382 def argv2line(argv):
1383 r"""Put together the given argument vector into a command line.
1385 "argv" is the argument vector to process.
1387 >>> from cmdln import argv2line
1388 >>> argv2line(['foo'])
1390 >>> argv2line(['foo', 'bar'])
1392 >>> argv2line(['foo', 'bar baz'])
1394 >>> argv2line(['foo"bar'])
1396 >>> print argv2line(['foo" bar'])
1398 >>> print argv2line(["foo' bar"])
1400 >>> argv2line(["foo'bar"])
1405 if ' ' in arg and '"' not in arg:
1407 elif ' ' in arg and "'" not in arg:
1410 arg = arg.replace('"', r'\"')
1412 escapedArgs.append(arg)
1413 return ' '.join(escapedArgs)
1416 # Recipe: dedent (0.1) in /Users/trentm/tm/recipes/cookbook
1417 def _dedentlines(lines, tabsize=8, skip_first_line=False):
1418 """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines
1420 "lines" is a list of lines to dedent.
1421 "tabsize" is the tab width to use for indent width calculations.
1422 "skip_first_line" is a boolean indicating if the first line should
1423 be skipped for calculating the indent width and for dedenting.
1424 This is sometimes useful for docstrings and similar.
1426 Same as dedent() except operates on a sequence of lines. Note: the
1427 lines list is modified **in-place**.
1431 print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
1432 % (tabsize, skip_first_line)
1435 for i, line in enumerate(lines):
1436 if i == 0 and skip_first_line: continue
1442 indent += tabsize - (indent % tabsize)
1444 continue # skip all-whitespace lines
1448 continue # skip all-whitespace lines
1449 if DEBUG: print "dedent: indent=%d: %r" % (indent, line)
1453 margin = min(margin, indent)
1454 if DEBUG: print "dedent: margin=%r" % margin
1456 if margin is not None and margin > 0:
1457 for i, line in enumerate(lines):
1458 if i == 0 and skip_first_line: continue
1460 for j, ch in enumerate(line):
1464 removed += tabsize - (removed % tabsize)
1466 if DEBUG: print "dedent: %r: EOL -> strip up to EOL" % line
1467 lines[i] = lines[i][j:]
1470 raise ValueError("unexpected non-whitespace char %r in "
1471 "line %r while removing %d-space margin"
1472 % (ch, line, margin))
1474 print "dedent: %r: %r -> removed %d/%d"\
1475 % (line, ch, removed, margin)
1476 if removed == margin:
1477 lines[i] = lines[i][j+1:]
1479 elif removed > margin:
1480 lines[i] = ' '*(removed-margin) + lines[i][j+1:]
1484 def _dedent(text, tabsize=8, skip_first_line=False):
1485 """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text
1487 "text" is the text to dedent.
1488 "tabsize" is the tab width to use for indent width calculations.
1489 "skip_first_line" is a boolean indicating if the first line should
1490 be skipped for calculating the indent width and for dedenting.
1491 This is sometimes useful for docstrings and similar.
1493 textwrap.dedent(s), but don't expand tabs to spaces
1495 lines = text.splitlines(1)
1496 _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line)
1497 return ''.join(lines)
1500 def _get_indent(marker, s, tab_width=8):
1501 """_get_indent(marker, s, tab_width=8) ->
1502 (<indentation-of-'marker'>, <indentation-width>)"""
1503 # Figure out how much the marker is indented.
1504 INDENT_CHARS = tuple(' \t')
1505 start = s.index(marker)
1508 if s[i-1] not in INDENT_CHARS:
1517 indent_width += tab_width - (indent_width % tab_width)
1518 return indent, indent_width
1520 def _get_trailing_whitespace(marker, s):
1521 """Return the whitespace content trailing the given 'marker' in string 's',
1522 up to and including a newline.
1525 start = s.index(marker) + len(marker)
1530 elif s[i] in '\r\n':
1532 if s[i] == '\r' and i+1 < len(s) and s[i+1] == '\n':