Statistics
| Branch: | Tag: | Revision:

root / docs / developers / adding-commands.rst @ a2ef112e

History | View | Annotate | Download (10.2 kB)

1
Adding Commands
2
===============
3

    
4
Kamaki commands are implemented as python classes, decorated with a special decorator called *command*. This decorator is a method of kamaki.cli that adds a new command in a CommandTree structure (kamaki.cli.commant_tree). The later is used by interfaces to manage kamaki commands.
5

    
6
In the following, a set of kamaki commands will be implemented::
7

    
8
    mygrp1 list all                         //show a list
9
    mygrp1 list details [--match=<>]        //show list of details
10
    mygrp2 list all [regular expression] [-l]       //list all subjects
11
    mygrp2 info <id> [name]      //get information for subject with id
12

    
13
There are two command sets to implement, namely mygrp1 and mygrp2. The first will contain two commands, namely list-all and list-details. The second one will also contain two commands, list-all and info. To avoid ambiguities, command names should rather be prefixed with the group they belong to, e.g. mygrp1-list-all and mygrp2-list-all.
14

    
15
The first command has the simplest possible syntax: no parameters, no runtime arguments. The second accepts an optional runtime argument with a value. The third features an optional argument and an optional runtime flag argument. The last is an example of a command with an obligatory and an optional argument.
16

    
17
Samples of the expected behavior in one-command mode are following:
18

    
19
.. code-block:: console
20

    
21
    $kamaki mygrp1
22
        mygrp1 description
23

    
24
        Options
25
         - - - -
26
        list
27
    $ kamaki mygrp1 list
28
        Syntax Error
29

    
30
        Options
31
         - - - -
32
        all        show a list
33
        details     show a list of details
34
    $ kamaki mygrp1 list all
35
        ... (mygrp1 client method is called) ...
36
    $ kamaki mygrp2 list all 'Z[.]' -l
37
        ... (mygrp2 client method is called) ...
38
    $
39

    
40
The above example will be used throughout the present guide for clarification purposes.
41

    
42
The CommandTree structure
43
-------------------------
44

    
45
CommandTree manages a command by its path. Each command is stored in multiple nodes on the tree, so that the last term is a leaf and the route from root to that leaf represents the command path. For example the commands *store upload*, *store list* and *store info* are stored together as shown bellow::
46

    
47
    - store
48
    ''''''''|- info
49
            |- list
50
            |- upload
51

    
52
The example used in the present, should result to the creation of two trees::
53

    
54
    - mygrp1
55
    ''''''''|- list
56
            '''''''|- all
57
                   |- details
58

    
59
    - mygrp2
60
    ''''''''|- list
61
            '''''''|- all
62
            |- info
63

    
64
Each command group should be stored on a different CommandTree. For that reason, command specification modules should contain a list of CommandTree objects, named *_commands*
65

    
66
A command group information (name, description) is provided at CommandTree structure initialization:
67

    
68
.. code-block:: python
69

    
70
    _mygrp1_commands = CommandTree('mygrp', 'mygrp1 description')
71
    _mygrp2_commands = CommandTree('mygrp', 'mygrp2 description')
72

    
73
    _commands = [_mygrp1_commands, _mygrp2_commands]
74

    
75
The command decorator
76
---------------------
77

    
78
The *command* decorator mines all the information necessary to build a command specification which is then inserted in a CommanTree instance::
79

    
80
    class code  --->  command()  -->  updated CommandTree structure
81

    
82
Kamaki interfaces make use of this CommandTree structure. Optimizations are possible by using special parameters on the command decorator method.
83

    
84
.. code-block:: python
85

    
86
    def command(cmd_tree, prefix='', descedants_depth=None):
87
    """Load a class as a command
88
        @cmd_tree is the CommandTree to be updated with a new command
89
        @prefix of the commands allowed to be inserted ('' for all)
90
        @descedants_depth is the depth of the tree descedants of the
91
            prefix command.
92
    """
93

    
94
Creating a new command specification set
95
----------------------------------------
96

    
97
A command specification developer should create a new module (python file) with as many classes as the command specifications to be offered. Each class should be decorated with *command*.
98

    
99
.. code-block:: python
100

    
101
    ...
102
    _commands = [_mygrp1_commands, _mygrp2_commands]
103

    
104
    @command(_mygrp1_commands)
105
    class mygrp1_list_all():
106
        ...
107

    
108
    ...
109

    
110
A list of CommandTree structures must exist in the module scope, with the name _commands, as shown above. Different CommandTree objects correspond to different command groups.
111

    
112
Get command description
113
-----------------------
114

    
115
The description of each command is the first line of the class commend. The following declaration of *mygrp2-info* command has a "*get information for subject with id*" description.
116

    
117
.. code-block:: python
118

    
119
    ...
120
    @command(_mygrp2_commands)
121
    class mygrp2_info()
122
        """get information for subject with id"""
123
        ...
124

    
125
Declare run-time argument
126
-------------------------
127

    
128
The argument mechanism allows the definition of run-time arguments. Some basic argument types are defined at the `argument module <cli.html#module-kamaki.cli.argument>`_, but it is not uncommon to extent these classes in order to achieve specialized type checking and syntax control (e.g. at `pithos_cli module <cli.html#module-kamaki.cli.commands.pithos_cli>`_).
129

    
130
To declare a run-time argument on a specific command, the object class should initialize a dict called *arguments* , where Argument objects are stored. Each argument object is a possible run-time argument. Syntax checking happens at client level, while the type checking is implemented in the Argument code (thus, many different Argument types might be needed).
131

    
132
.. code-block:: python
133

    
134
    from kamaki.cli.argument import ValueArgument
