Features requiring application changes

Multiline commands

Command input may span multiple lines for the commands whose names are listed in the parameter app.multiline_commands. These commands will be executed only after the user has entered a terminator. By default, the command terminator is ;; replacing or appending to the list app.terminators allows different terminators. A blank line is always considered a command terminator (cannot be overridden).

In multiline commands, output redirection characters like > and | are part of the command arguments unless they appear after the terminator.

Parsed statements

cmd2 passes arg to a do_ method (or default) as a Statement, a subclass of string that includes many attributes of the parsed input:

command
Name of the command called
args
The arguments to the command with output redirection or piping to shell commands removed
command_and_args
A string of just the command and the arguments, with output redirection or piping to shell commands removed
argv
A list of arguments a-la sys.argv, including the command as argv[0] and the subsequent arguments as additional items in the list. Quotes around arguments will be stripped as will any output redirection or piping portions of the command
raw
Full input exactly as typed.
terminator
Character used to end a multiline command

If Statement does not contain an attribute, querying for it will return None.

(Getting arg as a Statement is technically “free”, in that it requires no application changes from the cmd standard, but there will be no result unless you change your application to use any of the additional attributes.)

Environment parameters

Your application can define user-settable parameters which your code can reference. First create a class attribute with the default value. Then update the settable dictionary with your setting name and a short description before you initialize the superclass. Here’s an example, from examples/environment.py:

#!/usr/bin/env python
# coding=utf-8
"""
A sample application for cmd2 demonstrating customized environment parameters
"""

import cmd2


class EnvironmentApp(cmd2.Cmd):
    """ Example cmd2 application. """

    degrees_c = 22
    sunny = False

    def __init__(self):
        super().__init__()
        self.settable.update({'degrees_c': 'Temperature in Celsius'})
        self.settable.update({'sunny': 'Is it sunny outside?'})

    def do_sunbathe(self, arg):
        if self.degrees_c < 20:
            result = "It's {} C - are you a penguin?".format(self.degrees_c)
        elif not self.sunny:
            result = 'Too dim.'
        else:
            result = 'UV is bad for your skin.'
        self.poutput(result)

    def _onchange_degrees_c(self, old, new):
        # if it's over 40C, it's gotta be sunny, right?
        if new > 40:
            self.sunny = True


if __name__ == '__main__':
    c = EnvironmentApp()
    c.cmdloop()

If you want to be notified when a setting changes (as we do above), then define a method _onchange_{setting}(). This method will be called after the user changes a setting, and will receive both the old value and the new value.

(Cmd) set --long | grep sunny
sunny: False                # Is it sunny outside?
(Cmd) set --long | grep degrees
degrees_c: 22               # Temperature in Celsius
(Cmd) sunbathe
Too dim.
(Cmd) set degrees_c 41
degrees_c - was: 22
now: 41
(Cmd) set sunny
sunny: True
(Cmd) sunbathe
UV is bad for your skin.
(Cmd) set degrees_c 13
degrees_c - was: 41
now: 13
(Cmd) sunbathe
It's 13 C - are you a penguin?

Commands with flags

All do_ methods are responsible for interpreting the arguments passed to them. However, cmd2 lets a do_ methods accept Unix-style flags. It uses argparse to parse the flags, and they work the same way as for that module.

cmd2 defines a few decorators which change the behavior of how arguments get parsed for and passed to a do_ method. See the section Argument Processing for more information.

poutput, pfeedback, perror, ppaged

Standard cmd applications produce their output with self.stdout.write('output') (or with print, but print decreases output flexibility). cmd2 applications can use self.poutput('output'), self.pfeedback('message'), self.perror('errmsg'), and self.ppaged('text') instead. These methods have these advantages:

  • Handle output redirection to file and/or pipe appropriately
  • More concise
  • Option to display long output using a pager via ppaged()
Cmd.poutput(msg: Any, end: str = '\n', color: str = '') → None

Smarter self.stdout.write(); color aware and adds newline of not present.

Also handles BrokenPipeError exceptions for when a commands’s output has been piped to another process and that process terminates before the cmd2 command is finished executing.

Parameters:
  • msg – message to print to current stdout (anything convertible to a str with ‘{}’.format() is OK)
  • end – (optional) string appended after the end of the message if not already present, default a newline
  • color – (optional) color escape to output this message with
