Revision 16d7b9ff docs/developers/adding-commands.rst

b/docs/developers/adding-commands.rst
2 2
===============
3 3

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

  
9
In the following, a set of kamaki commands will be implemented::
10
For demonstration purposes, the following set of kamaki commands will be
11
implemented in this document::
10 12

  
11 13
    mygrp1 list all                         //show a list
12 14
    mygrp1 list details [--match=<>]        //show list of details
13 15
    mygrp2 list all [regular expression] [-l]       //list all subjects
14 16
    mygrp2 info <id> [name]      //get information for subject with id
15 17

  
16
There are two command sets to implement, namely mygrp1 and mygrp2. The first
17
will contain two commands, namely list-all and list-details. The second one
18
will also contain two commands, list-all and info. To avoid ambiguities,
19
command names should rather be prefixed with the group they belong to, e.g.
20
mygrp1-list-all and mygrp2-list-all.
18
There are two command groups to implement i.e., *mygrp1* and *mygrp2*,
19
containing two commands each (*list_all*, *list_details* and *list_all*, *info*
20
respectively). To avoid ambiguities, command names are prefixed with the
21
command group they belong to, e.g., *mygrp1_list_all* and *mygrp2_list_all*.
22
The underscore is used to separate command namespaces.
21 23

  
22
The first command has the simplest possible syntax: no parameters, no runtime
23
arguments. The second accepts an optional runtime argument with a value. The
24
third features an optional argument and an optional runtime flag argument. The
25
last is an example of a command with an obligatory and an optional argument.
24
The first command (*mygrp1_list_all*) has the simplest possible syntax: no
25
parameters, no runtime arguments. The second accepts an optional runtime argument with a value. The third features an optional parameter and an optional
26
runtime flag argument. The last is an example of a command with an obligatory
27
and an optional parameter.
26 28

  
27
Samples of the expected behavior in one-command mode are following:
29
Examples of the expected behavior in one-command mode:
28 30

  
29 31
.. code-block:: console
30 32

  
......
35 37
         - - - -
36 38
        list
37 39
    $ kamaki mygrp1 list
38
        Syntax Error
39 40

  
40 41
        Options
41 42
         - - - -
42 43
        all        show a list
43 44
        details     show a list of details
44 45
    $ kamaki mygrp1 list all
45
        ... (mygrp1 client method is called) ...
46
        ... (a mygrp1_list_all instance runs) ...
46 47
    $ kamaki mygrp2 list all 'Z[.]' -l
47
        ... (mygrp2 client method is called) ...
48
        ... (a mygrp2_list_all instance runs) ...
48 49
    $
49 50

  
50
The above example will be used throughout the present guide.
51

  
52 51
The CommandTree structure
53 52
-------------------------
54 53

  
55
CommandTree manages a command by its path. Each command is stored in multiple
56
nodes on the tree, so that the last term is a leaf and the route from root to
57
that leaf represents the command path. For example the commands *file upload*,
58
*file list* and *file info* are stored together as shown bellow::
54
CommandTree manages a command by its namespace. Each command is stored in
55
a tree path, where each node is a name. A leaf is the end term of a namespace and contains a pointer to the command class to be executed.
56

  
57
Here is an example from the actual kamaki command structure, where the commands
58
*file upload*, *file list* and *file info* are represented as shown bellow::
59 59

  
60 60
    - file
61 61
    ''''''''|- info
62 62
            |- list
63 63
            |- upload
64 64

  
65
The example used in the present, should result to the creation of two trees::
65
Now, let's load the showcase example on CommandTrees::
66 66

  
67 67
    - mygrp1
68 68
    ''''''''|- list
......
74 74
            '''''''|- all
75 75
            |- info
76 76

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

  
79
For that reason, command specification modules should contain a list of CommandTree objects, named *_commands*. This mechanism allows any interface
80
application to load the list of commands from the *_commands* array.
80 81

  
81
A command group information (name, description) is provided at CommandTree
82
structure initialization:
82
The first name of the command path and a description (name, description) are needed to initializeg a CommandTree:
83 83

  
84 84
.. code-block:: python
85 85

  
......
88 88

  
89 89
    _commands = [_mygrp1_commands, _mygrp2_commands]
90 90

  
91

  
91 92
The command decorator
92 93
---------------------
93 94

  
94
The *command* decorator mines all the information necessary to build a command
95
specification which is then inserted in a CommanTree instance::
95
All commands are specified by subclasses of *kamaki.cli.commands._command_init*
96
These classes are called "command specifications".
97

  
98
The *command* decorator mines all the information needed to build a namespace
99
from a command specification::
96 100

  
97 101
    class code  --->  command()  -->  updated CommandTree structure
98 102

  
99
Kamaki interfaces make use of this CommandTree structure. Optimizations are
103
Kamaki interfaces make use of the CommandTree structure. Optimizations are
100 104
possible by using special parameters on the command decorator method.
101 105

  
102 106
.. code-block:: python
......
108 112

  
109 113
        :param prefix: of the commands allowed to be inserted ('' for all)
