Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/examples/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@ Examples
:maxdepth: 1

first_app
removing_builtin_commands
alternate_event_loops
7 changes: 0 additions & 7 deletions docs/examples/removing_builtin_commands.rst

This file was deleted.

195 changes: 173 additions & 22 deletions docs/features/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,196 @@ Commands

.. _cmd: https://docs.python.org/3/library/cmd.html

How to create a command with a ``do_command`` method,
``cmd2`` is designed to make it easy for you to create new commands. These
commmands form the backbone of your application. If you started writing your
application using cmd_, all the commands you have built will work when you move
to ``cmd2``. However, there are many more capabilities available in ``cmd2``
which you can take advantage of to add more robust features to your commands,
and which makes your commands easier to write. Before we get to all the good
stuff, let's briefly discuss how to create a new command in your application.

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:
Basic Commands
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kotfu You did a great job improving the documentation in this file. I really like how it flows.

--------------

The simplest ``cmd2`` application looks like this::

#!/usr/bin/env python
"""A simple cmd2 application."""
import cmd2


class App(cmd2.Cmd):
"""A simple cmd2 application."""


if __name__ == '__main__':
import sys
c = App()
sys.exit(c.cmdloop())

This application subclasses ``cmd2.Cmd`` but has no code of it's own, so all
functionality (and there's quite a bit) is inherited. Lets create a simple
command in this application called ``echo`` which outputs any arguments given
to it. Add this method to the class::

def do_echo(self, line):
self.poutput(line)

When you type input into the ``cmd2`` prompt, the first space delimited word is
treated as the command name. ``cmd2`` looks for a method called
``do_commandname``. If it exists, it calls the method, passing the rest of the
user input as the first argument. If it doesn't exist ``cmd2`` prints an error
message. As a result of this behavior, the only thing you have to do to create
a new command is to define a new method in the class with the appropriate name.
This is exactly how you would create a command using the cmd_ module which is
part of the python standard library.

.. note::

See :ref:`features/generating_output:Generating Output` if you are
unfamiliar with the ``poutput()`` method.


Statements
----------

A command is passed one argument: a string which contains all the rest of the
user input. However, in ``cmd2`` this string is actually a ``Statement``
object, which is a subclass of ``str`` to retain backwards compatibility.

``cmd2`` has a much more sophsticated parsing engine than what's included in
the cmd_ module. This parsing handles:

- quoted arguments
- output redirection and piping
- multi-line commands
- shortcut, macro, and alias expansion

In addition to parsing all of these elements from the user input, ``cmd2`` also
has code to make all of these items work; it's almost transparent to you and to
the commands you write in your own application. However, by passing your
command the ``Statement`` object instead of just a plain string, you can get
visibility into what ``cmd2`` has done with the user input before your command
got it. You can also avoid writing a bunch of parsing code, because ``cmd2``
gives you access to what it has already parsed.

A ``Statement`` object is a subclass of ``str`` that contains the following
attributes:

command
Name of the command called
Name of the command called. You already know this because of the method
``cmd2`` called, but it can sometimes be nice to have it in a string, i.e.
if you want your error messages to contain the command name.

args
The arguments to the command with output redirection
or piping to shell commands removed
A string containing the arguments to the command with output redirection or
piping to shell commands removed. It turns out that the "string" value of
the ``Statement`` object has all the output redirection and piping clauses
removed as well. Quotes remain in the string.

command_and_args
A string of just the command and the arguments, with
output redirection or piping to shell commands removed
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
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.
Full input exactly as typed by the user.

terminator
Character used to end a multiline command
Character used to end a multiline command. You can configure multiple
termination characters, and this attribute will tell you which one the user
typed.

For many simple commands, like the ``echo`` command above, you can ignore the
``Statement`` object and all of it's attributes and just use the passed value
as a string. You might choose to use the ``argv`` attribute to do more
sophisticated argument processing. Before you go too far down that path, you
should check out the :ref:`features/argument_processing:Argument Processing`
functionality included with ``cmd2``.


Return Values
-------------

Most commands should return nothing (either by omitting a ``return`` statement,
or by ``return None``. This indicates that your command is finished (with or
without errors), and that ``cmd2`` should prompt the user for more input.

If you return ``True`` from a command method, that indicates to ``cmd2`` that
it should stop prompting for user input and cleanly exit. ``cmd2`` already
includes a ``quit`` command, but if you wanted to make another one called
``finis`` you could::

def do_finis(self, line):
"""Exit the application"""
return True


Exit Codes
----------

``cmd2`` has basic infrastructure to support sh/ksh/csh/bash type exit codes.
The ``cmd2.Cmd`` object sets an ``exit_code`` attribute to zero when it is
instantiated. The value of this attribute is returned from the ``cmdloop()``
call. Therefore, if you don't do anything with this attribute in your code,
``cmdloop()`` will (almost) always return zero. There are a few built-in
``cmd2`` commands which set ``exit_code`` to ``-1`` if an error occurs.

You can use this capability to easily return your own values to the operating
system shell::

#!/usr/bin/env python
"""A simple cmd2 application."""
import cmd2


class App(cmd2.Cmd):
"""A simple cmd2 application."""

def do_bail(self, line):
"""Exit the application""
self.perror("fatal error, exiting")
self.exit_code = 2
return true

if __name__ == '__main__':
import sys
c = App()
sys.exit(c.cmdloop())

If the app was run from the `bash` operating system shell, then you would see
the following interaction::

(Cmd) bail
fatal error, exiting
$ echo $?
2


Exception Handling
------------------

You may choose to catch and handle any exceptions which occur in
a command method. If the command method raises an exception, ``cmd2`` will
catch it and display it for you. The `debug` :ref:`setting
<features/settings:Settings>` controls how the exception is displayed. If
`debug` is `false`, which is the default, ``cmd2`` will display the exception
name and message. If `debug` is `true`, ``cmd2`` will display a traceback, and
then display the exception name and message.

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.)
Disabling or Hiding Commands
----------------------------

See :ref:`features/disable_commands:Disabling Commands` for details of how
to:

- remove commands included in ``cmd2``
- hide commands from the help menu
- disable and re-enable commands at runtime
101 changes: 100 additions & 1 deletion docs/features/disable_commands.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,103 @@
Disabling Commands
==================

How to disable and re-enable commands, by individual command and by category
``cmd2`` allows a developer to:

- remove commands included in ``cmd2``
- prevent commands from appearing in the help menu (hide commands)
- disable and re-enable commands at runtime


Remove A Command
----------------

When a command has been removed, the command method has been deleted from the
object. The command doesn't show up in help, and it can't be executed. This
approach is appropriate if you never want a built-in command to be part of your
application. Delete the command method in your initialization code::

class RemoveBuiltinCommand(cmd2.Cmd):
"""An app which removes a built-in command from cmd2"""

def __init__(self):
super().__init__()
# To remove built-in commands entirely, delete
# the "do_*" function from the cmd2.Cmd class
del cmd2.Cmd.do_edit


Hide A Command
--------------

When a command is hidden, it won't show up in the help menu, but if
the user knows it's there and types the command, it will be executed.
You hide a command by adding it to the ``hidden_commands`` list::

class HiddenCommands(cmd2.Cmd):
""An app which demonstrates how to hide a command"""
def __init__(self):
super().__init__()
self.hidden_commands.append('py')

As shown above, you would typically do this as part of initializing your
application. If you decide you want to unhide a command later in the execution
of your application, you can by doing::

self.hidden_commands = [cmd for cmd in self.hidden_commands if cmd != 'py']

You might be thinking that the list comprehension is overkill and you'd rather
do something like::

self.hidden_commands.remove('py')

You may be right, but ``remove()`` will raise a ``ValueError`` if ``py``
isn't in the list, and it will only remove the first one if it's in the list
multiple times.


Disable A Command
-----------------

One way to disable a command is to add code to the command method which
determines whether the command should be executed or not. If the command should
not be executed, your code can print an appropriate error message and return.

``cmd2`` also provides another way to accomplish the same thing. Here's a
simple app which disables the ``open`` command if the door is locked::

class DisabledCommands(cmd2.Cmd):
"""An application which disables and enables commands"""

def do_lock(self, line):
self.disable_command('open', "you can't open the door because it is locked")
self.poutput('the door is locked')

def do_unlock(self, line):
self.enable_command('open')
self.poutput('the door is unlocked')

def do_open(self, line):
"""open the door"""
self.poutput('opening the door')

This method has the added benefit of removing disabled commands from the help
menu. But, this method only works if you know in advance that the command
should be disabled, and if the conditions for re-enabling it are likewise known
in advance.


Disable A Category of Commands
------------------------------

You can group or categorize commands as shown in
:ref:`features/help:Categorizing Commands`. If you do so, you can disable and
enable all the commands in a category with a single method call. Say you have
created a category of commands called "Server Information". You can disable
all commands in that category::

not_connected_msg = 'You must be connected to use this command'
self.disable_category('Server Information', not_connected_msg)

Similarly, you can re-enable all the commands in a category::

self.enable_category('Server Information')
4 changes: 2 additions & 2 deletions docs/features/help.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Use ``help_method()`` to custom roll your own help messages.

See :ref:`features/argument_processing:Help Messages`

Grouping Commands
-----------------
Categorizing Commands
---------------------

By default, the ``help`` command displays::

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import cmd2


class RemoveUnusedBuiltinCommands(cmd2.Cmd):
class RemoveBuiltinCommands(cmd2.Cmd):
""" Example cmd2 application where we remove some unused built-in commands."""

def __init__(self):
Expand All @@ -27,5 +27,5 @@ def __init__(self):

if __name__ == '__main__':
import sys
app = RemoveUnusedBuiltinCommands()
app = RemoveBuiltinCommands()
sys.exit(app.cmdloop())