summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorEric Lin <anselor@gmail.com>2020-07-24 12:21:43 -0400
committeranselor <anselor@gmail.com>2020-08-04 13:38:08 -0400
commit06cee9126839c465a356f8b44a5f008853eb8cad (patch)
tree88de1a9f07f20fb6a7e1a8f77b1c48fb41382d19 /docs
parent787a31931ed4c4a18ae66a570d396b12b2b7b525 (diff)
downloadcmd2-git-06cee9126839c465a356f8b44a5f008853eb8cad.tar.gz
updated imports
Added additional documentation
Diffstat (limited to 'docs')
-rw-r--r--docs/features/commands.rst12
-rw-r--r--docs/features/index.rst1
-rw-r--r--docs/features/modular_commands.rst201
3 files changed, 214 insertions, 0 deletions
diff --git a/docs/features/commands.rst b/docs/features/commands.rst
index 13a4ac1f..8e61a472 100644
--- a/docs/features/commands.rst
+++ b/docs/features/commands.rst
@@ -209,3 +209,15 @@ to:
- remove commands included in ``cmd2``
- hide commands from the help menu
- disable and re-enable commands at runtime
+
+
+Modular Commands and Loading/Unloading Commands
+-----------------------------------------------
+
+See :ref:`features/modular_commands:Modular Commands` for details of how
+to:
+
+- Define commands in separate CommandSet modules
+- Load or unload commands at runtime
+
+
diff --git a/docs/features/index.rst b/docs/features/index.rst
index efc0fe67..48590b6a 100644
--- a/docs/features/index.rst
+++ b/docs/features/index.rst
@@ -17,6 +17,7 @@ Features
hooks
initialization
misc
+ modular_commands
multiline_commands
os
packaging
diff --git a/docs/features/modular_commands.rst b/docs/features/modular_commands.rst
new file mode 100644
index 00000000..d94e225a
--- /dev/null
+++ b/docs/features/modular_commands.rst
@@ -0,0 +1,201 @@
+Modular Commands
+================
+
+Overview
+--------
+
+Cmd2 also enables developers to modularize their command definitions into Command Sets. Command sets represent
+a logical grouping of commands within an cmd2 application. By default, all CommandSets will be discovered and loaded
+automatically when the cmd2.Cmd class is instantiated with this mixin. This also enables the developer to
+dynamically add/remove commands from the cmd2 application. This could be useful for loadable plugins that
+add additional capabilities.
+
+Features
+~~~~~~~~
+
+* Modular Command Sets - Commands can be broken into separate modules rather than in one god class holding all commands.
+* Automatic Command Discovery - In your application, merely defining and importing a CommandSet is sufficient for
+ cmd2 to discover and load your command. No manual registration is necessary.
+* Dynamically Loadable/Unloadable Commands - Command functions and CommandSets can both be loaded and unloaded
+ dynamically during application execution. This can enable features such as dynamically loaded modules that
+ add additional commands.
+
+See the examples for more details: https://github.com/python-cmd2/cmd2/tree/master/plugins/command_sets/examples
+
+
+Defining Commands
+-----------------
+
+Command Sets
+~~~~~~~~~~~~~
+
+CommandSets group multiple commands together. The plugin will inspect functions within a ``CommandSet``
+using the same rules as when they're defined in ``cmd2.Cmd``. Commands must be prefixed with ``do_``, help
+functions with ``help_``, and completer functions with ``complete_``.
+
+A new decorator ``with_default_category`` is provided to categorize all commands within a CommandSet in the
+same command category. Individual commands in a CommandSet may be override the default category by specifying a
+specific category with ``cmd.with_category``.
+
+CommandSet methods will always expect ``self``, and ``cmd2.Cmd`` as the first two parameters. The parameters that
+follow will depend on the specific command decorator being used.
+
+CommandSets will only be auto-loaded if the constructor takes no arguments.
+If you need to provide constructor arguments, see :ref:`features/modular_commands:Manual CommandSet Construction`
+
+.. code-block:: python
+
+ import cmd2
+ from cmd2 import CommandSet, with_default_category
+
+ @with_default_category('My Category')
+ class AutoLoadCommandSet(CommandSet):
+ def __init__(self):
+ super().__init__()
+
+ def do_hello(self, cmd: cmd2.Cmd, _: cmd2.Statement):
+ cmd.poutput('Hello')
+
+ def do_world(self, cmd: cmd2.Cmd, _: cmd2.Statement):
+ cmd.poutput('World')
+
+ class ExampleApp(cmd2.Cmd):
+ """
+ CommandSets are automatically loaded. Nothing needs to be done.
+ """
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ def do_something(self, arg):
+ self.poutput('this is the something command')
+
+
+Manual CommandSet Construction
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If a CommandSet class requires parameters to be provided to the constructor, you man manually construct
+CommandSets and pass in the constructor to Cmd2.
+
+.. code-block:: python
+
+ import cmd2
+ from cmd2 import CommandSet, with_default_category
+
+ @with_default_category('My Category')
+ class CustomInitCommandSet(CommandSet):
+ def __init__(self, arg1, arg2):
+ super().__init__()
+
+ self._arg1 = arg1
+ self._arg2 = arg2
+
+ def do_show_arg1(self, cmd: cmd2.Cmd, _: cmd2.Statement):
+ cmd.poutput('Arg1: ' + self._arg1)
+
+ def do_show_arg2(self, cmd: cmd2.Cmd, _: cmd2.Statement):
+ cmd.poutput('Arg2: ' + self._arg2)
+
+ class ExampleApp(cmd2.Cmd):
+ """
+ CommandSets with constructor parameters are provided in the constructor
+ """
+ def __init__(self, *args, **kwargs):
+ # gotta have this or neither the plugin or cmd2 will initialize
+ super().__init__(*args, **kwargs)
+
+ def do_something(self, arg):
+ self.last_result = 5
+ self.poutput('this is the something command')
+
+
+ def main():
+ my_commands = CustomInitCommandSet(1, 2)
+ app = ExampleApp(command_sets=[my_commands])
+ app.cmdloop()
+
+
+Dynamic Commands
+~~~~~~~~~~~~~~~~
+
+You man also dynamically load and unload commands by installing and removing CommandSets at runtime. For example,
+if you could support runtime loadable plugins or add/remove commands based on your state.
+
+You may need to disable command auto-loading if you need dynamically load commands at runtime.
+
+.. code-block:: python
+
+ import argparse
+ import cmd2
+ from cmd2 import CommandSet, with_argparser, with_category, with_default_category
+
+
+ @with_default_category('Fruits')
+ class LoadableFruits(CommandSet):
+ def __init__(self):
+ super().__init__()
+
+ def do_apple(self, cmd: cmd2.Cmd, _: cmd2.Statement):
+ cmd.poutput('Apple')
+
+ def do_banana(self, cmd: cmd2.Cmd, _: cmd2.Statement):
+ cmd.poutput('Banana')
+
+
+ @with_default_category('Vegetables')
+ class LoadableVegetables(CommandSet):
+ def __init__(self):
+ super().__init__()
+
+ def do_arugula(self, cmd: cmd2.Cmd, _: cmd2.Statement):
+ cmd.poutput('Arugula')
+
+ def do_bokchoy(self, cmd: cmd2.Cmd, _: cmd2.Statement):
+ cmd.poutput('Bok Choy')
+
+
+ class ExampleApp(cmd2.Cmd):
+ """
+ CommandSets are loaded via the `load` and `unload` commands
+ """
+
+ def __init__(self, *args, **kwargs):
+ # gotta have this or neither the plugin or cmd2 will initialize
+ super().__init__(*args, auto_load_commands=False, **kwargs)
+
+ self._fruits = LoadableFruits()
+ self._vegetables = LoadableVegetables()
+
+ load_parser = cmd2.Cmd2ArgumentParser('load')
+ load_parser.add_argument('cmds', choices=['fruits', 'vegetables'])
+
+ @with_argparser(load_parser)
+ @with_category('Command Loading')
+ def do_load(self, ns: argparse.Namespace):
+ if ns.cmds == 'fruits':
+ try:
+ self.install_command_set(self._fruits)
+ self.poutput('Fruits loaded')
+ except ValueError:
+ self.poutput('Fruits already loaded')
+
+ if ns.cmds == 'vegetables':
+ try:
+ self.install_command_set(self._vegetables)
+ self.poutput('Vegetables loaded')
+ except ValueError:
+ self.poutput('Vegetables already loaded')
+
+ @with_argparser(load_parser)
+ def do_unload(self, ns: argparse.Namespace):
+ if ns.cmds == 'fruits':
+ self.uninstall_command_set(self._fruits)
+ self.poutput('Fruits unloaded')
+
+ if ns.cmds == 'vegetables':
+ self.uninstall_command_set(self._vegetables)
+ self.poutput('Vegetables unloaded')
+
+
+ if __name__ == '__main__':
+ app = ExampleApp()
+ app.cmdloop()