110 114

  
111
        :param descedants_depth: is the depth of the tree descedants of the
115
        :param descedants_depth: is the depth of the tree descendants of the
112 116
            prefix command.
113 117
    """
114 118

  
115 119
Creating a new command specification set
116 120
----------------------------------------
117 121

  
118
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*.
122
A command specification developer should create a new module (python file) with
123
one command specification class per command. Each class should be decorated
124
with *command*.
119 125

  
120 126
.. code-block:: python
121 127

  
......
129 135
    ...
130 136

  
131 137
A list of CommandTree structures must exist in the module scope, with the name
132
_commands, as shown above. Different CommandTree objects correspond to
133
different command groups.
138
*_commands*. Different CommandTree objects correspond to different command
139
groups.
134 140

  
135
Get command description
141
Set command description
136 142
-----------------------
137 143

  
138 144
The description of each command is the first line of the class commend. The
......
143 149

  
144 150
    ...
145 151
    @command(_mygrp2_commands)
146
    class mygrp2_info()
147
        """get information for subject with id"""
152
    class mygrp2_info():
153
        """get information for subject with id
154
        Anything from this point and bellow constitutes the long description
155
        Please, mind the indentation, pep8 is not forgiving.
156
        """
148 157
        ...
149 158

  
159
Description placeholders
160
------------------------
161

  
162
There is possible to create an empty command, that can act as a description
163
placeholder. For example, the *mygrp1_list* namespace does not correspond to an
164
executable command, but it can have a helpful description. In that case, create
165
a command specification class with a command and no code:
166

  
167
.. code-block:: python
168

  
169
    @command(_mygrp1_commands)
170
    class mygrp1_list():
171
        """List mygrp1 objects.
172
        There are two versions: short and detailed
173
        """
174

  
175
.. warning:: A command specification class with no description is invalid and
176
    will cause an error.
177

  
150 178
Declare run-time argument
151 179
-------------------------
152 180

  
153
The argument mechanism allows the definition of run-time arguments. Some basic
154
argument types are defined at the
181
A special argument mechanism allows the definition of run-time arguments. This
182
mechanism is based on argparse and is designed to simplify argument definitions
183
when specifying commands.
184

  
185
Some basic argument types are defined at the
155 186
`argument module <code.html#module-kamaki.cli.argument>`_, but it is not
156
uncommon to extent these classes in order to achieve specialized type checking
157
and syntax control (e.g. at
158
`pithos cli module <code.html#module-kamaki.cli.commands.pithos>`_).
187
a bad idea to extent these classes in order to achieve specialized type
188
checking and syntax control. Still, in most cases, the argument types of the
189
argument package are enough for most cases.
159 190

  
160
To declare a run-time argument on a specific command, the object class should
161
initialize a dict called *arguments* , where Argument objects are stored. Each
162
argument object is a possible run-time argument. Syntax checking happens at
163
client level, while the type checking is implemented in the Argument code
164
(thus, many different Argument types might be needed).`
191
To declare a run-time argument on a specific command, the specification class
192
should contain a dict called *arguments* , where Argument objects are stored.
193
Each argument object is a run-time argument. Syntax checking happens at client
194
level, while the type checking is implemented in the Argument code (e.g.,
195
IntArgument checks if the value is an int).
165 196

  
166 197
.. code-block:: python
167 198

  
......
190 221

  
191 222
        arguments = dict(
192 223
            match=ValueArgument(
193
            'Filter output to match string', ('-m', --match'))
224
                'Filter output to match string', ('-m', --match'))
194 225
        )
195 226

  
196 227
Accessing run-time arguments
197 228
----------------------------
198 229

  
199
To access run-time arguments, users can use the _command_init interface, which
200
implements __item__ accessors to handle run-time argument values. In specific,
201
an instance of _command_init can use brackets to set or read <argument>.value .
230
To access run-time arguments, users can use the *_command_init* interface,
231
which implements *__item__* accessors to handle run-time argument values. In
232
other words, one may get the value of an argument with *self[<argument>]*.
202 233

  
203 234
.. code-block:: python
204 235

  
......
222 253
The main method and command parameters
223 254
--------------------------------------
224 255

  
225
The command behavior for each command / class is coded in *main*. The
226
parameters of *main* method defines the command parameters part of the syntax.
227
In specific::
256
The command behavior for each command class is coded in *main*. The
257
parameters of *main* method affect the syntax of the command. In specific::
228 258

  
229 259
    main(self, param)                   - obligatory parameter <param>
230 260
    main(self, param=None)              - optional parameter [param]
......
236 266
    main(self, *args)                   - arbitary number of params [...]
237 267
    main(self, param1____param2, *args) - <param1:param2> [...]
238 268

  
239
The information that can be mined by *command* for each individual command is
240
presented in the following:
269
Let's have a look at the command specification class again, and highlight the
270
parts that affect the command syntax:
241 271

  
242 272
.. code-block:: python
243 273
    :linenos:
......
245 275
    from kamaki.cli.argument import FlagArgument
246 276
    ...
247 277

  
248
    _commands = [_mygrp1_commands, _mygrp2=commands]
278
    _commands = [_mygrp1_commands, _mygrp2_commands]
249 279
    ...
250 280

  
251 281
    @command(_mygrp2_commands)
252 282
    class mygrp2_list_all():
253
        """List all subjects"""
283
        """List all subjects