Cmd.perror(err: Union[str, Exception], traceback_war: bool = True, err_color: str = '\x1b[91m', war_color: str = '\x1b[93m') → None

Print error message to sys.stderr and if debug is true, print an exception Traceback if one exists.

Parameters:
  • err – an Exception or error message to print out
  • traceback_war – (optional) if True, print a message to let user know they can enable debug
  • err_color – (optional) color escape to output error with
  • war_color – (optional) color escape to output warning with
Cmd.pfeedback(msg: str) → None

For printing nonessential feedback. Can be silenced with quiet. Inclusion in redirected output is controlled by feedback_to_output.

Cmd.ppaged(msg: str, end: str = '\n', chop: bool = False) → None

Print output using a pager if it would go off screen and stdout isn’t currently being redirected.

Never uses a pager inside of a script (Python or text) or when output is being redirected or piped or when stdout or stdin are not a fully functional terminal.

Parameters:
  • msg – message to print to current stdout (anything convertible to a str with ‘{}’.format() is OK)
  • end – string appended after the end of the message if not already present, default a newline
  • chop
    True -> causes lines longer than the screen width to be chopped (truncated) rather than wrapped
    • truncated text is still accessible by scrolling with the right & left arrow keys
    • chopping is ideal for displaying wide tabular data as is done in utilities like pgcli
    False -> causes lines longer than the screen width to wrap to the next line
    • wrapping is ideal when you want to keep users from having to use horizontal scrolling

WARNING: On Windows, the text always wraps regardless of what the chop argument is set to

Colored Output

The output methods in the previous section all honor the colors setting, which has three possible values:

Never
poutput(), pfeedback(), and ppaged() strip all ANSI escape sequences which instruct the terminal to colorize output
Terminal
(the default value) poutput(), pfeedback(), and ppaged() do not strip any ANSI escape sequences when the output is a terminal, but if the output is a pipe or a file the escape sequences are stripped. If you want colorized output you must add ANSI escape sequences, preferably using some python color library like plumbum.colors, colorama, blessings, or termcolor.
Always
poutput(), pfeedback(), and ppaged() never strip ANSI escape sequences, regardless of the output destination

Suppressing non-essential output

The quiet setting controls whether self.pfeedback() actually produces any output. If quiet is False, then the output will be produced. If quiet is True, no output will be produced.

This makes self.pfeedback() useful for non-essential output like status messages. Users can control whether they would like to see these messages by changing the value of the quiet setting.

select

Presents numbered options to user, as bash select.

app.select is called from within a method (not by the user directly; it is app.select, not app.do_select).

Cmd.select(opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], prompt: str = 'Your choice? ') → str

Presents a numbered menu to the user. Modeled after the bash shell’s SELECT. Returns the item chosen.

Argument opts can be:

a single string -> will be split into one-word options
a list of strings -> will be offered as options
a list of tuples -> interpreted as (value, text), so that the return value can differ from the text advertised to the user
def do_eat(self, arg):
    sauce = self.select('sweet salty', 'Sauce? ')
    result = '{food} with {sauce} sauce, yum!'
    result = result.format(food=arg, sauce=sauce)
    self.stdout.write(result + '\n')
(Cmd) eat wheaties
    1. sweet
    2. salty
Sauce? 2
wheaties with salty sauce, yum!

Exit code to shell

The self.exit_code attribute of your cmd2 application controls what exit code is sent to the shell when your application exits from cmdloop().

Asynchronous Feedback

cmd2 provides two functions to provide asynchronous feedback to the user without interfering with the command line. This means the feedback is provided to the user when they are still entering text at the prompt. To use this functionality, the application must be running in a terminal that supports VT100 control characters and readline. Linux, Mac, and Windows 10 and greater all support these.

async_alert()
Used to display an important message to the user while they are at the prompt in between commands. To the user it appears as if an alert message is printed above the prompt and their current input text and cursor location is left alone.
async_update_prompt()
Updates the prompt while the user is still typing at it. This is good for alerting the user to system changes dynamically in between commands. For instance you could alter the color of the prompt to indicate a system status or increase a counter to report an event.

cmd2 also provides a function to change the title of the terminal window. This feature requires the application be running in a terminal that supports VT100 control characters. Linux, Mac, and Windows 10 and greater all support these.

