Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11 kB)

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

    
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.
8

    
9
In the following, a set of kamaki commands will be implemented::
10

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

    
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.
21

    
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.
26

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

    
29
.. code-block:: console
30

    
31
    $kamaki mygrp1
32
        mygrp1 description
33

    
34
        Options
35
         - - - -
36
        list
37
    $ kamaki mygrp1 list
38
        Syntax Error
39

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

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

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

    
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::
59

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

    
65
The example used in the present, should result to the creation of two trees::
66

    
67
    - mygrp1
68
    ''''''''|- list
69
            '''''''|- all
70
                   |- details
71

    
72
    - mygrp2
73
    ''''''''|- list
74
            '''''''|- all
75
            |- info
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*
80

    
81
A command group information (name, description) is provided at CommandTree
82
structure initialization:
83

    
84
.. code-block:: python
85

    
86
    _mygrp1_commands = CommandTree('mygrp', 'mygrp1 description')
87
    _mygrp2_commands = CommandTree('mygrp', 'mygrp2 description')
88

    
89
    _commands = [_mygrp1_commands, _mygrp2_commands]
90

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

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

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

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

    
102
.. code-block:: python
103

    
104
    def command(cmd_tree, prefix='', descedants_depth=None):
105
    """Load a class as a command
106

    
107
        :param cmd_tree: is the CommandTree to be updated with a new command
108

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

    
111
        :param descedants_depth: is the depth of the tree descedants of the
112
            prefix command.
113
    """
114

    
115
Creating a new command specification set
116
----------------------------------------
117

    
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*.
119

    
120
.. code-block:: python
121

    
122
    ...
123
    _commands = [_mygrp1_commands, _mygrp2_commands]
124

    
125
    @command(_mygrp1_commands)
126
    class mygrp1_list_all():
127
        ...
128

    
129
    ...
130

    
131
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.
134

    
135
Get command description
136
-----------------------
137

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

    
142
.. code-block:: python
143

    
144
    ...
145
    @command(_mygrp2_commands)
146
    class mygrp2_info()
147
        """get information for subject with id"""
148
        ...
149

    
150
Declare run-time argument
151
-------------------------
152

    
153
The argument mechanism allows the definition of run-time arguments. Some basic
154
argument types are defined at the
155
`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>`_).
159

    
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).`
165

    
166
.. code-block:: python
167

    
168
    from kamaki.cli.argument import ValueArgument
169
    ...
170

    
171
    @command(_mygrp1_commands)
172
    class mygrp1_list_details():
173
        """list of details"""
174

    
175
        def __init__(self, global_args={}):
176
            global_args['match'] = ValueArgument(
177
                'Filter results to match string',
178
                ('-m', '--match'))
179
            self.arguments = global_args
180

    
181
or more usually and elegantly:
182

    
183
.. code-block:: python
184

    
185
    from kamaki.cli.argument import ValueArgument
186
    
187
    @command(_mygrp1_commands)
188
    class mygrp1_list_details():
189
    """List of details"""
190

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

    
196
Accessing run-time arguments
197
----------------------------
198

    
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 .
202

    
203
.. code-block:: python
204

    
205
    from kamaki.cli.argument import ValueArgument
206
    from kamaki.cli.commands import _command_init
207
    
208
    @command(_mygrp1_commands)
209
    class mygrp1_list_details(_command_init):
210
        """List of details"""
211

    
212
        arguments = dict(
213
            match=ValueArgument(
214
                'Filter output to match string', ('-m', --match'))
215
        )
216

    
217
        def check_runtime_arguments(self):
218
            ...
219
            assert self['match'] == self.arguments['match'].value
220
            ...
221

    
222
The main method and command parameters
223
--------------------------------------
224

    
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::
228

    
229
    main(self, param)                   - obligatory parameter <param>
230
    main(self, param=None)              - optional parameter [param]
231
    main(self, param1, param2=42)       - <param1> [param2]
232
    main(self, param1____param2)        - <param1:param2>
233
    main(self, param1____param2=[])     - [param1:param2]
234
    main(self, param1____param2__)      - <param1[:param2]>
235
    main(self, param1____param2__='')   - [param1[:param2]]
236
    main(self, *args)                   - arbitary number of params [...]
237
    main(self, param1____param2, *args) - <param1:param2> [...]
238

    
239
The information that can be mined by *command* for each individual command is
240
presented in the following:
241

    
242
.. code-block:: python
243
    :linenos:
244

    
245
    from kamaki.cli.argument import FlagArgument
246
    ...
247

    
248
    _commands = [_mygrp1_commands, _mygrp2=commands]
249
    ...
250

    
251
    @command(_mygrp2_commands)
252
    class mygrp2_list_all():
253
        """List all subjects"""
254

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

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

    
260
This will load the following information on the CommandTree:
261

    
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
265

    
266
Letting kamaki know
267
-------------------
268

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

    
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.
276

    
277
The user has to use a configuration file where the following is added:
278
::
279

    
280
    [global]
281
    mygrp1_cli = grps
282
    mygrp2_cli = grps
283

    
284
or equivalently:
285

    
286
.. code-block:: console
287

    
288
    $ kamaki config set mygrp1_cli grps
289
    $ kamaki config set mygrp2_cli grps
290

    
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::
294

    
295
    [global]
296
    mygrp_cli = /another/path/grps.py
297

    
298
Summary: create a command set
299
-----------------------------
300

    
301
.. code-block:: python
302

    
303
    #  File: grps.py
304

    
305
    from kamaki.cli.commands import _command_init
306
    from kamaki.cli.command_tree import CommandTree
307
    from kamaki.cli.argument import ValueArgument, FlagArgument
308
    ...
309

    
310

    
311
    #  Initiallize command trees
312

    
313
    _mygrp1_commands = CommandTree('mygrp', 'mygrp1 description')
314
    _mygrp2_commands = CommandTree('mygrp', 'mygrp2 description')
315

    
316
    _commands = [_mygrp1_commands, _mygrp2_commands]
317

    
318

    
319
    #  Define command specifications
320

    
321

    
322
    @command(_mygrp1_commands)
323
    class mygrp1_list_all(_command_init):
324
        """show a list"""
325

    
326
        def main(self):
327
            ...
328

    
329

    
330
    @command(_mygrp1_commands)
331
    class mygrp1_list_details(_command_init):
332
        """show list of details"""
333

    
334
        arguments = dict(
335
            match=ValueArgument(
336
                'Filter output to match string', ('-m', --match'))
337
        )
338

    
339
        def main(self):
340
            ...
341
            match_value = self['match']
342
            ...
343

    
344

    
345
    @command(_mygrp2_commands)
346
    class mygrp2_list_all(_command_init):
347
        """list all subjects"""
348

    
349
        arguments = dict(
350
            list=FlagArgument('detailed listing', '-l')
351
        )
352

    
353
        def main(self, regular_expression=None):
354
            ...
355
            detail_flag = self['list']
356
            ...
357
            if detail_flag:
358
                ...
359
            ...
360
            if regular_expression:
361
                ...
362
            ...
363

    
364

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

    
369
        def main(self, id, name=''):
370
            ...