File: command_sets.py

package info (click to toggle)
cmd2 3.2.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 2,664 kB
  • sloc: python: 17,488; makefile: 114; sh: 39; javascript: 7
file content (162 lines) | stat: -rwxr-xr-x 6,202 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/usr/bin/env python3
"""Example revolving around the CommandSet feature for modularizing commands.

It attempts to cover basic usage as well as more complex usage including dynamic loading and unloading of CommandSets, using
CommandSets to add subcommands, as well as how to categorize command in CommandSets. Here we have kept the implementation for
most commands trivial because the intent is to focus on the CommandSet feature set.

The `AutoLoadCommandSet` is a basic command set which is loaded automatically at application startup and stays loaded until
application exit. Ths is the simplest case of simply modularizing command definitions to different classes and/or files.

The `LoadableFruits` and `LoadableVegetables` CommandSets are dynamically loadable and un-loadable at runtime using the `load`
and `unload` commands. This demonstrates the ability to load and unload CommandSets based on application state. Each of these
also loads a subcommand of the `cut` command.
"""

import argparse

import cmd2
from cmd2 import (
    CommandSet,
    with_argparser,
    with_category,
    with_default_category,
)

COMMANDSET_BASIC = "Basic CommandSet"
COMMANDSET_DYNAMIC = "Dynamic CommandSet"
COMMANDSET_LOAD_UNLOAD = "Loading and Unloading CommandSets"
COMMANDSET_SUBCOMMAND = "Subcommands with CommandSet"


@with_default_category(COMMANDSET_BASIC)
class AutoLoadCommandSet(CommandSet):
    def __init__(self) -> None:
        """CommandSet class for auto-loading commands at startup."""
        super().__init__()

    def do_hello(self, _: cmd2.Statement) -> None:
        """Print hello."""
        self._cmd.poutput('Hello')

    def do_world(self, _: cmd2.Statement) -> None:
        """Print World."""
        self._cmd.poutput('World')


@with_default_category(COMMANDSET_DYNAMIC)
class LoadableFruits(CommandSet):
    def __init__(self) -> None:
        """CommandSet class for dynamically loading commands related to fruits."""
        super().__init__()

    def do_apple(self, _: cmd2.Statement) -> None:
        """Print Apple."""
        self._cmd.poutput('Apple')

    def do_banana(self, _: cmd2.Statement) -> None:
        """Print Banana."""
        self._cmd.poutput('Banana')

    banana_description = "Cut a banana"
    banana_parser = cmd2.Cmd2ArgumentParser(description=banana_description)
    banana_parser.add_argument('direction', choices=['discs', 'lengthwise'])

    @cmd2.as_subcommand_to('cut', 'banana', banana_parser, help=banana_description.lower())
    def cut_banana(self, ns: argparse.Namespace) -> None:
        """Cut banana."""
        self._cmd.poutput('cutting banana: ' + ns.direction)


@with_default_category(COMMANDSET_DYNAMIC)
class LoadableVegetables(CommandSet):
    def __init__(self) -> None:
        """CommandSet class for dynamically loading commands related to vegetables."""
        super().__init__()

    def do_arugula(self, _: cmd2.Statement) -> None:
        "Print Arguula."
        self._cmd.poutput('Arugula')

    def do_bokchoy(self, _: cmd2.Statement) -> None:
        """Print Bok Choy."""
        self._cmd.poutput('Bok Choy')

    bokchoy_description = "Cut some bokchoy"
    bokchoy_parser = cmd2.Cmd2ArgumentParser(description=bokchoy_description)
    bokchoy_parser.add_argument('style', choices=['quartered', 'diced'])

    @cmd2.as_subcommand_to('cut', 'bokchoy', bokchoy_parser, help=bokchoy_description.lower())
    def cut_bokchoy(self, ns: argparse.Namespace) -> None:
        """Cut bokchoy."""
        self._cmd.poutput('Bok Choy: ' + ns.style)


class CommandSetApp(cmd2.Cmd):
    """CommandSets are automatically loaded. Nothing needs to be done."""

    def __init__(self) -> None:
        """Cmd2 application for demonstrating the CommandSet features."""
        # This prevents all CommandSets from auto-loading, which is necessary if you don't want some to load at startup
        super().__init__(auto_load_commands=False)

        self.register_command_set(AutoLoadCommandSet())

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

        self.intro = 'The CommandSet feature allows defining commands in multiple files and the dynamic load/unload at runtime'

    load_parser = cmd2.Cmd2ArgumentParser()
    load_parser.add_argument('cmds', choices=['fruits', 'vegetables'])

    @with_argparser(load_parser)
    @with_category(COMMANDSET_LOAD_UNLOAD)
    def do_load(self, ns: argparse.Namespace) -> None:
        """Load a CommandSet at runtime."""
        if ns.cmds == 'fruits':
            try:
                self.register_command_set(self._fruits)
                self.poutput('Fruits loaded')
            except ValueError:
                self.poutput('Fruits already loaded')

        if ns.cmds == 'vegetables':
            try:
                self.register_command_set(self._vegetables)
                self.poutput('Vegetables loaded')
            except ValueError:
                self.poutput('Vegetables already loaded')

    @with_argparser(load_parser)
    @with_category(COMMANDSET_LOAD_UNLOAD)
    def do_unload(self, ns: argparse.Namespace) -> None:
        """Unload a CommandSet at runtime."""
        if ns.cmds == 'fruits':
            self.unregister_command_set(self._fruits)
            self.poutput('Fruits unloaded')

        if ns.cmds == 'vegetables':
            self.unregister_command_set(self._vegetables)
            self.poutput('Vegetables unloaded')

    cut_parser = cmd2.Cmd2ArgumentParser()
    cut_subparsers = cut_parser.add_subparsers(title='item', help='item to cut')

    @with_argparser(cut_parser)
    @with_category(COMMANDSET_SUBCOMMAND)
    def do_cut(self, ns: argparse.Namespace) -> None:
        """Intended to be used with dyanmically loaded subcommands specifically."""
        handler = ns.cmd2_handler.get()
        if handler is not None:
            handler(ns)
        else:
            # No subcommand was provided, so call help
            self.poutput('This command does nothing without sub-parsers registered')
            self.do_help('cut')


if __name__ == '__main__':
    app = CommandSetApp()
    app.cmdloop()