Skip to content

Commit f5806d0

Browse files
committed
Revisions and improvements for hooks and plugins
1 parent 375776e commit f5806d0

File tree

5 files changed

+117
-72
lines changed

5 files changed

+117
-72
lines changed

cmd2/cmd2.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -144,19 +144,27 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
144144
:param persistent_history_length: max number of history items to write to the persistent history file
145145
:param startup_script: file path to a script to execute at startup
146146
:param use_ipython: should the "ipy" command be included for an embedded IPython shell
147-
:param allow_cli_args: if True, then cmd2 will process command line arguments as either
148-
commands to be run or, if -t is specified, transcript files to run.
149-
This should be set to False if your application parses its own arguments.
150-
:param transcript_files: allow running transcript tests when allow_cli_args is False
151-
:param allow_redirection: should output redirection and pipes be allowed. this is only a security setting
152-
and does not alter parsing behavior.
147+
:param allow_cli_args: if ``True``, then :meth:`cmd2.Cmd.__init__` will process command
148+
line arguments as either commands to be run or, if ``-t`` or ``--test`` are given, transcript files to run. This should be
149+
set to ``False`` if your application parses its own command line
150+
arguments.
151+
:param transcript_files: pass a list of transcript files to be run on initialization.
152+
This allows running transcript tests when ``allow_cli_args``
153+
is ``False``. If ``allow_cli_args`` is ``True`` this parameter
154+
is ignored.
155+
:param allow_redirection: If ``False``, prevent output redirection and piping to shell
156+
commands. This parameter prevents redirection and piping, but
157+
does not alter parsing behavior. A user can still type
158+
redirection and piping tokens, and they will be parsed as such
159+
but they won't do anything.
153160
:param multiline_commands: list of commands allowed to accept multi-line input
154161
:param terminators: list of characters that terminate a command. These are mainly intended for terminating
155162
multiline commands, but will also terminate single-line commands. If not supplied, then
156163
defaults to semicolon. If your app only contains single-line commands and you want
157164
terminators to be treated as literals by the parser, then set this to an empty list.
158165
:param shortcuts: dictionary containing shortcuts for commands. If not supplied, then defaults to
159-
constants.DEFAULT_SHORTCUTS.
166+
constants.DEFAULT_SHORTCUTS. If you do not want any shortcuts, pass
167+
an empty dictionary.
160168
"""
161169
# If use_ipython is False, make sure the ipy command isn't available in this instance
162170
if not use_ipython:
@@ -374,16 +382,18 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
374382

375383
def add_settable(self, settable: Settable) -> None:
376384
"""
377-
Convenience method to add a settable parameter to self.settables
385+
Convenience method to add a settable parameter to ``self.settables``
386+
378387
:param settable: Settable object being added
379388
"""
380389
self.settables[settable.name] = settable
381390

382391
def remove_settable(self, name: str) -> None:
383392
"""
384-
Convenience method for removing a settable parameter from self.settables
393+
Convenience method for removing a settable parameter from ``self.settables``
394+
385395
:param name: name of the settable being removed
386-
:raises: KeyError if the no Settable matches this name
396+
:raises: KeyError if the Settable matches this name
387397
"""
388398
try:
389399
del self.settables[name]

docs/api/cmd.rst

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,19 @@ cmd2.Cmd
2424

2525
.. attribute:: prompt
2626

27-
The prompt issued to solicit input.
28-
Default: ``(Cmd)``.
27+
The prompt issued to solicit input. The default value is ``(Cmd)``.
28+
See :ref:`features/prompt:Prompt` for more information.
29+
30+
.. attribute:: continuation_prompt
31+
32+
The prompt issued to solicit input for the 2nd and subsequent lines
33+
of a :ref:`multiline command <features/multiline_commands:Multiline Commands>`
34+
35+
.. attribute:: echo
36+
37+
If ``True``, output the prompt and user input before executing the command.
38+
When redirecting a series of commands to an output file, this allows you to
39+
see the command in the output.
2940

3041
.. attribute:: settable
3142

@@ -47,3 +58,8 @@ cmd2.Cmd
4758

4859
An instance of :class:`cmd2.parsing.StatementParser` initialized and
4960
configured appropriately for parsing user input.
61+
62+
.. attribute:: intro
63+
64+
Set an introduction message which is displayed to the user before
65+
the :ref:`features/hooks:Command Processing Loop` begins.

docs/features/hooks.rst

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ command processing loop.
1919
Application Lifecycle Hooks
2020
---------------------------
2121

22-
You can register methods to be called at the beginning of the command loop::
22+
You can run a script on initialization by passing the script filename in the
23+
``startup_script`` parameter of :meth:`cmd2.Cmd.__init__`.
24+
25+
You can also register methods to be called at the beginning of the command
26+
loop::
2327

2428
class App(cmd2.Cmd):
2529
def __init__(self, *args, *kwargs):
@@ -29,8 +33,9 @@ You can register methods to be called at the beginning of the command loop::
2933
def myhookmethod(self):
3034
self.poutput("before the loop begins")
3135

32-
To retain backwards compatibility with `cmd.Cmd`, after all registered preloop
33-
hooks have been called, the ``preloop()`` method is called.
36+
To retain backwards compatibility with ``cmd.Cmd``, after all registered
37+
preloop hooks have been called, the :meth:`~cmd2.Cmd.preloop` method is
38+
called.
3439

3540
A similar approach allows you to register functions to be called after the
3641
command loop has finished::
@@ -43,54 +48,68 @@ command loop has finished::
4348
def myhookmethod(self):
4449
self.poutput("before the loop begins")
4550

46-
To retain backwards compatibility with `cmd.Cmd`, after all registered postloop
47-
hooks have been called, the ``postloop()`` method is called.
51+
To retain backwards compatibility with ``cmd.Cmd``, after all registered
52+
postloop hooks have been called, the :meth:`~cmd2.Cmd.postloop` method is
53+
called.
4854

4955
Preloop and postloop hook methods are not passed any parameters and any return
5056
value is ignored.
5157

58+
The approach of registering hooks instead of overriding methods allows multiple
59+
hooks to be called before the command loop begins or ends. Plugin authors
60+
should review :ref:`features/plugins:Hooks` for best practices writing hooks.
61+
5262

5363
Application Lifecycle Attributes
5464
--------------------------------
5565

56-
There are numerous attributes of and arguments to ``cmd2.Cmd`` which have a
57-
significant effect on the application behavior upon entering or during the main
58-
loop. A partial list of some of the more important ones is presented here:
66+
There are numerous attributes on :class:`cmd2.Cmd` which affect application
67+
behavior upon entering or during the command loop:
68+
69+
- :data:`~cmd2.Cmd.intro` - if provided this serves as the intro banner printed
70+
once at start of application, after :meth:`~cmd2.Cmd.preloop` is called.
71+
- :data:`~cmd2.Cmd.prompt` - see :ref:`features/prompt:Prompt` for more
72+
information.
73+
- :data:`~cmd2.Cmd.continuation_prompt` - The prompt issued to solicit input
74+
for the 2nd and subsequent lines of a
75+
:ref:`multiline command <features/multiline_commands:Multiline Commands>`
76+
- :data:`~cmd2.Cmd.echo` - if ``True`` write the prompt and the command into
77+
the output stream.
5978

60-
- **intro**: *str* - if provided this serves as the intro banner printed once
61-
at start of application, after ``preloop`` runs
62-
- **allow_cli_args**: *bool* - if True (default), then searches for -t or
63-
--test at command line to invoke transcript testing mode instead of a normal
64-
main loop and also processes any commands provided as arguments on the
65-
command line just prior to entering the main loop
66-
- **echo**: *bool* - if True, then the command line entered is echoed to the
67-
screen (most useful when running scripts)
68-
- **prompt**: *str* - sets the prompt which is displayed, can be dynamically
69-
changed based on application state and/or command results
79+
In addition, several arguments to :meth:`cmd2.Cmd.__init__` also affect
80+
the command loop behavior:
81+
82+
- ``allow_cli_args`` - allows commands to be specified on the operating system
83+
command line which are executed before the command processing loop begins.
84+
- ``transcript_files`` - see :ref:`features/transcripts:Transcripts` for more
85+
information
86+
- ``startup_script`` - run a script on initialization. See
87+
:ref:`features/scripting:Scripting` for more information.
7088

7189

7290
Command Processing Loop
7391
-----------------------
7492

75-
When you call `.cmdloop()`, the following sequence of events are repeated until
76-
the application exits:
93+
When you call :meth:`cmd2.Cmd.cmdloop`, the following sequence of events are
94+
repeated until the application exits:
7795

7896
#. Output the prompt
7997
#. Accept user input
80-
#. Parse user input into `Statement` object
81-
#. Call methods registered with `register_postparsing_hook()`
98+
#. Parse user input into a :class:`~cmd2.Statement` object
99+
#. Call methods registered with :meth:`~cmd2.Cmd.register_postparsing_hook()`
82100
#. Redirect output, if user asked for it and it's allowed
83101
#. Start timer
84-
#. Call methods registered with `register_precmd_hook()`
85-
#. Call `precmd()` - for backwards compatibility with ``cmd.Cmd``
86-
#. Add statement to history
102+
#. Call methods registered with :meth:`~cmd2.Cmd.register_precmd_hook`
103+
#. Call :meth:`~cmd2.Cmd.precmd` - for backwards compatibility with ``cmd.Cmd``
104+
#. Add statement to :ref:`features/history:History`
87105
#. Call `do_command` method
88-
#. Call methods registered with `register_postcmd_hook()`
89-
#. Call `postcmd(stop, statement)` - for backwards compatibility with
106+
#. Call methods registered with :meth:`~cmd2.Cmd.register_postcmd_hook()`
107+
#. Call :meth:`~cmd2.Cmd.postcmd` - for backwards compatibility with
90108
``cmd.Cmd``
91109
#. Stop timer and display the elapsed time
92110
#. Stop redirecting output if it was redirected
93-
#. Call methods registered with `register_cmdfinalization_hook()`
111+
#. Call methods registered with
112+
:meth:`~cmd2.Cmd.register_cmdfinalization_hook()`
94113

95114
By registering hook methods, steps 4, 8, 12, and 16 allow you to run code
96115
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
103122
influence the command processing loop.
104123

105124
If a hook raises an exception:
125+
106126
- no more hooks (except command finalization hooks) of any kind will be called
107127
- if the command has not yet been executed, it will not be executed
108128
- the exception message will be displayed for the user.

docs/features/plugins.rst

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,15 @@ Plugins
55
a ``cmd2`` plugin which can extend basic ``cmd2`` functionality and can be
66
used by multiple applications.
77

8-
Adding functionality
9-
--------------------
10-
118
There are many ways to add functionality to ``cmd2`` using a plugin. Most
129
plugins will be implemented as a mixin. A mixin is a class that encapsulates
1310
and injects code into another class. Developers who use a plugin in their
14-
``cmd2`` project, will inject the plugin's code into their subclass of
15-
``cmd2.Cmd``.
11+
``cmd2`` project will inject the plugin's code into their subclass of
12+
:class:`cmd2.Cmd`.
1613

1714

1815
Mixin and Initialization
19-
~~~~~~~~~~~~~~~~~~~~~~~~
16+
------------------------
2017

2118
The following short example shows how to mix in a plugin and how the plugin
2219
gets initialized.
@@ -29,7 +26,6 @@ Here's the plugin::
2926
super().__init__(*args, **kwargs)
3027
# code placed here runs after cmd2.Cmd initializes
3128

32-
3329
and an example app which uses the plugin::
3430

3531
import cmd2
@@ -44,36 +40,37 @@ and an example app which uses the plugin::
4440
# code placed here runs after cmd2.Cmd and
4541
# all plugins have initialized
4642

47-
Note how the plugin must be inherited (or mixed in) before ``cmd2.Cmd``.
43+
Note how the plugin must be inherited (or mixed in) before :class:`cmd2.Cmd`.
4844
This is required for two reasons:
4945

50-
- The ``cmd.Cmd.__init__()`` method in the python standard library does not
46+
- The ``cmd.Cmd.__init__`` method in the python standard library does not
5147
call ``super().__init__()``. Because of this oversight, if you don't
5248
inherit from ``MyPlugin`` first, the ``MyPlugin.__init__()`` method will
5349
never be called.
54-
- You may want your plugin to be able to override methods from ``cmd2.Cmd``.
55-
If you mixin the plugin after ``cmd2.Cmd``, the python method resolution
56-
order will call ``cmd2.Cmd`` methods before it calls those in your plugin.
50+
- You may want your plugin to be able to override methods from
51+
:class:`cmd2.Cmd`. If you mixin the plugin after ``cmd2.Cmd``, the python
52+
method resolution order will call :class:`cmd2.Cmd` methods before it calls
53+
those in your plugin.
5754

5855

5956
Add commands
60-
~~~~~~~~~~~~
57+
------------
6158

6259
Your plugin can add user visible commands. You do it the same way in a plugin
63-
that you would in a ``cmd2.Cmd`` app::
60+
that you would in a :class:`cmd2.Cmd` app::
6461

6562
class MyPlugin:
6663
def do_say(self, statement):
6764
"""Simple say command"""
6865
self.poutput(statement)
6966

7067
You have all the same capabilities within the plugin that you do inside a
71-
``cmd2.Cmd`` app, including argument parsing via decorators and custom help
72-
methods.
68+
:class:`cmd2.Cmd` app, including argument parsing via decorators and custom
69+
help methods.
7370

7471

7572
Add (or hide) settings
76-
~~~~~~~~~~~~~~~~~~~~~~
73+
----------------------
7774

7875
A plugin may add user controllable settings to the application. Here's an
7976
example::
@@ -86,33 +83,34 @@ example::
8683
self.mysetting = 'somevalue'
8784
self.add_settable(cmd2.Settable('mysetting', str, 'short help message for mysetting'))
8885

89-
You can also hide settings from the user by removing them from
90-
``self.settables``.
86+
You can hide settings from the user by calling
87+
:meth:`~cmd2.Cmd.remove_settable`. See :ref:`features/settings:Settings` for
88+
more information.
9189

9290

9391
Decorators
94-
~~~~~~~~~~
92+
----------
9593

9694
Your plugin can provide a decorator which users of your plugin can use to
9795
wrap functionality around their own commands.
9896

9997

10098
Override methods
101-
~~~~~~~~~~~~~~~~
99+
----------------
102100

103-
Your plugin can override core ``cmd2.Cmd`` methods, changing their behavior.
104-
This approach should be used sparingly, because it is very brittle. If a
105-
developer chooses to use multiple plugins in their application, and several
106-
of the plugins override the same method, only the first plugin to be mixed in
107-
will have the overridden method called.
101+
Your plugin can override core :class:`cmd2.Cmd` methods, changing their
102+
behavior. This approach should be used sparingly, because it is very brittle.
103+
If a developer chooses to use multiple plugins in their application, and
104+
several of the plugins override the same method, only the first plugin to be
105+
mixed in will have the overridden method called.
108106

109107
Hooks are a much better approach.
110108

111109

112110
Hooks
113-
~~~~~
111+
-----
114112

115-
Plugins can register hook methods, which are called by :class:`~cmd2.Cmd`
113+
Plugins can register hook methods, which are called by :class:`cmd2.Cmd`
116114
during various points in the application and command processing lifecycle.
117115
Plugins should not override any of the deprecated hook methods, instead they
118116
should register their hooks as described in the :ref:`features/hooks:Hooks`
@@ -147,7 +145,7 @@ ways hooks can influence the lifecycle.
147145

148146

149147
Classes and Functions
150-
~~~~~~~~~~~~~~~~~~~~~
148+
---------------------
151149

152150
Your plugin can also provide classes and functions which can be used by
153151
developers of ``cmd2`` based applications. Describe these classes and

docs/features/transcripts.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,10 @@ output matches the expected result from the transcript.
185185

186186
.. note::
187187

188-
If you have set ``allow_cli_args`` to False in order to disable parsing of
189-
command line arguments at invocation, then the use of ``-t`` or ``--test`` to
190-
run transcript testing is automatically disabled. In this case, you can
188+
If you have passed an ``allow_cli_args`` parameter containing `False` to
189+
:meth:`cmd2.Cmd.__init__` in order to disable parsing of command line
190+
arguments at invocation, then the use of ``-t`` or ``--test`` to run
191+
transcript testing is automatically disabled. In this case, you can
191192
alternatively provide a value for the optional ``transcript_files`` when
192193
constructing the instance of your ``cmd2.Cmd`` derived class in order to
193194
cause a transcript test to run::

0 commit comments

Comments
 (0)