Skip to content

Commit d5f5c02

Browse files
committed
Create a new command_sets.py example
command_sets.py merges three previous examples: - modular_commands_basic.py - modular_commands_dynamic.py - modular_subcommands.py
1 parent d39c643 commit d5f5c02

File tree

5 files changed

+66
-150
lines changed

5 files changed

+66
-150
lines changed

examples/README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ each:
2525
- Demonstrates how to accept and parse command-line arguments when invoking a cmd2 application
2626
- [color.py](https://github.com/python-cmd2/cmd2/blob/main/examples/color.py)
2727
- Show the numerous colors available to use in your cmd2 applications
28+
- [command_sets.py](https://github.com/python-cmd2/cmd2/blob/main/examples/command_sets.py)
29+
- Example that demonstrates the `CommandSet` features for modularizing commands and demonstrates
30+
all main capabilities including basic CommandSets, dynamic loading an unloading, using
31+
subcommands, etc.
2832
- [custom_parser.py](https://github.com/python-cmd2/cmd2/blob/main/examples/custom_parser.py)
2933
- Demonstrates how to create your own custom `Cmd2ArgumentParser`
3034
- [default_categories.py](https://github.com/python-cmd2/cmd2/blob/main/examples/default_categories.py)
@@ -52,15 +56,9 @@ each:
5256
- Shows how to use various `cmd2` application lifecycle hooks
5357
- [migrating.py](https://github.com/python-cmd2/cmd2/blob/main/examples/migrating.py)
5458
- A simple `cmd` application that you can migrate to `cmd2` by changing one line
55-
- [modular_commands_basic.py](https://github.com/python-cmd2/cmd2/blob/main/examples/modular_commands_basic.py)
56-
- Demonstrates based `CommandSet` usage
57-
- [modular_commands_dynamic.py](https://github.com/python-cmd2/cmd2/blob/main/examples/modular_commands_dynamic.py)
58-
- Demonstrates dynamic `CommandSet` loading and unloading
59-
- [modular_commands_main.py](https://github.com/python-cmd2/cmd2/blob/main/examples/modular_commands_main.py)
59+
- [modular_commands.py](https://github.com/python-cmd2/cmd2/blob/main/examples/modular_commands.py)
6060
- Complex example demonstrating a variety of methods to load `CommandSets` using a mix of
6161
command decorators
62-
- [modular_subcommands.py](https://github.com/python-cmd2/cmd2/blob/main/examples/modular_subcommands.py)
63-
- Shows how to dynamically add and remove subcommands at runtime using `CommandSets`
6462
- [paged_output.py](https://github.com/python-cmd2/cmd2/blob/main/examples/paged_output.py)
6563
- Shows how to use output pagination within `cmd2` apps via the `ppaged` method
6664
- [persistent_history.py](https://github.com/python-cmd2/cmd2/blob/main/examples/persistent_history.py)

examples/modular_subcommands.py renamed to examples/command_sets.py

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
#!/usr/bin/env python3
2-
"""A simple example demonstrating modular subcommand loading through CommandSets.
2+
"""This example revolves around the CommandSet feature for modularizing commands.
33
4-
In this example, there are loadable CommandSets defined. Each CommandSet has 1 subcommand defined that will be
5-
attached to the 'cut' command.
4+
It attempts to cover basic usage as well as more complex usage including dynamic loading and unloading of CommandSets, using
5+
CommandSets to add subcommands, as well as how to categorize command in CommandSets. Here we have kept the implementation for
6+
most commands trivial because the intent is to focus on the CommandSet feature set.
67
7-
The cut command is implemented with the `do_cut` function that has been tagged as an argparse command.
8+
The `AutoLoadCommandSet` is a basic command set which is loaded automatically at application startup and stays loaded until
9+
application exit. Ths is the simplest case of simply modularizing command definitions to different classes and/or files.
810
9-
The `load` and `unload` command will load and unload the CommandSets. The available top level commands as well as
10-
subcommands to the `cut` command will change depending on which CommandSets are loaded.
11+
The `LoadableFruits` and `LoadableVegetables` CommandSets are dynamically loadable and un-loadable at runtime using the `load`
12+
and `unload` commands. This demonstrates the ability to load and unload CommandSets based on application state. Each of these
13+
also loads a subcommand of the `cut` command.
1114
"""
1215

1316
import argparse
@@ -20,15 +23,39 @@
2023
with_default_category,
2124
)
2225

26+
COMMANDSET_BASIC = "Basic CommandSet"
27+
COMMANDSET_DYNAMIC = "Dynamic CommandSet"
28+
COMMANDSET_LOAD_UNLOAD = "Loading and Unloading CommandSets"
29+
COMMANDSET_SUBCOMMAND = "Subcommands with CommandSet"
2330

24-
@with_default_category('Fruits')
31+
32+
@with_default_category(COMMANDSET_BASIC)
33+
class AutoLoadCommandSet(CommandSet):
34+
def __init__(self) -> None:
35+
super().__init__()
36+
37+
def do_hello(self, _: cmd2.Statement) -> None:
38+
"""Prints hello."""
39+
self._cmd.poutput('Hello')
40+
41+
def do_world(self, _: cmd2.Statement) -> None:
42+
"""Prints World."""
43+
self._cmd.poutput('World')
44+
45+
46+
@with_default_category(COMMANDSET_DYNAMIC)
2547
class LoadableFruits(CommandSet):
2648
def __init__(self) -> None:
2749
super().__init__()
2850

2951
def do_apple(self, _: cmd2.Statement) -> None:
52+
"""Prints Apple."""
3053
self._cmd.poutput('Apple')
3154

55+
def do_banana(self, _: cmd2.Statement) -> None:
56+
"""Prints Banana"""
57+
self._cmd.poutput('Banana')
58+
3259
banana_description = "Cut a banana"
3360
banana_parser = cmd2.Cmd2ArgumentParser(description=banana_description)
3461
banana_parser.add_argument('direction', choices=['discs', 'lengthwise'])
@@ -39,38 +66,48 @@ def cut_banana(self, ns: argparse.Namespace) -> None:
3966
self._cmd.poutput('cutting banana: ' + ns.direction)
4067

4168

42-
@with_default_category('Vegetables')
69+
@with_default_category(COMMANDSET_DYNAMIC)
4370
class LoadableVegetables(CommandSet):
4471
def __init__(self) -> None:
4572
super().__init__()
4673

4774
def do_arugula(self, _: cmd2.Statement) -> None:
75+
"Prints Arguula."
4876
self._cmd.poutput('Arugula')
4977

78+
def do_bokchoy(self, _: cmd2.Statement) -> None:
79+
"""Prints Bok Choy."""
80+
self._cmd.poutput('Bok Choy')
81+
5082
bokchoy_description = "Cut some bokchoy"
5183
bokchoy_parser = cmd2.Cmd2ArgumentParser(description=bokchoy_description)
5284
bokchoy_parser.add_argument('style', choices=['quartered', 'diced'])
5385

5486
@cmd2.as_subcommand_to('cut', 'bokchoy', bokchoy_parser, help=bokchoy_description.lower())
55-
def cut_bokchoy(self, _: argparse.Namespace) -> None:
56-
self._cmd.poutput('Bok Choy')
87+
def cut_bokchoy(self, ns: argparse.Namespace) -> None:
88+
self._cmd.poutput('Bok Choy: ' + ns.style)
5789

5890

59-
class ExampleApp(cmd2.Cmd):
91+
class CommandSetApp(cmd2.Cmd):
6092
"""CommandSets are automatically loaded. Nothing needs to be done."""
6193

62-
def __init__(self, *args, **kwargs) -> None:
63-
# gotta have this or neither the plugin or cmd2 will initialize
64-
super().__init__(*args, auto_load_commands=False, **kwargs)
94+
def __init__(self) -> None:
95+
# This prevents all CommandSets from auto-loading, which is necessary if you don't want some to load at startup
96+
super().__init__(auto_load_commands=False)
97+
98+
self.register_command_set(AutoLoadCommandSet())
6599

100+
# Store the dyanmic CommandSet classes for ease of loading and unloading
66101
self._fruits = LoadableFruits()
67102
self._vegetables = LoadableVegetables()
68103

104+
self.intro = 'The CommandSet feature allows defining commands in multiple files and the dynamic load/unload at runtime'
105+
69106
load_parser = cmd2.Cmd2ArgumentParser()
70107
load_parser.add_argument('cmds', choices=['fruits', 'vegetables'])
71108

72109
@with_argparser(load_parser)
73-
@with_category('Command Loading')
110+
@with_category(COMMANDSET_LOAD_UNLOAD)
74111
def do_load(self, ns: argparse.Namespace) -> None:
75112
if ns.cmds == 'fruits':
76113
try:
@@ -87,6 +124,7 @@ def do_load(self, ns: argparse.Namespace) -> None:
87124
self.poutput('Vegetables already loaded')
88125

89126
@with_argparser(load_parser)
127+
@with_category(COMMANDSET_LOAD_UNLOAD)
90128
def do_unload(self, ns: argparse.Namespace) -> None:
91129
if ns.cmds == 'fruits':
92130
self.unregister_command_set(self._fruits)
@@ -100,8 +138,9 @@ def do_unload(self, ns: argparse.Namespace) -> None:
100138
cut_subparsers = cut_parser.add_subparsers(title='item', help='item to cut')
101139

102140
@with_argparser(cut_parser)
141+
@with_category(COMMANDSET_SUBCOMMAND)
103142
def do_cut(self, ns: argparse.Namespace) -> None:
104-
# Call handler for whatever subcommand was selected
143+
"""Intended to be used with dyanmically loaded subcommands specifically."""
105144
handler = ns.cmd2_handler.get()
106145
if handler is not None:
107146
handler(ns)
@@ -112,5 +151,5 @@ def do_cut(self, ns: argparse.Namespace) -> None:
112151

113152

114153
if __name__ == '__main__':
115-
app = ExampleApp()
154+
app = CommandSetApp()
116155
app.cmdloop()

examples/modular_commands_main.py renamed to examples/modular_commands.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
2-
"""A complex example demonstrating a variety of methods to load CommandSets using a mix of command decorators
3-
with examples of how to integrate tab completion with argparse-based commands.
2+
"""A complex example demonstrating a variety of methods to load CommandSets using a mix of command decorators.
3+
4+
Includes examples of how to integrate tab completion with argparse-based commands.
45
"""
56

67
import argparse
@@ -26,6 +27,7 @@
2627

2728
class WithCommandSets(Cmd):
2829
def __init__(self, command_sets: Iterable[CommandSet] | None = None) -> None:
30+
"""Cmd2 application to demonstrate a variety of methods for loading CommandSets."""
2931
super().__init__(command_sets=command_sets)
3032
self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball']
3133

@@ -54,7 +56,7 @@ def choices_provider(self) -> list[str]:
5456

5557
@with_argparser(example_parser)
5658
def do_example(self, _: argparse.Namespace) -> None:
57-
"""The example command."""
59+
"""An example command."""
5860
self.poutput("I do nothing")
5961

6062

examples/modular_commands_basic.py

Lines changed: 0 additions & 35 deletions
This file was deleted.

examples/modular_commands_dynamic.py

Lines changed: 0 additions & 88 deletions
This file was deleted.

0 commit comments

Comments
 (0)