summaryrefslogtreecommitdiff
path: root/docs/features
diff options
context:
space:
mode:
Diffstat (limited to 'docs/features')
-rw-r--r--docs/features/commands.rst195
-rw-r--r--docs/features/disable_commands.rst101
-rw-r--r--docs/features/help.rst4
3 files changed, 275 insertions, 25 deletions
diff --git a/docs/features/commands.rst b/docs/features/commands.rst
index 6a8fedee..196944b5 100644
--- a/docs/features/commands.rst
+++ b/docs/features/commands.rst
@@ -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
+--------------
+
+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 to 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 my 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 occers.
+
+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.poutput("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 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:
+- removing commands included in ``cmd2``
+- hiding commands from the help menu
+- disabling and re-enabling commands at runtime
diff --git a/docs/features/disable_commands.rst b/docs/features/disable_commands.rst
index b0f7f11b..78d214bb 100644
--- a/docs/features/disable_commands.rst
+++ b/docs/features/disable_commands.rst
@@ -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 (hiding 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')
diff --git a/docs/features/help.rst b/docs/features/help.rst
index 755d40f9..03e0867b 100644
--- a/docs/features/help.rst
+++ b/docs/features/help.rst
@@ -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::