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
1316import argparse
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 )
2547class 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 )
4370class 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
114153if __name__ == '__main__' :
115- app = ExampleApp ()
154+ app = CommandSetApp ()
116155 app .cmdloop ()
0 commit comments