set_window_title()
Sets the terminal window title

The easiest way to understand these functions is to see the AsyncPrinting example for a demonstration.

Grouping Commands

By default, the help command displays:

Documented commands (type help <topic>):
========================================
alias    findleakers  pyscript    sessions             status       vminfo
config   help         quit        set                  stop         which
connect  history      redeploy    shell                thread_dump
deploy   list         resources   shortcuts            unalias
edit     load         restart     sslconnectorciphers  undeploy
expire   py           serverinfo  start                version

If you have a large number of commands, you can optionally group your commands into categories. Here’s the output from the example help_categories.py:

Documented commands (type help <topic>):

Application Management
======================
deploy  findleakers  redeploy  sessions  stop
expire  list         restart   start     undeploy

Connecting
==========
connect  which

Server Information
==================
resources  serverinfo  sslconnectorciphers  status  thread_dump  vminfo

Other
=====
alias   edit  history  py        quit  shell      unalias
config  help  load     pyscript  set   shortcuts  version

There are 2 methods of specifying command categories, using the @with_category decorator or with the categorize() function. Once a single command category is detected, the help output switches to a categorized mode of display. All commands with an explicit category defined default to the category Other.

Using the @with_category decorator:

@with_category(CMD_CAT_CONNECTING)
def do_which(self, _):
    """Which command"""
    self.poutput('Which')

Using the categorize() function:

You can call with a single function:

def do_connect(self, _):
    """Connect command"""
    self.poutput('Connect')

# Tag the above command functions under the category Connecting
categorize(do_connect, CMD_CAT_CONNECTING)

Or with an Iterable container of functions:

def do_undeploy(self, _):
    """Undeploy command"""
    self.poutput('Undeploy')

def do_stop(self, _):
    """Stop command"""
    self.poutput('Stop')

def do_findleakers(self, _):
    """Find Leakers command"""
    self.poutput('Find Leakers')

# Tag the above command functions under the category Application Management
categorize((do_undeploy,
            do_stop,
            do_findleakers), CMD_CAT_APP_MGMT)

The help command also has a verbose option (help -v or help --verbose) that combines the help categories with per-command Help Messages:

Documented commands (type help <topic>):

Application Management
================================================================================
deploy              Deploy command
expire              Expire command
findleakers         Find Leakers command
list                List command
redeploy            Redeploy command
restart             usage: restart [-h] {now,later,sometime,whenever}
sessions            Sessions command
start               Start command
stop                Stop command
undeploy            Undeploy command

Connecting
================================================================================
connect             Connect command
which               Which command

Server Information
================================================================================
resources              Resources command
serverinfo             Server Info command
sslconnectorciphers    SSL Connector Ciphers command is an example of a command that contains
                       multiple lines of help information for the user. Each line of help in a
                       contiguous set of lines will be printed and aligned in the verbose output
                       provided with 'help --verbose'
status                 Status command
thread_dump            Thread Dump command
vminfo                 VM Info command

Other
================================================================================
alias               Define or display aliases
config              Config command
edit                Edit a file in a text editor
help                List available commands with "help" or detailed help with "help cmd"
history             usage: history [-h] [-r | -e | -s | -o FILE | -t TRANSCRIPT] [arg]
load                Runs commands in script file that is encoded as either ASCII or UTF-8 text
py                  Invoke python command, shell, or script
pyscript            Runs a python script file inside the console
quit                Exits this application
set                 usage: set [-h] [-a] [-l] [settable [settable ...]]
shell               Execute a command as if at the OS prompt
shortcuts           Lists shortcuts available
unalias             Unsets aliases
version             Version command

Disabling Commands

cmd2 supports disabling commands during runtime. This is useful if certain commands should only be available when the application is in a specific state. When a command is disabled, it will not show up in the help menu or tab complete. If a user tries to run the command, a command-specific message supplied by the developer will be printed. The following functions support this feature.

enable_command()
Enable an individual command
enable_category()
Enable an entire category of commands
disable_command()
Disable an individual command and set the message that will print when this command is run or help is called on it while disabled
disable_category()
Disable an entire category of commands and set the message that will print when anything in this category is run or help is called on it while disabled

See the definitions of these functions for descriptions of their arguments.

See the do_enable_commands() and do_disable_commands() functions in the HelpCategories example for a demonstration.