2 # Copyright (c) 2002-2007 ActiveState Software Inc.
3 # License: MIT (see LICENSE.txt for license details)
5 # Home: http://trentm.com/projects/cmdln/
7 """An improvement on Python's standard cmd.py module.
9 As with cmd.py, this module provides "a simple framework for writing
10 line-oriented command intepreters." This module provides a 'RawCmdln'
11 class that fixes some design flaws in cmd.Cmd, making it more scalable
12 and nicer to use for good 'cvs'- or 'svn'-style command line interfaces
13 or simple shells. And it provides a 'Cmdln' class that add
14 optparse-based option processing. Basically you use it like this:
18 class MySVN(cmdln.Cmdln):
21 @cmdln.alias('stat', 'st')
22 @cmdln.option('-v', '--verbose', action='store_true'
23 help='print verbose information')
24 def do_status(self, subcmd, opts, *paths):
25 print "handle 'svn status' command"
29 if __name__ == "__main__":
34 See the README.txt or <http://trentm.com/projects/cmdln/> for more
38 __version_info__ = (1, 1, 2)
39 __version__ = '.'.join(map(str, __version_info__))
46 from pprint import pprint
54 LOOP_ALWAYS, LOOP_NEVER, LOOP_IF_EMPTY = range(3)
56 # An unspecified optional argument when None is a meaningful value.
57 _NOT_SPECIFIED = ("Not", "Specified")
59 # Pattern to match a TypeError message from a call that
60 # failed because of incorrect number of arguments (see
62 _INCORRECT_NUM_ARGS_RE = re.compile(
63 r"(takes [\w ]+ )(\d+)( arguments? \()(\d+)( given\))")
69 class CmdlnError(Exception):
70 """A cmdln.py usage error."""
71 def __init__(self, msg):
76 class CmdlnUserError(Exception):
77 """An error by a user of a cmdln-based tool/shell."""
82 #---- public methods and classes
85 """Decorator to add aliases for Cmdln.do_* command handlers.
88 class MyShell(cmdln.Cmdln):
89 @cmdln.alias("!", "sh")
90 def do_shell(self, argv):
91 #...implement 'shell' command
94 if not hasattr(f, "aliases"):
101 class RawCmdln(cmd.Cmd):
102 """An improved (on cmd.Cmd) framework for building multi-subcommand
103 scripts (think "svn" & "cvs") and simple shells (think "pdb" and
110 class MySVN(cmdln.RawCmdln):
113 @cmdln.aliases('stat', 'st')
114 def do_status(self, argv):
115 print "handle 'svn status' command"
117 if __name__ == "__main__":
119 retval = shell.main()
122 See <http://trentm.com/projects/cmdln> for more information.
124 name = None # if unset, defaults basename(sys.argv[0])
125 prompt = None # if unset, defaults to self.name+"> "
126 version = None # if set, default top-level options include --version
128 # Default messages for some 'help' command error cases.
129 # They are interpolated with one arg: the command.
130 nohelp = "no help on '%s'"
131 unknowncmd = "unknown command: '%s'"
133 helpindent = '' # string with which to indent help output
135 def __init__(self, completekey='tab',
136 stdin=None, stdout=None, stderr=None):
137 """Cmdln(completekey='tab', stdin=None, stdout=None, stderr=None)
139 The optional argument 'completekey' is the readline name of a
140 completion key; it defaults to the Tab key. If completekey is
141 not None and the readline module is available, command completion
142 is done automatically.
144 The optional arguments 'stdin', 'stdout' and 'stderr' specify
145 alternate input, output and error output file objects; if not
146 specified, sys.* are used.
148 If 'stdout' but not 'stderr' is specified, stdout is used for
149 error output. This is to provide least surprise for users used
150 to only the 'stdin' and 'stdout' options with cmd.Cmd.
153 if self.name is None:
154 self.name = os.path.basename(sys.argv[0])
155 if self.prompt is None:
156 self.prompt = self.name+"> "
157 self._name_str = self._str(self.name)
158 self._prompt_str = self._str(self.prompt)
159 if stdin is not None:
162 self.stdin = sys.stdin
163 if stdout is not None:
166 self.stdout = sys.stdout
167 if stderr is not None:
169 elif stdout is not None:
172 self.stderr = sys.stderr
174 self.completekey = completekey
175 self.cmdlooping = False
177 def get_optparser(self):
178 """Hook for subclasses to set the option parser for the
179 top-level command/shell.
181 This option parser is used retrieved and used by `.main()' to
182 handle top-level options.
184 The default implements a single '-h|--help' option. Sub-classes
185 can return None to have no options at the top-level. Typically
186 an instance of CmdlnOptionParser should be returned.
188 version = (self.version is not None
189 and "%s %s" % (self._name_str, self.version)
191 return CmdlnOptionParser(self, version=version)
193 def postoptparse(self):
194 """Hook method executed just after `.main()' parses top-level
197 When called `self.options' holds the results of the option parse.
201 def main(self, argv=None, loop=LOOP_NEVER):
202 """A possible mainline handler for a script, like so:
205 class MyCmd(cmdln.Cmdln):
209 if __name__ == "__main__":
212 By default this will use sys.argv to issue a single command to
213 'MyCmd', then exit. The 'loop' argument can be use to control
214 interactive shell behaviour.
217 "argv" (optional, default sys.argv) is the command to run.
218 It must be a sequence, where the first element is the
219 command name and subsequent elements the args for that
221 "loop" (optional, default LOOP_NEVER) is a constant
222 indicating if a command loop should be started (i.e. an
223 interactive shell). Valid values (constants on this module):
224 LOOP_ALWAYS start loop and run "argv", if any
225 LOOP_NEVER run "argv" (or .emptyline()) and exit
226 LOOP_IF_EMPTY run "argv", if given, and exit;
227 otherwise, start loop
233 argv = argv[:] # don't modify caller's list
235 self.optparser = self.get_optparser()
236 if self.optparser: # i.e. optparser=None means don't process for opts
238 self.options, args = self.optparser.parse_args(argv[1:])
239 except CmdlnUserError, ex:
240 msg = "%s: %s\nTry '%s help' for info.\n"\
241 % (self.name, ex, self.name)
242 self.stderr.write(self._str(msg))
245 except StopOptionProcessing, ex:
248 self.options, args = None, argv[1:]
251 if loop == LOOP_ALWAYS:
253 self.cmdqueue.append(args)
254 return self.cmdloop()
255 elif loop == LOOP_NEVER:
257 return self.cmd(args)
259 return self.emptyline()
260 elif loop == LOOP_IF_EMPTY:
262 return self.cmd(args)
264 return self.cmdloop()
267 """Run one command and exit.
269 "argv" is the arglist for the command to run. argv[0] is the
270 command to run. If argv is an empty list then the
271 'emptyline' handler is run.
273 Returns the return value from the command handler.
275 assert isinstance(argv, (list, tuple)), \
276 "'argv' is not a sequence: %r" % argv
279 argv = self.precmd(argv)
280 retval = self.onecmd(argv)
283 if not self.cmdexc(argv):
289 """Safely convert the given str/unicode to a string for printing."""
293 #XXX What is the proper encoding to use here? 'utf-8' seems
294 # to work better than "getdefaultencoding" (usually
295 # 'ascii'), on OS X at least.
297 #return s.encode(sys.getdefaultencoding(), "replace")
298 return s.encode("utf-8", "replace")
300 def cmdloop(self, intro=None):
301 """Repeatedly issue a prompt, accept input, parse into an argv, and
302 dispatch (via .precmd(), .onecmd() and .postcmd()), passing them
303 the argv. In other words, start a shell.
305 "intro" (optional) is a introductory message to print when
306 starting the command loop. This overrides the class
307 "intro" attribute, if any.
309 self.cmdlooping = True
311 if self.use_rawinput and self.completekey:
314 self.old_completer = readline.get_completer()
315 readline.set_completer(self.complete)
316 readline.parse_and_bind(self.completekey+": complete")
323 intro_str = self._str(intro)
324 self.stdout.write(intro_str+'\n')
329 argv = self.cmdqueue.pop(0)
330 assert isinstance(argv, (list, tuple)), \
331 "item on 'cmdqueue' is not a sequence: %r" % argv
333 if self.use_rawinput:
335 line = raw_input(self._prompt_str)
339 self.stdout.write(self._prompt_str)
341 line = self.stdin.readline()
345 line = line[:-1] # chop '\n'
346 argv = line2argv(line)
348 argv = self.precmd(argv)
349 retval = self.onecmd(argv)
352 if not self.cmdexc(argv):
355 self.lastretval = retval
358 if self.use_rawinput and self.completekey:
361 readline.set_completer(self.old_completer)
364 self.cmdlooping = False
367 def precmd(self, argv):
368 """Hook method executed just before the command argv is
369 interpreted, but after the input prompt is generated and issued.
371 "argv" is the cmd to run.
373 Returns an argv to run (i.e. this method can modify the command
378 def postcmd(self, argv):
379 """Hook method executed just after a command dispatch is finished.
381 "argv" is the command that was run.
385 def cmdexc(self, argv):
386 """Called if an exception is raised in any of precmd(), onecmd(),
387 or postcmd(). If True is returned, the exception is deemed to have
388 been dealt with. Otherwise, the exception is re-raised.
390 The default implementation handles CmdlnUserError's, which
391 typically correspond to user error in calling commands (as
392 opposed to programmer error in the design of the script using
396 type, exc, traceback = sys.exc_info()
397 if isinstance(exc, CmdlnUserError):
398 msg = "%s %s: %s\nTry '%s help %s' for info.\n"\
399 % (self.name, argv[0], exc, self.name, argv[0])
400 self.stderr.write(self._str(msg))
404 def onecmd(self, argv):
406 return self.emptyline()
408 cmdname = self._get_canonical_cmd_name(argv[0])
410 handler = self._get_cmd_handler(cmdname)
412 return self._dispatch_cmd(handler, argv)
413 return self.default(argv)
415 def _dispatch_cmd(self, handler, argv):
418 def default(self, argv):
419 """Hook called to handle a command for which there is no handler.
421 "argv" is the command and arguments to run.
423 The default implementation writes and error message to stderr
424 and returns an error exit status.
426 Returns a numeric command exit status.
428 errmsg = self._str(self.unknowncmd % (argv[0],))
430 self.stderr.write(errmsg+"\n")
432 self.stderr.write("%s: %s\nTry '%s help' for info.\n"
433 % (self._name_str, errmsg, self._name_str))
437 def parseline(self, line):
438 # This is used by Cmd.complete (readline completer function) to
439 # massage the current line buffer before completion processing.
440 # We override to drop special '!' handling.
443 return None, None, line
445 line = 'help ' + line[1:]
447 while i < n and line[i] in self.identchars: i = i+1
448 cmd, arg = line[:i], line[i:].strip()
449 return cmd, arg, line
451 def helpdefault(self, cmd, known):
452 """Hook called to handle help on a command for which there is no
455 "cmd" is the command name on which help was requested.
456 "known" is a boolean indicating if this command is known
457 (i.e. if there is a handler for it).
459 Returns a return code.
462 msg = self._str(self.nohelp % (cmd,))
464 self.stderr.write(msg + '\n')
466 self.stderr.write("%s: %s\n" % (self.name, msg))
468 msg = self.unknowncmd % (cmd,)
470 self.stderr.write(msg + '\n')
472 self.stderr.write("%s: %s\n"
473 "Try '%s help' for info.\n"
474 % (self.name, msg, self.name))
478 def do_help(self, argv):
479 """${cmd_name}: give detailed help on a specific sub-command
482 ${name} help [COMMAND]
484 if len(argv) > 1: # asking for help on a particular command
486 cmdname = self._get_canonical_cmd_name(argv[1]) or argv[1]
488 return self.helpdefault(argv[1], False)
490 helpfunc = getattr(self, "help_"+cmdname, None)
494 handler = self._get_cmd_handler(cmdname)
496 doc = handler.__doc__
498 return self.helpdefault(argv[1], handler != None)
499 else: # bare "help" command
500 doc = self.__class__.__doc__ # try class docstring
502 # Try to provide some reasonable useful default help.
503 if self.cmdlooping: prefix = ""
504 else: prefix = self.name+' '
512 """ % (prefix, prefix)
515 if doc: # *do* have help content, massage and print that
516 doc = self.help_reindent(doc)
517 doc = self.help_preprocess(doc, cmdname)
518 doc = doc.rstrip() + '\n' # trim down trailing space
519 self.stdout.write(self._str(doc))
521 do_help.aliases = ["?"]
523 def help_reindent(self, help, indent=None):
524 """Hook to re-indent help strings before writing to stdout.
526 "help" is the help content to re-indent
527 "indent" is a string with which to indent each line of the
528 help content after normalizing. If unspecified or None
529 then the default is use: the 'self.helpindent' class
530 attribute. By default this is the empty string, i.e.
533 By default, all common leading whitespace is removed and then
534 the lot is indented by 'self.helpindent'. When calculating the
535 common leading whitespace the first line is ignored -- hence
536 help content for Conan can be written as follows and have the
537 expected indentation:
539 def do_crush(self, ...):
540 '''${cmd_name}: crush your enemies, see them driven before you...
542 c.f. Conan the Barbarian'''
545 indent = self.helpindent
546 lines = help.splitlines(0)
547 _dedentlines(lines, skip_first_line=True)
548 lines = [(indent+line).rstrip() for line in lines]
549 return '\n'.join(lines)
551 def help_preprocess(self, help, cmdname):
552 """Hook to preprocess a help string before writing to stdout.
554 "help" is the help string to process.
555 "cmdname" is the canonical sub-command name for which help
556 is being given, or None if the help is not specific to a
559 By default the following template variables are interpolated in
560 help content. (Note: these are similar to Python 2.4's
561 string.Template interpolation but not quite.)
564 The tool's/shell's name, i.e. 'self.name'.
566 A formatted table of options for this shell/tool.
568 A formatted table of available sub-commands.
570 A formatted table of additional help topics (i.e. 'help_*'
571 methods with no matching 'do_*' method).
573 The name (and aliases) for this sub-command formatted as:
574 "NAME (ALIAS1, ALIAS2, ...)".
576 A formatted usage block inferred from the command function
579 A formatted table of options for this sub-command. (This is
580 only available for commands using the optparse integration,
581 i.e. using @cmdln.option decorators or manually setting the
582 'optparser' attribute on the 'do_*' method.)
584 Returns the processed help.
587 "${name}": self._help_preprocess_name,
588 "${option_list}": self._help_preprocess_option_list,
589 "${command_list}": self._help_preprocess_command_list,
590 "${help_list}": self._help_preprocess_help_list,
591 "${cmd_name}": self._help_preprocess_cmd_name,
592 "${cmd_usage}": self._help_preprocess_cmd_usage,
593 "${cmd_option_list}": self._help_preprocess_cmd_option_list,
596 for marker, preprocessor in preprocessors.items():
598 help = preprocessor(help, cmdname)
601 def _help_preprocess_name(self, help, cmdname=None):
602 return help.replace("${name}", self.name)
604 def _help_preprocess_option_list(self, help, cmdname=None):
605 marker = "${option_list}"
606 indent, indent_width = _get_indent(marker, help)
607 suffix = _get_trailing_whitespace(marker, help)
610 # Setup formatting options and format.
611 # - Indentation of 4 is better than optparse default of 2.
612 # C.f. Damian Conway's discussion of this in Perl Best
614 self.optparser.formatter.indent_increment = 4
615 self.optparser.formatter.current_indent = indent_width
616 block = self.optparser.format_option_help() + '\n'
620 help = help.replace(indent+marker+suffix, block, 1)
624 def _help_preprocess_command_list(self, help, cmdname=None):
625 marker = "${command_list}"
626 indent, indent_width = _get_indent(marker, help)
627 suffix = _get_trailing_whitespace(marker, help)
629 # Find any aliases for commands.
630 token2canonical = self._get_canonical_map()
632 for token, cmdname in token2canonical.items():
633 if token == cmdname: continue
634 aliases.setdefault(cmdname, []).append(token)
636 # Get the list of (non-hidden) commands and their
637 # documentation, if any.
638 cmdnames = {} # use a dict to strip duplicates
639 for attr in self.get_names():
640 if attr.startswith("do_"):
641 cmdnames[attr[3:]] = True
642 cmdnames = cmdnames.keys()
645 for cmdname in cmdnames:
646 if aliases.get(cmdname):
649 cmdstr = "%s (%s)" % (cmdname, ", ".join(a))
654 helpfunc = getattr(self, 'help_'+cmdname)
655 except AttributeError:
656 handler = self._get_cmd_handler(cmdname)
658 doc = handler.__doc__
662 # Strip "${cmd_name}: " from the start of a command's doc. Best
663 # practice dictates that command help strings begin with this, but
664 # it isn't at all wanted for the command list.
665 to_strip = "${cmd_name}:"
666 if doc and doc.startswith(to_strip):
667 #log.debug("stripping %r from start of %s's help string",
669 doc = doc[len(to_strip):].lstrip()
670 linedata.append( (cmdstr, doc) )
673 subindent = indent + ' '*4
674 lines = _format_linedata(linedata, subindent, indent_width+4)
675 block = indent + "Commands:\n" \
676 + '\n'.join(lines) + "\n\n"
677 help = help.replace(indent+marker+suffix, block, 1)
680 def _gen_names_and_attrs(self):
681 # Inheritance says we have to look in class and
682 # base classes; order is not important.
684 classes = [self.__class__]
686 aclass = classes.pop(0)
688 classes = classes + list(aclass.__bases__)
689 for name in dir(aclass):
690 yield (name, getattr(aclass, name))
692 def _help_preprocess_help_list(self, help, cmdname=None):
693 marker = "${help_list}"
694 indent, indent_width = _get_indent(marker, help)
695 suffix = _get_trailing_whitespace(marker, help)
697 # Determine the additional help topics, if any.
699 token2cmdname = self._get_canonical_map()
700 for attrname, attr in self._gen_names_and_attrs():
701 if not attrname.startswith("help_"): continue
702 helpname = attrname[5:]
703 if helpname not in token2cmdname:
704 helpnames[helpname] = attr
707 linedata = [(n, a.__doc__ or "") for n, a in helpnames.items()]
710 subindent = indent + ' '*4
711 lines = _format_linedata(linedata, subindent, indent_width+4)
713 + "Additional help topics (run `%s help TOPIC'):\n" % self.name
718 help = help.replace(indent+marker+suffix, block, 1)
721 def _help_preprocess_cmd_name(self, help, cmdname=None):
722 marker = "${cmd_name}"
723 handler = self._get_cmd_handler(cmdname)
725 raise CmdlnError("cannot preprocess '%s' into help string: "
726 "could not find command handler for %r"
729 if hasattr(handler, "aliases"):
730 s += " (%s)" % (", ".join(handler.aliases))
731 help = help.replace(marker, s)
734 #TODO: this only makes sense as part of the Cmdln class.
735 # Add hooks to add help preprocessing template vars and put
736 # this one on that class.
737 def _help_preprocess_cmd_usage(self, help, cmdname=None):
738 marker = "${cmd_usage}"
739 handler = self._get_cmd_handler(cmdname)
741 raise CmdlnError("cannot preprocess '%s' into help string: "
742 "could not find command handler for %r"
744 indent, indent_width = _get_indent(marker, help)
745 suffix = _get_trailing_whitespace(marker, help)
747 # Extract the introspection bits we need.
748 func = handler.im_func
749 if func.func_defaults:
750 func_defaults = list(func.func_defaults)
753 co_argcount = func.func_code.co_argcount
754 co_varnames = func.func_code.co_varnames
755 co_flags = func.func_code.co_flags
759 # Adjust argcount for possible *args and **kwargs arguments.
760 argcount = co_argcount
761 if co_flags & CO_FLAGS_ARGS: argcount += 1
762 if co_flags & CO_FLAGS_KWARGS: argcount += 1
764 # Determine the usage string.
765 usage = "%s %s" % (self.name, cmdname)
766 if argcount <= 2: # handler ::= do_FOO(self, argv)
767 usage += " [ARGS...]"
768 elif argcount >= 3: # handler ::= do_FOO(self, subcmd, opts, ...)
769 argnames = list(co_varnames[3:argcount])
771 if co_flags & CO_FLAGS_KWARGS:
772 name = argnames.pop(-1)
774 # There is no generally accepted mechanism for passing
775 # keyword arguments from the command line. Could
776 # *perhaps* consider: arg=value arg2=value2 ...
777 warnings.warn("argument '**%s' on '%s.%s' command "
778 "handler will never get values"
779 % (name, self.__class__.__name__,
781 if co_flags & CO_FLAGS_ARGS:
782 name = argnames.pop(-1)
783 tail = "[%s...]" % name.upper()
785 func_defaults.pop(-1)
786 name = argnames.pop(-1)
787 tail = "[%s%s%s]" % (name.upper(), (tail and ' ' or ''), tail)
789 name = argnames.pop(-1)
790 tail = "%s %s" % (name.upper(), tail)
794 self.helpindent + "Usage:",
795 self.helpindent + ' '*4 + usage
797 block = '\n'.join(block_lines) + '\n\n'
799 help = help.replace(indent+marker+suffix, block, 1)
802 #TODO: this only makes sense as part of the Cmdln class.
803 # Add hooks to add help preprocessing template vars and put
804 # this one on that class.
805 def _help_preprocess_cmd_option_list(self, help, cmdname=None):
806 marker = "${cmd_option_list}"
807 handler = self._get_cmd_handler(cmdname)
809 raise CmdlnError("cannot preprocess '%s' into help string: "
810 "could not find command handler for %r"
812 indent, indent_width = _get_indent(marker, help)
813 suffix = _get_trailing_whitespace(marker, help)
814 if hasattr(handler, "optparser"):
815 # Setup formatting options and format.
816 # - Indentation of 4 is better than optparse default of 2.
817 # C.f. Damian Conway's discussion of this in Perl Best
819 handler.optparser.formatter.indent_increment = 4
820 handler.optparser.formatter.current_indent = indent_width
821 block = handler.optparser.format_option_help() + '\n'
825 help = help.replace(indent+marker+suffix, block, 1)
828 def _get_canonical_cmd_name(self, token):
829 map = self._get_canonical_map()
830 return map.get(token, None)
832 def _get_canonical_map(self):
833 """Return a mapping of available command names and aliases to
834 their canonical command name.
836 cacheattr = "_token2canonical"
837 if not hasattr(self, cacheattr):
838 # Get the list of commands and their aliases, if any.
840 cmd2funcname = {} # use a dict to strip duplicates
841 for attr in self.get_names():
842 if attr.startswith("do_"): cmdname = attr[3:]
843 elif attr.startswith("_do_"): cmdname = attr[4:]
846 cmd2funcname[cmdname] = attr
847 token2canonical[cmdname] = cmdname
848 for cmdname, funcname in cmd2funcname.items(): # add aliases
849 func = getattr(self, funcname)
850 aliases = getattr(func, "aliases", [])
851 for alias in aliases:
852 if alias in cmd2funcname:
854 warnings.warn("'%s' alias for '%s' command conflicts "
856 % (alias, cmdname, cmd2funcname[alias]))
858 token2canonical[alias] = cmdname
859 setattr(self, cacheattr, token2canonical)
860 return getattr(self, cacheattr)
862 def _get_cmd_handler(self, cmdname):
865 handler = getattr(self, 'do_' + cmdname)
866 except AttributeError:
868 # Private command handlers begin with "_do_".
869 handler = getattr(self, '_do_' + cmdname)
870 except AttributeError:
874 def _do_EOF(self, argv):
875 # Default EOF handler
876 # Note: an actual EOF is redirected to this command.
877 #TODO: separate name for this. Currently it is available from
878 # command-line. Is that okay?
879 self.stdout.write('\n')
884 # Different from cmd.Cmd: don't repeat the last command for an
889 return self.do_help(["help"])
892 #---- optparse.py extension to fix (IMO) some deficiencies
894 # See the class _OptionParserEx docstring for details.
897 class StopOptionProcessing(Exception):
898 """Indicate that option *and argument* processing should stop
899 cleanly. This is not an error condition. It is similar in spirit to
900 StopIteration. This is raised by _OptionParserEx's default "help"
901 and "version" option actions and can be raised by custom option
904 Hence the typical CmdlnOptionParser (a subclass of _OptionParserEx)
907 parser = CmdlnOptionParser(mycmd)
908 parser.add_option("-f", "--force", dest="force")
911 opts, args = parser.parse_args()
912 except StopOptionProcessing:
913 # normal termination, "--help" was probably given
917 class _OptionParserEx(optparse.OptionParser):
918 """An optparse.OptionParser that uses exceptions instead of sys.exit.
920 This class is an extension of optparse.OptionParser that differs
922 - Correct (IMO) the default OptionParser error handling to never
923 sys.exit(). Instead OptParseError exceptions are passed through.
924 - Add the StopOptionProcessing exception (a la StopIteration) to
925 indicate normal termination of option processing.
926 See StopOptionProcessing's docstring for details.
928 I'd also like to see the following in the core optparse.py, perhaps
929 as a RawOptionParser which would serve as a base class for the more
930 generally used OptionParser (that works as current):
931 - Remove the implicit addition of the -h|--help and --version
932 options. They can get in the way (e.g. if want '-?' and '-V' for
933 these as well) and it is not hard to do:
934 optparser.add_option("-h", "--help", action="help")
935 optparser.add_option("--version", action="version")
936 These are good practices, just not valid defaults if they can
939 def error(self, msg):
940 raise optparse.OptParseError(msg)
942 def exit(self, status=0, msg=None):
944 raise StopOptionProcessing(msg)
946 #TODO: don't lose status info here
947 raise optparse.OptParseError(msg)
951 #---- optparse.py-based option processing support
953 class CmdlnOptionParser(_OptionParserEx):
954 """An optparse.OptionParser class more appropriate for top-level
955 Cmdln options. For parsing of sub-command options, see
959 - disable_interspersed_args() by default, because a Cmdln instance
960 has sub-commands which may themselves have options.
961 - Redirect print_help() to the Cmdln.do_help() which is better
962 equiped to handle the "help" action.
963 - error() will raise a CmdlnUserError: OptionParse.error() is meant
964 to be called for user errors. Raising a well-known error here can
965 make error handling clearer.
966 - Also see the changes in _OptionParserEx.
968 def __init__(self, cmdln, **kwargs):
970 kwargs["prog"] = self.cmdln.name
971 _OptionParserEx.__init__(self, **kwargs)
972 self.disable_interspersed_args()
974 def print_help(self, file=None):
975 self.cmdln.onecmd(["help"])
977 def error(self, msg):
978 raise CmdlnUserError(msg)
981 class SubCmdOptionParser(_OptionParserEx):
982 def set_cmdln_info(self, cmdln, subcmd):
983 """Called by Cmdln to pass relevant info about itself needed
989 def print_help(self, file=None):
990 self.cmdln.onecmd(["help", self.subcmd])
992 def error(self, msg):
993 raise CmdlnUserError(msg)
996 def option(*args, **kwargs):
997 """Decorator to add an option to the optparser argument of a Cmdln
1001 class MyShell(cmdln.Cmdln):
1002 @cmdln.option("-f", "--force", help="force removal")
1003 def do_remove(self, subcmd, opts, *args):
1006 #XXX Is there a possible optimization for many options to not have a
1007 # large stack depth here?
1009 if not hasattr(f, "optparser"):
1010 f.optparser = SubCmdOptionParser()
1011 f.optparser.add_option(*args, **kwargs)
1016 class Cmdln(RawCmdln):
1017 """An improved (on cmd.Cmd) framework for building multi-subcommand
1018 scripts (think "svn" & "cvs") and simple shells (think "pdb" and
1025 class MySVN(cmdln.Cmdln):
1028 @cmdln.aliases('stat', 'st')
1029 @cmdln.option('-v', '--verbose', action='store_true'
1030 help='print verbose information')
1031 def do_status(self, subcmd, opts, *paths):
1032 print "handle 'svn status' command"
1036 if __name__ == "__main__":
1038 retval = shell.main()
1041 'Cmdln' extends 'RawCmdln' by providing optparse option processing
1042 integration. See this class' _dispatch_cmd() docstring and
1043 <http://trentm.com/projects/cmdln> for more information.
1045 def _dispatch_cmd(self, handler, argv):
1046 """Introspect sub-command handler signature to determine how to
1047 dispatch the command. The raw handler provided by the base
1048 'RawCmdln' class is still supported:
1050 def do_foo(self, argv):
1051 # 'argv' is the vector of command line args, argv[0] is
1052 # the command name itself (i.e. "foo" or an alias)
1055 In addition, if the handler has more than 2 arguments option
1056 processing is automatically done (using optparse):
1058 @cmdln.option('-v', '--verbose', action='store_true')
1059 def do_bar(self, subcmd, opts, *args):
1060 # subcmd = <"bar" or an alias>
1061 # opts = <an optparse.Values instance>
1063 print "lots of debugging output..."
1064 # args = <tuple of arguments>
1068 TODO: explain that "*args" can be other signatures as well.
1070 The `cmdln.option` decorator corresponds to an `add_option()`
1071 method call on an `optparse.OptionParser` instance.
1073 You can declare a specific number of arguments:
1075 @cmdln.option('-v', '--verbose', action='store_true')
1076 def do_bar2(self, subcmd, opts, bar_one, bar_two):
1079 and an appropriate error message will be raised/printed if the
1080 command is called with a different number of args.
1082 co_argcount = handler.im_func.func_code.co_argcount
1083 if co_argcount == 2: # handler ::= do_foo(self, argv)
1084 return handler(argv)
1085 elif co_argcount >= 3: # handler ::= do_foo(self, subcmd, opts, ...)
1087 optparser = handler.optparser
1088 except AttributeError:
1089 optparser = handler.im_func.optparser = SubCmdOptionParser()
1090 assert isinstance(optparser, SubCmdOptionParser)
1091 optparser.set_cmdln_info(self, argv[0])
1093 opts, args = optparser.parse_args(argv[1:])
1094 except StopOptionProcessing:
1095 #TODO: this doesn't really fly for a replacement of
1096 # optparse.py behaviour, does it?
1097 return 0 # Normal command termination
1100 return handler(argv[0], opts, *args)
1101 except TypeError, ex:
1102 # Some TypeError's are user errors:
1103 # do_foo() takes at least 4 arguments (3 given)
1104 # do_foo() takes at most 5 arguments (6 given)
1105 # do_foo() takes exactly 5 arguments (6 given)
1106 # Raise CmdlnUserError for these with a suitably
1107 # massaged error message.
1109 tb = sys.exc_info()[2] # the traceback object
1110 if tb.tb_next is not None:
1111 # If the traceback is more than one level deep, then the
1112 # TypeError do *not* happen on the "handler(...)" call
1113 # above. In that we don't want to handle it specially
1114 # here: it would falsely mask deeper code errors.
1117 match = _INCORRECT_NUM_ARGS_RE.search(msg)
1119 msg = list(match.groups())
1120 msg[1] = int(msg[1]) - 3
1122 msg[2] = msg[2].replace("arguments", "argument")
1123 msg[3] = int(msg[3]) - 3
1124 msg = ''.join(map(str, msg))
1125 raise CmdlnUserError(msg)
1129 raise CmdlnError("incorrect argcount for %s(): takes %d, must "
1130 "take 2 for 'argv' signature or 3+ for 'opts' "
1131 "signature" % (handler.__name__, co_argcount))
1135 #---- internal support functions
1137 def _format_linedata(linedata, indent, indent_width):
1138 """Format specific linedata into a pleasant layout.
1140 "linedata" is a list of 2-tuples of the form:
1141 (<item-display-string>, <item-docstring>)
1142 "indent" is a string to use for one level of indentation
1143 "indent_width" is a number of columns by which the
1144 formatted data will be indented when printed.
1146 The <item-display-string> column is held to 15 columns.
1149 WIDTH = 78 - indent_width
1151 NAME_WIDTH_LOWER_BOUND = 13
1152 NAME_WIDTH_UPPER_BOUND = 16
1153 NAME_WIDTH = max([len(s) for s,d in linedata])
1154 if NAME_WIDTH < NAME_WIDTH_LOWER_BOUND:
1155 NAME_WIDTH = NAME_WIDTH_LOWER_BOUND
1157 NAME_WIDTH = NAME_WIDTH_UPPER_BOUND
1159 DOC_WIDTH = WIDTH - NAME_WIDTH - SPACING
1160 for namestr, doc in linedata:
1161 line = indent + namestr
1162 if len(namestr) <= NAME_WIDTH:
1163 line += ' ' * (NAME_WIDTH + SPACING - len(namestr))
1166 line = indent + ' ' * (NAME_WIDTH + SPACING)
1167 line += _summarize_doc(doc, DOC_WIDTH)
1168 lines.append(line.rstrip())
1171 def _summarize_doc(doc, length=60):
1172 r"""Parse out a short one line summary from the given doclines.
1174 "doc" is the doc string to summarize.
1175 "length" is the max length for the summary
1177 >>> _summarize_doc("this function does this")
1178 'this function does this'
1179 >>> _summarize_doc("this function does this", 10)
1181 >>> _summarize_doc("this function does this\nand that")
1182 'this function does this and that'
1183 >>> _summarize_doc("this function does this\n\nand that")
1184 'this function does this'
1189 assert length > 3, "length <= 3 is absurdly short for a doc summary"
1190 doclines = doc.strip().splitlines(0)
1195 for i, line in enumerate(doclines):
1196 stripped = line.strip()
1199 summlines.append(stripped)
1200 if len(''.join(summlines)) >= length:
1203 summary = ' '.join(summlines)
1204 if len(summary) > length:
1205 summary = summary[:length-3] + "..."
1209 def line2argv(line):
1210 r"""Parse the given line into an argument vector.
1212 "line" is the line of input to parse.
1214 This may get niggly when dealing with quoting and escaping. The
1215 current state of this parsing may not be completely thorough/correct
1218 >>> from cmdln import line2argv
1219 >>> line2argv("foo")
1221 >>> line2argv("foo bar")
1223 >>> line2argv("foo bar ")
1225 >>> line2argv(" foo bar")
1230 >>> line2argv("'foo bar'")
1232 >>> line2argv('"foo bar"')
1234 >>> line2argv(r'"foo\"bar"')
1236 >>> line2argv("'foo bar' spam")
1238 >>> line2argv("'foo 'bar spam")
1241 >>> line2argv('some\tsimple\ttests')
1242 ['some', 'simple', 'tests']
1243 >>> line2argv('a "more complex" test')
1244 ['a', 'more complex', 'test']
1245 >>> line2argv('a more="complex test of " quotes')
1246 ['a', 'more=complex test of ', 'quotes']
1247 >>> line2argv('a more" complex test of " quotes')
1248 ['a', 'more complex test of ', 'quotes']
1249 >>> line2argv('an "embedded \\"quote\\""')
1250 ['an', 'embedded "quote"']
1253 >>> line2argv('foo bar C:\\')
1254 ['foo', 'bar', 'C:\\']
1256 # Komodo change 127581
1257 >>> line2argv(r'"\test\slash" "foo bar" "foo\"bar"')
1258 ['\\test\\slash', 'foo bar', 'foo"bar']
1260 # Komodo change 127629
1261 >>> if sys.platform == "win32":
1262 ... line2argv(r'\foo\bar') == ['\\foo\\bar']
1263 ... line2argv(r'\\foo\\bar') == ['\\\\foo\\\\bar']
1264 ... line2argv('"foo') == ['foo']
1266 ... line2argv(r'\foo\bar') == ['foobar']
1267 ... line2argv(r'\\foo\\bar') == ['\\foo\\bar']
1269 ... line2argv('"foo')
1270 ... except ValueError, ex:
1271 ... "not terminated" in str(ex)
1280 arg = None # the current argument being parsed
1284 if i >= len(line): break
1287 if ch == "\\" and i+1 < len(line):
1288 # escaped char always added to arg, regardless of state
1289 if arg is None: arg = ""
1290 if (sys.platform == "win32"
1291 or state in ("double-quoted", "single-quoted")
1292 ) and line[i+1] not in tuple('"\''):
1298 if state == "single-quoted":
1303 elif state == "double-quoted":
1308 elif state == "default":
1310 if arg is None: arg = ""
1311 state = "double-quoted"
1313 if arg is None: arg = ""
1314 state = "single-quoted"
1315 elif ch in string.whitespace:
1320 if arg is None: arg = ""
1324 if not sys.platform == "win32" and state != "default":
1325 raise ValueError("command line is not terminated: unfinished %s "
1330 def argv2line(argv):
1331 r"""Put together the given argument vector into a command line.
1333 "argv" is the argument vector to process.
1335 >>> from cmdln import argv2line
1336 >>> argv2line(['foo'])
1338 >>> argv2line(['foo', 'bar'])
1340 >>> argv2line(['foo', 'bar baz'])
1342 >>> argv2line(['foo"bar'])
1344 >>> print argv2line(['foo" bar'])
1346 >>> print argv2line(["foo' bar"])
1348 >>> argv2line(["foo'bar"])
1353 if ' ' in arg and '"' not in arg:
1355 elif ' ' in arg and "'" not in arg:
1358 arg = arg.replace('"', r'\"')
1360 escapedArgs.append(arg)
1361 return ' '.join(escapedArgs)
1364 # Recipe: dedent (0.1) in /Users/trentm/tm/recipes/cookbook
1365 def _dedentlines(lines, tabsize=8, skip_first_line=False):
1366 """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines
1368 "lines" is a list of lines to dedent.
1369 "tabsize" is the tab width to use for indent width calculations.
1370 "skip_first_line" is a boolean indicating if the first line should
1371 be skipped for calculating the indent width and for dedenting.
1372 This is sometimes useful for docstrings and similar.
1374 Same as dedent() except operates on a sequence of lines. Note: the
1375 lines list is modified **in-place**.
1379 print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
1380 % (tabsize, skip_first_line)
1383 for i, line in enumerate(lines):
1384 if i == 0 and skip_first_line: continue
1390 indent += tabsize - (indent % tabsize)
1392 continue # skip all-whitespace lines
1396 continue # skip all-whitespace lines
1397 if DEBUG: print "dedent: indent=%d: %r" % (indent, line)
1401 margin = min(margin, indent)
1402 if DEBUG: print "dedent: margin=%r" % margin
1404 if margin is not None and margin > 0:
1405 for i, line in enumerate(lines):
1406 if i == 0 and skip_first_line: continue
1408 for j, ch in enumerate(line):
1412 removed += tabsize - (removed % tabsize)
1414 if DEBUG: print "dedent: %r: EOL -> strip up to EOL" % line
1415 lines[i] = lines[i][j:]
1418 raise ValueError("unexpected non-whitespace char %r in "
1419 "line %r while removing %d-space margin"
1420 % (ch, line, margin))
1422 print "dedent: %r: %r -> removed %d/%d"\
1423 % (line, ch, removed, margin)
1424 if removed == margin:
1425 lines[i] = lines[i][j+1:]
1427 elif removed > margin:
1428 lines[i] = ' '*(removed-margin) + lines[i][j+1:]
1432 def _dedent(text, tabsize=8, skip_first_line=False):
1433 """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text
1435 "text" is the text to dedent.
1436 "tabsize" is the tab width to use for indent width calculations.
1437 "skip_first_line" is a boolean indicating if the first line should
1438 be skipped for calculating the indent width and for dedenting.
1439 This is sometimes useful for docstrings and similar.
1441 textwrap.dedent(s), but don't expand tabs to spaces
1443 lines = text.splitlines(1)
1444 _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line)
1445 return ''.join(lines)
1448 def _get_indent(marker, s, tab_width=8):
1449 """_get_indent(marker, s, tab_width=8) ->
1450 (<indentation-of-'marker'>, <indentation-width>)"""
1451 # Figure out how much the marker is indented.
1452 INDENT_CHARS = tuple(' \t')
1453 start = s.index(marker)
1456 if s[i-1] not in INDENT_CHARS:
1465 indent_width += tab_width - (indent_width % tab_width)
1466 return indent, indent_width
1468 def _get_trailing_whitespace(marker, s):
1469 """Return the whitespace content trailing the given 'marker' in string 's',
1470 up to and including a newline.
1473 start = s.index(marker) + len(marker)
1478 elif s[i] in '\r\n':
1480 if s[i] == '\r' and i+1 < len(s) and s[i+1] == '\n':
1490 #---- bash completion support
1491 # Note: This is still experimental. I expect to change this
1494 # To get Bash completion for a cmdln.Cmdln class, run the following
1496 # $ complete -C 'python -m cmdln /path/to/script.py CmdlnClass' cmdname
1498 # $ complete -C 'python -m cmdln ~/bin/svn.py SVN' svn
1500 #TODO: Simplify the above so don't have to given path to script (try to
1501 # find it on PATH, if possible). Could also make class name
1502 # optional if there is only one in the module (common case).
1504 if __name__ == "__main__" and len(sys.argv) == 6:
1506 return # no-op, comment out for debugging
1507 from os.path import expanduser
1508 fout = open(expanduser("~/tmp/bashcpln.log"), 'a')
1509 fout.write(str(s) + '\n')
1512 # Recipe: module_from_path (1.0.1+)
1513 def _module_from_path(path):
1515 path = os.path.expanduser(path)
1516 dir = os.path.dirname(path) or os.curdir
1517 name = os.path.splitext(os.path.basename(path))[0]
1518 sys.path.insert(0, dir)
1520 iinfo = imp.find_module(name, [dir])
1521 return imp.load_module(name, *iinfo)
1523 sys.path.remove(dir)
1525 def _get_bash_cplns(script_path, class_name, cmd_name,
1526 token, preceding_token):
1528 _log('get_cplns(%r, %r, %r, %r, %r)'
1529 % (script_path, class_name, cmd_name, token, preceding_token))
1530 comp_line = os.environ["COMP_LINE"]
1531 comp_point = int(os.environ["COMP_POINT"])
1532 _log("COMP_LINE: %r" % comp_line)
1533 _log("COMP_POINT: %r" % comp_point)
1536 script = _module_from_path(script_path)
1537 except ImportError, ex:
1538 _log("error importing `%s': %s" % (script_path, ex))
1540 shell = getattr(script, class_name)()
1541 cmd_map = shell._get_canonical_map()
1544 # Determine if completing the sub-command name.
1545 parts = comp_line[:comp_point].split(None, 1)
1547 if len(parts) == 1 or not (' ' in parts[1] or '\t' in parts[1]):
1548 #TODO: if parts[1].startswith('-'): handle top-level opts
1549 _log("complete sub-command names")
1551 for name, canon_name in cmd_map.items():
1552 if name.startswith(token):
1553 matches[name] = canon_name
1556 elif len(matches) == 1:
1557 return matches.keys()
1558 elif len(set(matches.values())) == 1:
1559 return [matches.values()[0]]
1561 return matches.keys()
1563 # Otherwise, complete options for the given sub-command.
1564 #TODO: refine this so it does the right thing with option args
1565 if token.startswith('-'):
1566 cmd_name = comp_line.split(None, 2)[1]
1568 cmd_canon_name = cmd_map[cmd_name]
1571 handler = shell._get_cmd_handler(cmd_canon_name)
1572 optparser = getattr(handler, "optparser", None)
1573 if optparser is None:
1574 optparser = SubCmdOptionParser()
1576 for option in optparser.option_list:
1577 for opt_str in option._short_opts + option._long_opts:
1578 if opt_str.startswith(token):
1579 opt_strs.append(opt_str)
1584 for cpln in _get_bash_cplns(*sys.argv[1:]):