Enhanced Cmd class for command line interpreters.

Gribouillis 1 Tallied Votes 1K Views Share

This snippet defines a class OptCmd which adds line and options parsing capabilities to the python standard lib's cmd.Cmd class. It allows you to quickly write an extensible command line interpreter.

To use this module, you will need this other code snippet, saved as a module named 'argv.py'.

Enjoy the OptCmd class !

TrustyTony commented: neat +12
# optcmd.py (this version is for python 2 only).
#
# This code has been placed in the public domain.
# written by Gribouillis for the python forum at www.daniweb.com
# date: 02/2010.
# source: cmdsmart.tm

"""
This module optcmd defines a enhanced subclass OptCmd of the
python standard library class cmd.Cmd, which adds line parsing
capabilities to the Cmd class.

IMPORTANT NOTE this module needs the module argv.py from
  http://www.daniweb.com/code/snippet234768.html

In order to define command interpreter by subclassing cmd.Cmd, one
needs to define do_* like methods, as described in the cmd module's
documentation. For example to define a command 'translate', you would
write a method

    def do_translate(self, line):
      etc

Every such method takes an argument line which is a string containing
the command line entered by the user. The Cmd class does not at all
process this line of input.

By subclassing this module's OptCmd class, you would write instead
a method

    def do_translate(self, infile, outfile, verbose=False):
      etc

with meaningful arguments, and if the user enters a line like

  (cmd) translate myfile.hge myfile.oiu -v

the interpreter parses the line and calls do_translate with the arguments

  instance.do_translate('myfile.hge', 'myfile.oiu', True)

The arguments with default values (like verbose above) are interpreted
as command options by the parser, and the first letter of the option name
is used as a short option (so that translate can be called with the
long option --verbose or the short one -v).

Options with a default value of False (like verbose above) dont take
values on the command line, and a value of True or False is passed
depending of -v being present on the command line or not.

Options with any other default value (like None for example) need to
be given a value if they appear on the command line. For example

  (cmd) translate --language english

if we have a default value argument language = None in do_translate.

The do_* methods in class OptCmd accept arguments of the form

   do_thing(self, x, y, z)
   do_thing(self, x, y, z, a=value, b=value)
   do_thing(self, x, y, z, a=value, b=value, *args)

keywords arguments **kwd are not allowed.

"""


from cmd import Cmd 
import inspect 
from argv import buildargv 
import optparse 
import types 
import re 
import traceback 
import sys 

class ParseError (Exception ):
  pass 

class OptError (ParseError ):
  pass 

class OptParser (optparse .OptionParser ):
  def __init__ (self ):
    optparse .OptionParser .__init__ (self )
    self .set_usage (optparse .SUPPRESS_USAGE )
  def error (self ,message ):
    raise OptError (message )
  def exit (self ):
    raise OptError ("")

class DoFunction (object ):
  _doc_re =re .compile ("^ *(?=[^ \n])",re .M )

  def __init__ (self ,name ,target_function ):
    self .__name__ =name 
    if target_function .__doc__ is None :
      self .__doc__ =None 
    else :
      self .__doc__ =self .unindent (target_function .__doc__ )
    self .target_function =target_function 
    args ,varargs ,varkwd ,defaults =inspect .getargspec (target_function )
    args =args [1 :]
    self .option_parser =OptParser ()
    if defaults is not None :
      n =len (defaults )
      args ,optargs =args [:-n ],args [-n :]
    else :
      optargs =()
    self .fixed_args =tuple (args )
    self .opt_args =tuple (optargs )
    self .var_args =varargs 
    for i ,opt in enumerate (optargs ):
      option_names =["-"+opt [0 ]]
      if len (opt )>1 :
        option_names .append ("--"+opt )
      option_parameters =dict (dest =opt ,default =defaults [i ])
      if defaults [i ]is False :
        option_parameters ["action"]="store_true"
      else :
        option_parameters ["action"]="store"
      self .option_parser .add_option (*option_names ,**option_parameters )

  def unindent (self ,docstring ):
    indent =min (len (x )for x in self ._doc_re .findall (docstring ))
    return "\n".join (s [indent :]for s in docstring .split ("\n"))

  def __call__ (self ,optcmd ,line ):
    argv =buildargv (line )
    try :
      opts ,args =self .option_parser .parse_args (argv )
      n =len (self .fixed_args )
      if len (args )<n or (len (args )>n and not self .var_args ):
        raise ParseError ("Invalid number of arguments, expected %s."
        %str (self .fixed_args ))
      values =list (args [:n ])
      values .extend (getattr (opts ,k )for k in self .opt_args )
      if self .var_args :
        values .extend (args [n :])
      return self .target_function (optcmd ,*values )
    except ParseError :
      optcmd .handle_parse_error (self ,line )
    except Exception :
      optcmd .handle_callback_error (self )

  def __get__ (self ,obj ,objtype =None ):

    return types .MethodType (self ,obj ,objtype )


class DeclarativeMeta (type ):
  "This class was taken from http://snippets.dzone.com/posts/show/779"
  def __new__ (meta ,class_name ,bases ,new_attrs ):
    cls =type .__new__ (meta ,class_name ,bases ,new_attrs )
    if new_attrs .has_key ('__classinit__'):
      cls .__classinit__ =staticmethod (cls .__classinit__ .im_func )
    cls .__classinit__ (cls ,new_attrs )
    return cls 

class OptCmd (object ,Cmd ):
  __metaclass__ =DeclarativeMeta 

  def __classinit__ (cls ,new_attrs ):
    for k in (k for k in new_attrs if k .startswith ("do_")):


#name = "do_" + k[4:]
      name =k 
      setattr (cls ,name ,DoFunction (name ,new_attrs [k ]))

  def __init__ (self ):
    Cmd .__init__ (self )


  def handle_parse_error (self ,func ,line ):
    traceback .print_exc ()

  def handle_callback_error (self ,func ):
    traceback .print_exc ()

  def do_quit (self ):
    """
    usage: quit
      exit the interpreter.
    """
    return True 


class ExampleCmd (OptCmd ):

  def emptyline (self ):
    pass 

  def do_boom (self ,name ,verbose =False ,output =None ):
    """
    usage: boom [options] name

    options:
      -v --verbose: print lots of stuff.
      -o --output [file]: specify an output file.
    """
    print ("hello from do_boom%s"%str ((name ,verbose ,output )))

  def do_foo (self ):
    pass 

if __name__ =="__main__":
  inter =ExampleCmd ()
  inter .cmdloop ("             THIS is the EXAMPLE INTERPRETER\n")