Revision 1 as of 2002-09-20 10:31:25

Clear message

The cmd module provide a powerfull tool for creating command line oriented user interface.

Command line advocacy

In theses days of graphical user interface, a command line interpreter can seems antique. I agree that GUI are often more friendly (and in fact I'm happy to have something other than "ed" to create this document). But a command line interface can have several advantages:

And even if you plan to create a GUI software, it's often good to start with a text interface. This will allow you to focus on the applicative logic independently of the interface. This is often a good way to create modular software.

cmd module basics

The module define only one class: the Cmd class. Creating a command line interpreter is done by sub-classing the cmd.Cmd class.

Creating a command

The main goal of an interpreter is to respond to commands. A command is the first part of a text line entered at the interpreter prompt. This part is defined as the longer string of characters contained in the identchars member. By default identchars contain non accented letters, digits and the underscore symbol. The end of the line is the command parameters.

Commands handling is really easy: if you want to define the command spam, you only have to define the do_spam method in your derived class.

parameters

The do_xxx method should only take one extra parameter. This parameter correspond to the part of the string entered by the user after the command name. The do_xxx job is to parse this string and to find the command parameters values. Python provide many helpful tools to parse this string but this is quite out of the scope of his how-to.

errors

The interpreter use the following format to signal errors:

*** <error description>: <additional parameters>

It's generally a good idea to use the same format for application errors.

return value

In the most common case: commands shouldn't return a value. The exception is when you want to exit the interpreter loop: any command that return a true value stop the interpreter.

sample

The following function define a command which take two numerical arguments and print the result of the addition.

def do_add(self,s): 
    l = s.split() 
    if len(l)!=2: 
       print "*** invalid number of arguments" 
       return 
    try: 
       l = [int(i) for i in l] 
    except ValueError: 
       print "*** arguments should be numbers" 
       return 
    print l[0]+l[1]

Now if you run the interpreter, you will have:

(Cmd) add 4 
*** invalid number of arguments 
(Cmd) add 5 4 
9

Help

Help support is another strength of the cmd module. You can provide documentation for the xxx command by defining the help_xxx method. For the add command, you could for sample define:

def help_add(self):
    print 'add two integral numbers'

And then, in the interactive interpreter you will have:

(Cmd) help add 

add two integral numbers

You can also define help for topics that are not related to commands.

def help_introduction(self):
    print 'introduction'
    print 'a good place for a tutorial'

The interpreter understand the ? character as a shortcut for the help command.

Completion

Completion is a very interesting feature: when the user press the TAB key, the interpreter will try to complete the command or propose several alternatives. Completion will be available only if the computer support the readline module. You can also disable completion by passing the None value to the completekey attribute of the Cmd class constructor.

The interpreter is able to process completion for commands names, but for commands arguments you will have to help it. For the command xxx, this is done by defining a complete_xxx method. For sample, if you have defined a color command, completion method for this command could be:

_AVAILABLE_COLORS = ('blue', 'green', 'yellow', 'red', 'black')

def complete_color(self, text, line, begidx, endidx):
    return [i for i in _AVAILABLE_COLORS if i.startswith(text)]

The complete_xxx method take four arguments:

And should return a list (possibly empty) of strings representing the possible completions. The arguments begidx and endidx are useful when completion depend of the position of the argument.

Starting the interpreter

Once you have defined your own interpreter class, the only thing to do is to create an instance and to call the mainloop method:

interpreter = MyCmdInterpreter()
interpreter.mainloop()

Interface customization

The cmd module provide several hooks to change the behavior of the interpreter. You should notice that your users won't necessary thanks you to deviate from the standard behavior.

Empty lines

By default when an empty line is entered, the last command is repeated. You can change this behavior by overriding the emptyline method. For sample to disable the repetition of the last command:

def emptyline(self):
    pass

Help summary

When the help command is called without arguments, it print a summary of all the documentation topics:

(Cmd) help

Documented commands (type help <topic>):
======================================== 
EOF add exit macro shell test 

Miscellaneous help topics:
========================== 
intro 

Undocumented commands: 
====================== 
line help  

(Cmd) 

This summary is separated in three parts:

You can customize this screen, with several data members:

Introduction message

At the startup, the interpreter print the self.intro string. This string can be overridden via an optional argument to the cmdloop() method.

Advanced material

Defaults handling

Theses methods have the same parameters than the do_xxx and complete_xxx methods.

Nested interpreters

If your program become complex, or if your data structure is hierarchical, it can be interesting to define nested interpreters (calling an interpreter inside an other interpreter). In that case, I like having prompt like:

(Cmd) test
(Cmd:Test) exit
(Cmd)

You can do this by changing the prompt attribute of the nested interpreter:

def do_test(self, s):
    i = TestCmd()
    i.prompt = self.prompt[:-1]+':Test)'
    i.cmdloop()

Note that it can be a better practice to do this in the constructor of the nested interpreter.

Sometime, it can be useful to have more directive interaction sessions with the users. The Cmd class allow you to use the print and raw_input functions without problems.

def do_hello(self, s):
    if s=='':
        s = raw_input('Your name please: ')
    print 'Hello',s

FIXME: How to change completion behavior of raw_input?

The interpreter loop

At the start of the interpreter loop the preloop method is called. At the end of the loop this is the postloop method. This methods take no arguments and shouldn't return any value. The following show how to make the interpreter more polite:

class polite_cmd(cmd.Cmd,object): 
    def preloop(self): 
        print 'Hello' 
        super(polite_cmd,self).preloop() 

    def postloop(self): 
        print 'Goodbye'
        super(polite_cmd,self).postloop() 

Command processing

When a command line is processed, several methods are called

The precmd and postcmd methods do nothing by default and was only intended as hook for derived class. In fact with the Python 2.2 super method, they are useless because anything can be done by overriding the onecmd method. So you should probably avoid to use this two hooks.

class dollar_cmd(cmd.Cmd, object):

    def onecmd(self, line):
        ''' define $ as a shortcut for the dollar command
            and ask for confirmation when the interpreter exit'''
        if line[:1] == '$':
            line = 'dollar '+line[1:]
        r = super (dollar_cmd, self).onecmd(line)
        if r:
            r = raw_input('really exit ?(y/n):')=='y'
        return r

But, if you want to simulate an interpreter entry you should call this three methods in the good order. For sample if you want to print the help message at startup:

interpreter = MyCmdInterpreter()
l = interpreter.precmd('help')
r = interpreter.onecmd(l)
r = interpreter.postcmd(r, l)
if not r:
    interpreter.mainloop()

This will prevent you from troubles if you want later to inherit for a class which would have modified the hooks.

Creating components

One other strength of the cmd module is that it handle multiple inheritance. That mean that you can create helper class intended to provide additional features.

Shell access

import os

class shell_cmd(cmd.Cmd,object): 

    def do_shell(self, s): 
        os.system(s) 

    def help_shell(self): 
        print "execute shell commands"

By deriving from this class, you will be able to execute any shell command:

(Cmd) shell date 

Thu Sep 9 08:57:14 CEST 2002 

(Cmd) ! ls /usr/local/lib/python2.2/config 

Makefile Setup.config config.c install-sh makesetup Setup
Setup.local config.c.in libpython2.2.a python.o

By the way the cmd module understand the ! character as a shortcut for the shell command.

Exit

class exit_cmd(cmd.Cmd,object): 

    def can_exit(self): 
        return True 

    def onecmd(self, line):
        r = super (exit_cmd, self).onecmd(line)
        if r and (self.can_exit() or 
           raw_input('exit anyway ? (yes/no):')=='yes'): 
             return True
        return False 

    def do_exit(self, s):
        return True

    def help_exit(self): 
        print "Exit the interpreter."
        print "You can also use the Ctrl-D shortcut." 

    do_EOF = do_exit 
    help_EOF= help_exit

This class provide the exit command to abort the interpreter. You can protect exit by overriding the can_exit method.

Gluing all together

Now with a class that inherit both from exit_cmd and shell_cmd you will be able to define an interpreter that understand the shell and exit commands.

References

[http://www.python.org/doc/current/lib/Cmd-objects.html cmd module reference]

Unable to edit the page? See the FrontPage for instructions.