135
    ...
136

    
137
    @command(_mygrp1_commands)
138
    class mygrp1_list_details():
139
        """list of details"""
140

    
141
        def __init__(self, global_args={})
142
            global_args['match'] = ValueArgument(
143
                'Filter results to match string',
144
                '--match')
145
            self.arguments = global_args
146

    
147
The main method and command parameters
148
--------------------------------------
149

    
150
The command behavior for each command / class is coded in *main*. The parameters of *main* method defines the command parameters part of the syntax. In specific::
151

    
152
    main(self, param)                   - obligatory parameter
153
    main(self, param=None)              - optional parameter
154
    main(self, param1, param2=42)       - <param1> [param2]
155
    main(self, param1____param2)        - <param1:param2>
156
    main(self, param1____param2=[])     - [param1:param2]
157
    main(self, param1____param2__)      - <param1[:param2]>
158
    main(self, param1____param2__='')   - [param1[:param2]]
159
    main(self, *args)                   - arbitary number of params [...]
160
    main(self, param1____param2, *args) - <param1:param2> [...]
161

    
162
The information that can be mined by *command* for each individual command is presented in the following:
163

    
164
.. code-block:: python
165
    :linenos:
166

    
167
    from kamaki.cli.argument import FlagArgument
168
    ...
169

    
170
    _commands = [_mygrp1_commands, _mygrp2=commands]
171
    ...
172

    
173
    @command(_mygrp2_commands)
174
    class mygrp2_list_all(object):
175
        """List all subjects"""
176

    
177
        def __init__(self, global_args={}):
178
            global_args['list'] = FlagArgument(
179
                'detailed list',
180
                '-l,
181
                False)
182

    
183
            self.arguments = global_args
184

    
185
        def main(self, reg_exp=None):
186
            ...
187

    
188
This will load the following information on the CommandTree:
189

    
190
* Syntax (from lines 8,12,19): mygrp list all [reg exp] [-l]
191
* Description (form line 9): List all subjects
192
* Arguments help (from line 13,14): -l: detailed list
193

    
194
Letting kamaki know
195
-------------------
196

    
197
Kamaki will load a command specification *only* if it is set as a configurable option. To demonstrate this, let the command specifications coded above be stored in a file named *grps.py*.
198

    
199
The developer should move file *grps.py* to kamaki/cli/commands, the default place for command specifications, although running a command specification from a different path is also a kamaki feature.
200

    
201
The user has to use a configuration file where the following is added:
202
::
203

    
204
    [mygrp1]
205
    cli=grps
206

    
207
    [mygrp2]
208
    cli=grps
209

    
210
or alternatively:
211

    
212
.. code-block:: console
213

    
214
    $ kamaki config set mygrp1.cli = grps
215
    $ kamaki config set mygrp2.cli = grps
216

    
217
Command specification modules don't need to live in kamaki/cli/commands, although this is suggested for uniformity. If a command module exist in another path::
218

    
219
    [mygrp]
220
    cli=/another/path/grps.py
221

    
222
Summary: create a command set
223
-----------------------------
224

    
225
.. code-block:: python
226

    
227
    #  File: grps.py
228

    
229
    from kamaki.cli.command_tree import CommandTree
230
    from kamaki.cli.argument import ValueArgument, FlagArgument
231
    ...
232

    
233

    
234
    #  Initiallize command trees
235

    
236
    _mygrp1_commands = CommandTree('mygrp', 'mygrp1 description')
237
    _mygrp2_commands = CommandTree('mygrp', 'mygrp2 description')
238

    
239
    _commands = [_mygrp1_commands, _mygrp2_commands]
240

    
241

    
242
    #  Define command specifications
243

    
244

    
245
    @command(_mygrp1_commands)
246
    class mygrp1_list_all():
247
        """show a list"""
248

    
249
        arguments = {}
250

    
251
        def main(self):
252
            ...
253

    
254

    
255
    @command(_mygrp1_commands)
256
    class mygrp1_list_details():
257
        """show list of details"""
258

    
259
        arguments = {}
260

    
261
        def __init__(self, global_args={})
262
            global_args['match'] = ValueArgument(
263
                'Filter results to match string',
264
                '--match')
265
            self.arguments = global_args
266

    
267
        def main(self):
268
            ...
269
            match_value = self.arguments['list'].value
270
            ...
271

    
272

    
273
    @command(_mygrp2_commands)
274
    class mygrp2_list_all():
275
        """list all subjects"""
276

    
277
        arguments = {}
278

    
279
        def __init__(self, global_args={})
280
            global_args['match'] = FlagArgument('detailed listing', '-l')
281
            self.arguments = global_args
282

    
283
        def main(self, regular_expression=None):
284
            ...
285
            detail_flag = self.arguments['list'].value
286
            ...
287
            if detail_flag:
288
                ...
289
            ...
290
            if regular_expression:
291
                ...
292
            ...
293

    
294

    
295
    @command(_mygrp2_commands)
296
    class mygrp2_info():
297
        """get information for subject with id"""
298

    
299
        arguments = {}
300

    
301
        def main(self, id, name=''):
302
            ...