Fix deprecated terms in documentation
[kamaki] / docs / developers / adding-commands.rst
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             ...