Revision fa382f9e docs/developers/adding-commands.rst

b/docs/developers/adding-commands.rst
1 1
Adding Commands
2 2
===============
3 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.
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 8

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

  
......
10 13
    mygrp2 list all [regular expression] [-l]       //list all subjects
11 14
    mygrp2 info <id> [name]      //get information for subject with id
12 15

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

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

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

  
......
37 47
        ... (mygrp2 client method is called) ...
38 48
    $
39 49

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

  
42 52
The CommandTree structure
43 53
-------------------------
44 54

  
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 *file upload*, *file list* and *file info* are stored together as shown bellow::
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::
46 59

  
47 60
    - file
48 61
    ''''''''|- info
......
61 74
            '''''''|- all
62 75
            |- info
63 76

  
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*
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*
65 80

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

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

  
......
75 91
The command decorator
76 92
---------------------
77 93

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

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

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

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

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

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

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

  
90 111
        :param descedants_depth: is the depth of the tree descedants of the
91 112
            prefix command.
92 113
    """
......
107 128

  
108 129
    ...
109 130

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

  
112 135
Get command description
113 136
-----------------------
114 137

  
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.
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.
116 141

  
117 142
.. code-block:: python
118 143

  
......
125 150
Declare run-time argument
126 151
-------------------------
127 152

  
128
The argument mechanism allows the definition of run-time arguments. Some basic argument types are defined at the `argument module <code.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 <code.html#module-kamaki.cli.commands.pithos>`_).
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>`_).
129 159

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

  
132 166
.. code-block:: python
133 167

  
......
141 175
        def __init__(self, global_args={}):
142 176
            global_args['match'] = ValueArgument(
143 177
                'Filter results to match string',
144
                '--match')
178
                ('-m', '--match'))
145 179
            self.arguments = global_args
146 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

  
147 222
The main method and command parameters
148 223
--------------------------------------
149 224

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

  
152 229
    main(self, param)                   - obligatory parameter <param>
153 230
    main(self, param=None)              - optional parameter [param]
......
159 236
    main(self, *args)                   - arbitary number of params [...]
160 237
    main(self, param1____param2, *args) - <param1:param2> [...]
161 238

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

  
164 242
.. code-block:: python
165 243
    :linenos:
......
171 249
    ...
172 250

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

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

  
183
            self.arguments = global_args
255
        arguments = dict(FlagArgument('detailed list', '-l'))
184 256

  
185 257
        def main(self, reg_exp=None):
186 258
            ...
......
194 266
Letting kamaki know
195 267
-------------------
196 268

  
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*.
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*.
198 272

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

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

  
204
    [mygrp1]
205
    cli=grps
206

  
207
    [mygrp2]
208
    cli=grps
280
    [global]
281
    mygrp1_cli = grps
282
    mygrp2_cli = grps
209 283

  
210
or alternatively:
284
or equivalently:
211 285

  
212 286
.. code-block:: console
213 287

  
214
    $ kamaki config set mygrp1.cli = grps
215
    $ kamaki config set mygrp2.cli = grps
288
    $ kamaki config set mygrp1_cli grps
289
    $ kamaki config set mygrp2_cli grps
216 290

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

  
219
    [mygrp]
220
    cli=/another/path/grps.py
295
    [global]
296
    mygrp_cli = /another/path/grps.py
221 297

  
222 298
Summary: create a command set
223 299
-----------------------------
......
226 302

  
227 303
    #  File: grps.py
228 304

  
305
    from kamaki.cli.commands import _command_init
229 306
    from kamaki.cli.command_tree import CommandTree
230 307
    from kamaki.cli.argument import ValueArgument, FlagArgument
231 308
    ...
......
243 320

  
244 321

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

  
249
        arguments = {}
250

  
251 326
        def main(self):
252 327
            ...
253 328

  
254 329

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

  
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
334
        arguments = dict(
335
            match=ValueArgument(
336
                'Filter output to match string', ('-m', --match'))
337
        )
266 338

  
267 339
        def main(self):
268 340
            ...
269
            match_value = self.arguments['list'].value
341
            match_value = self['match']
270 342
            ...
271 343

  
272 344

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

  
277
        arguments = {}
278

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

  
283 353
        def main(self, regular_expression=None):
284 354
            ...
285
            detail_flag = self.arguments['list'].value
355
            detail_flag = self['list']
286 356
            ...
287 357
            if detail_flag:
288 358
                ...
......
293 363

  
294 364

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

  
299
        arguments = {}
300

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

Also available in: Unified diff