diff options
Diffstat (limited to 'docs/features')
-rw-r--r-- | docs/features/commands.rst | 195 | ||||
-rw-r--r-- | docs/features/disable_commands.rst | 101 | ||||
-rw-r--r-- | docs/features/help.rst | 4 |
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:: |