root / docs / developers / adding-commands.rst @ fa382f9e
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 |
... |