summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/hooks.rst317
1 files changed, 294 insertions, 23 deletions
diff --git a/docs/hooks.rst b/docs/hooks.rst
index 1e96e963..0ec5d7e9 100644
--- a/docs/hooks.rst
+++ b/docs/hooks.rst
@@ -1,43 +1,314 @@
-.. cmd2 documentation for application and command lifecycle and the hooks which are available
+.. cmd2 documentation for application and command lifecycle and the available hooks
cmd2 Application Lifecycle and Hooks
====================================
The typical way of starting a cmd2 application is as follows::
- from cmd2 import Cmd
- class App(Cmd):
+ import cmd2
+ class App(cmd2.Cmd):
# customized attributes and methods here
- app = App()
- app.cmdloop()
-There are several pre-existing methods and attributes which you can tweak to control the overall behavior of your
-application before, during, and after the main loop.
+ if __name__ == '__main__':
+ app = App()
+ app.cmdloop()
-Application Lifecycle Hook Methods
-----------------------------------
-The ``preloop`` and ``postloop`` methods run before and after the main loop, respectively.
+There are several pre-existing methods and attributes which you can tweak to
+control the overall behavior of your application before, during, and after the
+command processing loop.
-.. automethod:: cmd2.cmd2.Cmd.preloop
+Application Lifecycle Hooks
+---------------------------
+
+You can register methods to be called at the beginning of the command loop::
+
+ class App(cmd2.Cmd):
+ def __init__(self, *args, *kwargs):
+ super().__init__(*args, **kwargs)
+ self.register_preloop_hook(self.myhookmethod)
+
+ def myhookmethod(self):
+ self.poutput("before the loop begins")
+
+And also after the command loop has finished::
+
+ class App(cmd2.Cmd):
+ def __init__(self, *args, *kwargs):
+ super().__init__(*args, **kwargs)
+ self.register_postloop_hook(self.myhookmethod)
+
+ def myhookmethod(self):
+ self.poutput("before the loop begins")
+
+Preloop and postloop hook methods are not passed any parameters and any return
+value is ignored.
-.. automethod:: cmd2.cmd2.Cmd.postloop
Application Lifecycle Attributes
--------------------------------
-There are numerous attributes (member variables of the ``cmd2.Cmd``) which have a significant effect on the application
-behavior upon entering or during the main loop. A partial list of some of the more important ones is presented here:
+There are numerous attributes (member variables of the ``cmd2.Cmd``) which have
+a significant effect on the application behavior upon entering or during the
+main loop. A partial list of some of the more important ones is presented here:
+
+- **intro**: *str* - if provided this serves as the intro banner printed once
+ at start of application, after ``preloop`` runs
+- **allow_cli_args**: *bool* - if True (default), then searches for -t or
+ --test at command line to invoke transcript testing mode instead of a normal
+ main loop and also processes any commands provided as arguments on the
+ command line just prior to entering the main loop
+- **echo**: *bool* - if True, then the command line entered is echoed to the
+ screen (most useful when running scripts)
+- **prompt**: *str* - sets the prompt which is displayed, can be dynamically
+ changed based on application state and/or command results
+
+
+Command Processing Loop
+-----------------------
+
+When you call `.cmdloop()`, the following sequence of events are repeated until
+the application exits:
+
+1. Output the prompt
+2. Accept user input
+3. Parse user input into `Statement` object
+4. Call methods registered with `register_postparsing_hook()`
+5. Call `postparsing_precmd()` - for backwards compatibility with prior releases of cmd2, now deprecated
+6. Redirect output, if user asked for it and it's allowed
+7. Start timer
+8. Call methods registered with `register_precmd_hook()`
+9. Call `precmd()` - for backwards compatibility with ``cmd.Cmd``
+10. Add statement to history
+11. Call `do_command` method
+12. Call methods registered with `register_postcmd_hook()`
+13. Call `postcmd(stop, statement)` - for backwards compatibility with ``cmd.Cmd``
+14. Stop timer and display the elapsed time
+15. Stop redirecting output if it was redirected
+16. Call methods registered with `register_cmdfinalization_hook()`
+17. Call `postparsing_postcmd()` - for backwards compatibility - deprecated
+
+By registering hook methods, steps 4, 8, 12, and 16 allow you to run code
+during, and control the flow of the command processing loop. Be aware that
+plugins also utilize these hooks, so there may be code running that is not part
+of your application. Methods registered for a hook are called in the order they
+were registered. You can register a function more than once, and it will be
+called each time it was registered.
+
+Postparsing, precomamnd, and postcommand hook methods share some common ways to
+influence the command processing loop.
+
+If a hook raises a ``cmd2.EmptyStatement`` exception:
+- no more hooks (except command finalization hooks) of any kind will be called
+- if the command has not yet been executed, it will not be executed
+- no error message will be displayed to the user
+
+If a hook raises any other exception:
+- no more hooks (except command finalization hooks) of any kind will be called
+- if the command has not yet been executed, it will not be executed
+- the exception message will be displayed for the user.
+
+Specific types of hook methods have additional options as described below.
+
+Postparsing Hooks
+^^^^^^^^^^^^^^^^^
+
+Postparsing hooks are called after the user input has been parsed but before
+execution of the comamnd. These hooks can be used to:
+
+- modify the user input
+- run code before every command executes
+- cancel execution of the current command
+- exit the application
+
+When postparsing hooks are called, output has not been redirected, nor has the
+timer for command execution been started.
+
+To define and register a postparsing hook, do the following::
+
+ class App(cmd2.Cmd):
+ def __init__(self, *args, *kwargs):
+ super().__init__(*args, **kwargs)
+ self.register_postparsing_hook(self.myhookmethod)
+
+ def myhookmethod(self, params: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData:
+ # the statement object created from the user input
+ # is available as params.statement
+ return params
+
+``register_postparsing_hook()`` checks the method signature of the passed callable,
+and raises a ``TypeError`` if it has the wrong number of parameters. It will
+also raise a ``TypeError`` if the passed parameter and return value are not annotated
+as ``PostparsingData``.
+
+
+The hook method will be passed one parameter, a ``PostparsingData`` object
+which we will refer to as ``params``. ``params`` contains two attributes.
+``params.statement`` is a ``Statement`` object which describes the parsed
+user input. There are many useful attributes in the ``Statement``
+object, including ``.raw`` which contains exactly what the user typed.
+``params.stop`` is set to ``False`` by default.
+
+The hook method must return a ``PostparsingData`` object, and it is very
+convenient to just return the object passed into the hook method. The hook
+method may modify the attributes of the object to influece the behavior of
+the application. If ``params.stop`` is set to true, a fatal failure is
+triggered prior to execution of the command, and the application exits.
+
+To modify the user input, you create a new ``Statement`` object and return it in
+``params.statement``. Don't try and directly modify the contents of a
+``Statement`` object, there be dragons. Instead, use the various attributes in a
+``Statement`` object to construct a new string, and then parse that string to
+create a new ``Statement`` object.
+
+``cmd2.Cmd()`` uses an instance of ``cmd2.StatementParser`` to parse user input.
+This instance has been configured with the proper command terminators, multiline
+commands, and other parsing related settings. This instance is available as the
+``self.statement_parser`` attribute. Here's a simple example which shows the
+proper technique::
+
+ def myhookmethod(self, params: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData:
+ if not '|' in params.statement.raw:
+ newinput = params.statement.raw + ' | less'
+ params.statement = self.statement_parser.parse(newinput)
+ return params
+
+If a postparsing hook returns a ``PostparsingData`` object with the ``stop``
+attribute set to ``True``:
+
+- no more hooks of any kind (except command finalization hooks) will be called
+- the command will not be executed
+- no error message will be displayed to the user
+- the application will exit
-- **intro**: *str* - if provided this serves as the intro banner printed once at start of application, after ``preloop`` runs
-- **allow_cli_args**: *bool* - if True (default), then searches for -t or --test at command line to invoke transcript testing mode instead of a normal main loop
- and also processes any commands provided as arguments on the command line just prior to entering the main loop
-- **echo**: *bool* - if True, then the command line entered is echoed to the screen (most useful when running scripts)
-- **prompt**: *str* - sets the prompt which is displayed, can be dynamically changed based on application state and/or
- command results
+Precommand Hooks
+^^^^^^^^^^^^^^^^^
+
+Precommand hooks can modify the user input, but can not request the application
+terminate. If your hook needs to be able to exit the application, you should
+implement it as a postparsing hook.
+
+Once output is redirected and the timer started, all the hooks registered with
+``register_precmd_hook()`` are called. Here's how to do it::
+
+ class App(cmd2.Cmd):
+ def __init__(self, *args, *kwargs):
+ super().__init__(*args, **kwargs)
+ self.register_precmd_hook(self.myhookmethod)
+
+ def myhookmethod(self, data: cmd2.plugin.PrecommandData) -> cmd2.plugin.PrecommandData:
+ # the statement object created from the user input
+ # is available as data.statement
+ return data
+
+``register_precmd_hook()`` checks the method signature of the passed callable,
+and raises a ``TypeError`` if it has the wrong number of parameters. It will
+also raise a ``TypeError`` if the parameters and return value are not annotated
+as ``PrecommandData``.
+
+You may choose to modify the user input by creating a new ``Statement`` with
+different properties (see above). If you do so, assign your new ``Statement``
+object to ``data.statement``.
+
+The precommand hook must return a ``PrecommandData`` object. You don't have to
+create this object from scratch, you can just return the one passed into the hook.
+
+After all registered precommand hooks have been called,
+``self.precmd(statement)`` will be called. To retain full backward compatibility
+with ``cmd.Cmd``, this method is passed a ``Statement``, not a
+``PrecommandData`` object.
+
+
+Postcommand Hooks
+^^^^^^^^^^^^^^^^^^
+
+Once the command method has returned (i.e. the ``do_command(self, statement)
+method`` has been called and returns, all postcommand hooks are called. If
+output was redirected by the user, it is still redirected, and the command timer
+is still running.
+
+Here's how to define and register a postcommand hook::
+
+ class App(cmd2.Cmd):
+ def __init__(self, *args, *kwargs):
+ super().__init__(*args, **kwargs)
+ self.register_postcmd_hook(self.myhookmethod)
+
+ def myhookmethod(self, data: cmd2.plugin.PostcommandData) -> cmd2.plugin.PostcommandData:
+ return stop
+
+Your hook will be passed a ``PostcommandData`` object, which has a ``statement``
+attribute that describes the command which was executed. If your postcommand
+hook method gets called, you are guaranteed that the command method was called,
+and that it didn't raise an exception.
+
+If any postcommand hook raises an exception, the exception will be displayed to the user, and no further postcommand hook methods
+will be called. Command finalization hooks, if any, will be called.
+
+After all registered precommand hooks have been called,
+``self.postcmd(statement)`` will be called to retain full backward compatibility
+with ``cmd.Cmd``.
+
+If any postcommand hook (registered or ``self.postcmd()``) returns ``True``,
+subsequent postcommand hooks will still be called, as will the command
+finalization hooks, but once those hooks have all been called, the application
+will terminate.
+
+Any postcommand hook can change the value of the ``stop`` parameter before
+returning it, and the modified value will be passed to the next postcommand
+hook. The value returned by the final postcommand hook will be passed to the
+command finalization hooks, which may further modify the value. If your hook
+blindly returns ``False``, a prior hook's requst to exit the application will
+not be honored. It's best to return the value you were passed unless you have a
+compelling reason to do otherwise.
+
+
+Command Finalization Hooks
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Command finalization hooks are called even if one of the other types of hooks or
+the command method raise an exception. Here's how to create and register a
+command finalization hook::
+
+ class App(cmd2.Cmd):
+ def __init__(self, *args, *kwargs):
+ super().__init__(*args, **kwargs)
+ self.register_cmdfinalization_hook(self.myhookmethod)
+
+ def myhookmethod(self, stop, statement):
+ return stop
+
+Command Finalization hooks must check whether the statement object is ``None``. There are certain circumstances where these hooks may be called before the statement has been parsed, so you can't always rely on having a statement.
+
+If any prior postparsing or precommand hook has requested the application to
+terminate, the value of the ``stop`` parameter passed to the first command
+finalization hook will be ``True``. Any command finalization hook can change the
+value of the ``stop`` parameter before returning it, and the modified value will
+be passed to the next command finalization hook. The value returned by the final
+command finalization hook will determine whether the application terminates or
+not.
+
+This approach to command finalization hooks can be powerful, but it can also
+cause problems. If your hook blindly returns ``False``, a prior hook's requst to
+exit the application will not be honored. It's best to return the value you were
+passed unless you have a compelling reason to do otherwise.
+
+If any command finalization hook raises an exception, no more command
+finalization hooks will be called. If the last hook to return a value returned
+``True``, then the exception will be rendered, and the application will
+terminate.
+
+Deprecated Application Lifecycle Hook Methods
+---------------------------------------------
+
+The ``preloop`` and ``postloop`` methods run before and after the main loop, respectively.
+
+.. automethod:: cmd2.cmd2.Cmd.preloop
+
+.. automethod:: cmd2.cmd2.Cmd.postloop
-Command Processing Hooks
-------------------------
+Deprecated Command Processing Hooks
+-----------------------------------
Inside the main loop, every time the user hits <Enter> the line is processed by the ``onecmd_plus_hooks`` method.
@@ -56,4 +327,4 @@ the various hook methods, presented in chronological order starting with the one
.. automethod:: cmd2.cmd2.Cmd.postcmd
-.. automethod:: cmd2.cmd2.Cmd.postparsing_postcmd
+.. automethod:: cmd2.cmd2.Cmd.postparsing_postcmd \ No newline at end of file