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