summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkotfu <kotfu@kotfu.net>2020-02-22 12:22:35 -0700
committerkotfu <kotfu@kotfu.net>2020-02-22 12:22:35 -0700
commitf5806d06e0c453c189c99a1dce5ef98208c4d8cf (patch)
tree72722498e07da1b2e75680aa5a4ddb19dbf2f034
parent375776e8a0281a47afa2846a19687206c5f9fee1 (diff)
downloadcmd2-git-f5806d06e0c453c189c99a1dce5ef98208c4d8cf.tar.gz
Revisions and improvements for hooks and plugins
-rw-r--r--cmd2/cmd2.py30
-rw-r--r--docs/api/cmd.rst20
-rw-r--r--docs/features/hooks.rst76
-rw-r--r--docs/features/plugins.rst56
-rw-r--r--docs/features/transcripts.rst7
5 files changed, 117 insertions, 72 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index b475ac67..a662682b 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -144,19 +144,27 @@ class Cmd(cmd.Cmd):
:param persistent_history_length: max number of history items to write to the persistent history file
:param startup_script: file path to a script to execute at startup
:param use_ipython: should the "ipy" command be included for an embedded IPython shell
- :param allow_cli_args: if True, then cmd2 will process command line arguments as either
- commands to be run or, if -t is specified, transcript files to run.
- This should be set to False if your application parses its own arguments.
- :param transcript_files: allow running transcript tests when allow_cli_args is False
- :param allow_redirection: should output redirection and pipes be allowed. this is only a security setting
- and does not alter parsing behavior.
+ :param allow_cli_args: if ``True``, then :meth:`cmd2.Cmd.__init__` will process command
+ line arguments as either commands to be run or, if ``-t`` or ``--test`` are given, transcript files to run. This should be
+ set to ``False`` if your application parses its own command line
+ arguments.
+ :param transcript_files: pass a list of transcript files to be run on initialization.
+ This allows running transcript tests when ``allow_cli_args``
+ is ``False``. If ``allow_cli_args`` is ``True`` this parameter
+ is ignored.
+ :param allow_redirection: If ``False``, prevent output redirection and piping to shell
+ commands. This parameter prevents redirection and piping, but
+ does not alter parsing behavior. A user can still type
+ redirection and piping tokens, and they will be parsed as such
+ but they won't do anything.
:param multiline_commands: list of commands allowed to accept multi-line input
:param terminators: list of characters that terminate a command. These are mainly intended for terminating
multiline commands, but will also terminate single-line commands. If not supplied, then
defaults to semicolon. If your app only contains single-line commands and you want
terminators to be treated as literals by the parser, then set this to an empty list.
:param shortcuts: dictionary containing shortcuts for commands. If not supplied, then defaults to
- constants.DEFAULT_SHORTCUTS.
+ constants.DEFAULT_SHORTCUTS. If you do not want any shortcuts, pass
+ an empty dictionary.
"""
# If use_ipython is False, make sure the ipy command isn't available in this instance
if not use_ipython:
@@ -374,16 +382,18 @@ class Cmd(cmd.Cmd):
def add_settable(self, settable: Settable) -> None:
"""
- Convenience method to add a settable parameter to self.settables
+ Convenience method to add a settable parameter to ``self.settables``
+
:param settable: Settable object being added
"""
self.settables[settable.name] = settable
def remove_settable(self, name: str) -> None:
"""
- Convenience method for removing a settable parameter from self.settables
+ Convenience method for removing a settable parameter from ``self.settables``
+
:param name: name of the settable being removed
- :raises: KeyError if the no Settable matches this name
+ :raises: KeyError if the Settable matches this name
"""
try:
del self.settables[name]
diff --git a/docs/api/cmd.rst b/docs/api/cmd.rst
index 34c0dcf7..3fcd3352 100644
--- a/docs/api/cmd.rst
+++ b/docs/api/cmd.rst
@@ -24,8 +24,19 @@ cmd2.Cmd
.. attribute:: prompt
- The prompt issued to solicit input.
- Default: ``(Cmd)``.
+ The prompt issued to solicit input. The default value is ``(Cmd)``.
+ See :ref:`features/prompt:Prompt` for more information.
+
+ .. attribute:: continuation_prompt
+
+ The prompt issued to solicit input for the 2nd and subsequent lines
+ of a :ref:`multiline command <features/multiline_commands:Multiline Commands>`
+
+ .. attribute:: echo
+
+ If ``True``, output the prompt and user input before executing the command.
+ When redirecting a series of commands to an output file, this allows you to
+ see the command in the output.
.. attribute:: settable
@@ -47,3 +58,8 @@ cmd2.Cmd
An instance of :class:`cmd2.parsing.StatementParser` initialized and
configured appropriately for parsing user input.
+
+ .. attribute:: intro
+
+ Set an introduction message which is displayed to the user before
+ the :ref:`features/hooks:Command Processing Loop` begins.
diff --git a/docs/features/hooks.rst b/docs/features/hooks.rst
index 9cac40cd..fec8e258 100644
--- a/docs/features/hooks.rst
+++ b/docs/features/hooks.rst
@@ -19,7 +19,11 @@ command processing loop.
Application Lifecycle Hooks
---------------------------
-You can register methods to be called at the beginning of the command loop::
+You can run a script on initialization by passing the script filename in the
+``startup_script`` parameter of :meth:`cmd2.Cmd.__init__`.
+
+You can also register methods to be called at the beginning of the command
+loop::
class App(cmd2.Cmd):
def __init__(self, *args, *kwargs):
@@ -29,8 +33,9 @@ You can register methods to be called at the beginning of the command loop::
def myhookmethod(self):
self.poutput("before the loop begins")
-To retain backwards compatibility with `cmd.Cmd`, after all registered preloop
-hooks have been called, the ``preloop()`` method is called.
+To retain backwards compatibility with ``cmd.Cmd``, after all registered
+preloop hooks have been called, the :meth:`~cmd2.Cmd.preloop` method is
+called.
A similar approach allows you to register functions to be called after the
command loop has finished::
@@ -43,54 +48,68 @@ command loop has finished::
def myhookmethod(self):
self.poutput("before the loop begins")
-To retain backwards compatibility with `cmd.Cmd`, after all registered postloop
-hooks have been called, the ``postloop()`` method is called.
+To retain backwards compatibility with ``cmd.Cmd``, after all registered
+postloop hooks have been called, the :meth:`~cmd2.Cmd.postloop` method is
+called.
Preloop and postloop hook methods are not passed any parameters and any return
value is ignored.
+The approach of registering hooks instead of overriding methods allows multiple
+hooks to be called before the command loop begins or ends. Plugin authors
+should review :ref:`features/plugins:Hooks` for best practices writing hooks.
+
Application Lifecycle Attributes
--------------------------------
-There are numerous attributes of and arguments to ``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 on :class:`cmd2.Cmd` which affect application
+behavior upon entering or during the command loop:
+
+- :data:`~cmd2.Cmd.intro` - if provided this serves as the intro banner printed
+ once at start of application, after :meth:`~cmd2.Cmd.preloop` is called.
+- :data:`~cmd2.Cmd.prompt` - see :ref:`features/prompt:Prompt` for more
+ information.
+- :data:`~cmd2.Cmd.continuation_prompt` - The prompt issued to solicit input
+ for the 2nd and subsequent lines of a
+ :ref:`multiline command <features/multiline_commands:Multiline Commands>`
+- :data:`~cmd2.Cmd.echo` - if ``True`` write the prompt and the command into
+ the output stream.
-- **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
+In addition, several arguments to :meth:`cmd2.Cmd.__init__` also affect
+the command loop behavior:
+
+- ``allow_cli_args`` - allows commands to be specified on the operating system
+ command line which are executed before the command processing loop begins.
+- ``transcript_files`` - see :ref:`features/transcripts:Transcripts` for more
+ information
+- ``startup_script`` - run a script on initialization. See
+ :ref:`features/scripting:Scripting` for more information.
Command Processing Loop
-----------------------
-When you call `.cmdloop()`, the following sequence of events are repeated until
-the application exits:
+When you call :meth:`cmd2.Cmd.cmdloop`, the following sequence of events are
+repeated until the application exits:
#. Output the prompt
#. Accept user input
-#. Parse user input into `Statement` object
-#. Call methods registered with `register_postparsing_hook()`
+#. Parse user input into a :class:`~cmd2.Statement` object
+#. Call methods registered with :meth:`~cmd2.Cmd.register_postparsing_hook()`
#. Redirect output, if user asked for it and it's allowed
#. Start timer
-#. Call methods registered with `register_precmd_hook()`
-#. Call `precmd()` - for backwards compatibility with ``cmd.Cmd``
-#. Add statement to history
+#. Call methods registered with :meth:`~cmd2.Cmd.register_precmd_hook`
+#. Call :meth:`~cmd2.Cmd.precmd` - for backwards compatibility with ``cmd.Cmd``
+#. Add statement to :ref:`features/history:History`
#. Call `do_command` method
-#. Call methods registered with `register_postcmd_hook()`
-#. Call `postcmd(stop, statement)` - for backwards compatibility with
+#. Call methods registered with :meth:`~cmd2.Cmd.register_postcmd_hook()`
+#. Call :meth:`~cmd2.Cmd.postcmd` - for backwards compatibility with
``cmd.Cmd``
#. Stop timer and display the elapsed time
#. Stop redirecting output if it was redirected
-#. Call methods registered with `register_cmdfinalization_hook()`
+#. Call methods registered with
+ :meth:`~cmd2.Cmd.register_cmdfinalization_hook()`
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
@@ -103,6 +122,7 @@ Postparsing, precommand, and postcommand hook methods share some common ways to
influence the command processing loop.
If a hook raises an 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.
diff --git a/docs/features/plugins.rst b/docs/features/plugins.rst
index d6e3eb9c..ecd3a32d 100644
--- a/docs/features/plugins.rst
+++ b/docs/features/plugins.rst
@@ -5,18 +5,15 @@ Plugins
a ``cmd2`` plugin which can extend basic ``cmd2`` functionality and can be
used by multiple applications.
-Adding functionality
---------------------
-
There are many ways to add functionality to ``cmd2`` using a plugin. Most
plugins will be implemented as a mixin. A mixin is a class that encapsulates
and injects code into another class. Developers who use a plugin in their
-``cmd2`` project, will inject the plugin's code into their subclass of
-``cmd2.Cmd``.
+``cmd2`` project will inject the plugin's code into their subclass of
+:class:`cmd2.Cmd`.
Mixin and Initialization
-~~~~~~~~~~~~~~~~~~~~~~~~
+------------------------
The following short example shows how to mix in a plugin and how the plugin
gets initialized.
@@ -29,7 +26,6 @@ Here's the plugin::
super().__init__(*args, **kwargs)
# code placed here runs after cmd2.Cmd initializes
-
and an example app which uses the plugin::
import cmd2
@@ -44,23 +40,24 @@ and an example app which uses the plugin::
# code placed here runs after cmd2.Cmd and
# all plugins have initialized
-Note how the plugin must be inherited (or mixed in) before ``cmd2.Cmd``.
+Note how the plugin must be inherited (or mixed in) before :class:`cmd2.Cmd`.
This is required for two reasons:
-- The ``cmd.Cmd.__init__()`` method in the python standard library does not
+- The ``cmd.Cmd.__init__`` method in the python standard library does not
call ``super().__init__()``. Because of this oversight, if you don't
inherit from ``MyPlugin`` first, the ``MyPlugin.__init__()`` method will
never be called.
-- You may want your plugin to be able to override methods from ``cmd2.Cmd``.
- If you mixin the plugin after ``cmd2.Cmd``, the python method resolution
- order will call ``cmd2.Cmd`` methods before it calls those in your plugin.
+- You may want your plugin to be able to override methods from
+ :class:`cmd2.Cmd`. If you mixin the plugin after ``cmd2.Cmd``, the python
+ method resolution order will call :class:`cmd2.Cmd` methods before it calls
+ those in your plugin.
Add commands
-~~~~~~~~~~~~
+------------
Your plugin can add user visible commands. You do it the same way in a plugin
-that you would in a ``cmd2.Cmd`` app::
+that you would in a :class:`cmd2.Cmd` app::
class MyPlugin:
def do_say(self, statement):
@@ -68,12 +65,12 @@ that you would in a ``cmd2.Cmd`` app::
self.poutput(statement)
You have all the same capabilities within the plugin that you do inside a
-``cmd2.Cmd`` app, including argument parsing via decorators and custom help
-methods.
+:class:`cmd2.Cmd` app, including argument parsing via decorators and custom
+help methods.
Add (or hide) settings
-~~~~~~~~~~~~~~~~~~~~~~
+----------------------
A plugin may add user controllable settings to the application. Here's an
example::
@@ -86,33 +83,34 @@ example::
self.mysetting = 'somevalue'
self.add_settable(cmd2.Settable('mysetting', str, 'short help message for mysetting'))
-You can also hide settings from the user by removing them from
-``self.settables``.
+You can hide settings from the user by calling
+:meth:`~cmd2.Cmd.remove_settable`. See :ref:`features/settings:Settings` for
+more information.
Decorators
-~~~~~~~~~~
+----------
Your plugin can provide a decorator which users of your plugin can use to
wrap functionality around their own commands.
Override methods
-~~~~~~~~~~~~~~~~
+----------------
-Your plugin can override core ``cmd2.Cmd`` methods, changing their behavior.
-This approach should be used sparingly, because it is very brittle. If a
-developer chooses to use multiple plugins in their application, and several
-of the plugins override the same method, only the first plugin to be mixed in
-will have the overridden method called.
+Your plugin can override core :class:`cmd2.Cmd` methods, changing their
+behavior. This approach should be used sparingly, because it is very brittle.
+If a developer chooses to use multiple plugins in their application, and
+several of the plugins override the same method, only the first plugin to be
+mixed in will have the overridden method called.
Hooks are a much better approach.
Hooks
-~~~~~
+-----
-Plugins can register hook methods, which are called by :class:`~cmd2.Cmd`
+Plugins can register hook methods, which are called by :class:`cmd2.Cmd`
during various points in the application and command processing lifecycle.
Plugins should not override any of the deprecated hook methods, instead they
should register their hooks as described in the :ref:`features/hooks:Hooks`
@@ -147,7 +145,7 @@ ways hooks can influence the lifecycle.
Classes and Functions
-~~~~~~~~~~~~~~~~~~~~~
+---------------------
Your plugin can also provide classes and functions which can be used by
developers of ``cmd2`` based applications. Describe these classes and
diff --git a/docs/features/transcripts.rst b/docs/features/transcripts.rst
index 1af2a74f..fa6d9cb3 100644
--- a/docs/features/transcripts.rst
+++ b/docs/features/transcripts.rst
@@ -185,9 +185,10 @@ output matches the expected result from the transcript.
.. note::
- If you have set ``allow_cli_args`` to False in order to disable parsing of
- command line arguments at invocation, then the use of ``-t`` or ``--test`` to
- run transcript testing is automatically disabled. In this case, you can
+ If you have passed an ``allow_cli_args`` parameter containing `False` to
+ :meth:`cmd2.Cmd.__init__` in order to disable parsing of command line
+ arguments at invocation, then the use of ``-t`` or ``--test`` to run
+ transcript testing is automatically disabled. In this case, you can
alternatively provide a value for the optional ``transcript_files`` when
constructing the instance of your ``cmd2.Cmd`` derived class in order to
cause a transcript test to run::