284
        Refers to the subject accessible by current user
285
        """
254 286

  
255 287
        arguments = dict(FlagArgument('detailed list', '-l'))
256 288

  
257 289
        def main(self, reg_exp=None):
258 290
            ...
259 291

  
260
This will load the following information on the CommandTree:
292
The above lines contain the following information:
293

  
294
* Namespace and name (line 8): mygrp2 list all
295
* Short (line 9) and long (line 10) description
296
* Parameters (line 15): [reg exp]
297
* Runtime arguments (line 13): [-l]
298
* Runtime arguments help (line 13): detailed list
261 299

  
262
* Syntax (from lines 8,12,19): mygrp list all [reg exp] [-l]
263
* Description (form line 9): List all subjects
264
* Arguments help (from line 13,14): -l: detailed list
300
.. tip:: It is suggested to code the main functionality in a member method
301
    called *_run*. This allows the separation between syntax and logic. For
302
    example, an external library may need to call a command without caring
303
    about its command line behavior.
265 304

  
266 305
Letting kamaki know
267 306
-------------------
......
270 309
option. To demonstrate this, let the command specifications coded above be
271 310
stored in a file named *grps.py*.
272 311

  
273
The developer should move file *grps.py* to kamaki/cli/commands, the default
274
place for command specifications, although running a command specification from
275
a different path is also a kamaki feature.
312
The developer should move the file *grps.py* to *kamaki/cli/commands*, the
313
default place for command specifications
276 314

  
277
The user has to use a configuration file where the following is added:
315
These lines should be contained in the kamaki configuration file for a new
316
command specification module to work:
278 317
::
279 318

  
280 319
    [global]
......
288 327
    $ kamaki config set mygrp1_cli grps
289 328
    $ kamaki config set mygrp2_cli grps
290 329

  
291
Command specification modules don't need to live in kamaki/cli/commands,
292
although this is suggested for uniformity. If a command module exist in another
293
path::
330
.. note:: running a command specification from a different path is supported.
331
    To achieve this, add a *<group>_cli = </path/to/module>* line in the
332
    configure file under the *global* section.
333
::
294 334

  
295 335
    [global]
296 336
    mygrp_cli = /another/path/grps.py
......
320 360

  
321 361

  
322 362
    @command(_mygrp1_commands)
363
    class mygrp1_list(_command_init):
364
        """List mygrp1 objects.
365
        There are two versions: short and detailed
366
        """
367

  
368

  
369
    @command(_mygrp1_commands)
323 370
    class mygrp1_list_all(_command_init):
324 371
        """show a list"""
325 372

  
326
        def main(self):
373
        def _run():
327 374
            ...
328 375

  
376
        def main(self):
377
            self._run()
378

  
329 379

  
330 380
    @command(_mygrp1_commands)
331 381
    class mygrp1_list_details(_command_init):
......
336 386
                'Filter output to match string', ('-m', --match'))
337 387
        )
338 388

  
339
        def main(self):
340
            ...
389
        def _run(self):
341 390
            match_value = self['match']
342 391
            ...
343 392

  
393
        def main(self):
394
        self._run()
395

  
396

  
397
    #The following will also create a mygrp2_list command with no description
398

  
344 399

  
345 400
    @command(_mygrp2_commands)
346 401
    class mygrp2_list_all(_command_init):
......
350 405
            list=FlagArgument('detailed listing', '-l')
351 406
        )
352 407

  
353
        def main(self, regular_expression=None):
354
            ...
355
            detail_flag = self['list']
408
        def _run(self, regexp):
356 409
            ...
357
            if detail_flag:
410
            if self['list']:
358 411
                ...
359
            ...
360
            if regular_expression:
412
            else:
361 413
                ...
362
            ...
414

  
415
        def main(self, regular_expression=None):
416
            self._run(regular_expression)
363 417

  
364 418

  
365 419
    @command(_mygrp2_commands)
366 420
    class mygrp2_info(_command_init):
367 421
        """get information for subject with id"""
368 422

  
369
        def main(self, id, name=''):
423
        def _run(self, grp_id, grp_name):
370 424
            ...
425

  
426
        def main(self, id, name=''):
427
            self._run(id, name) 

Also available in: Unified diff