root / docs / developers / adding-commands.rst @ 59cadffb
History | View | Annotate | Download (10.2 kB)
1 |
Adding Commands |
---|---|
2 |
=============== |
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. |
5 |
|
6 |
In the following, a set of kamaki commands will be implemented:: |
7 |
|
8 |
mygrp1 list all //show a list |
9 |
mygrp1 list details [--match=<>] //show list of details |
10 |
mygrp2 list all [regular expression] [-l] //list all subjects |
11 |
mygrp2 info <id> [name] //get information for subject with id |
12 |
|
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. |
14 |
|
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. |
16 |
|
17 |
Samples of the expected behavior in one-command mode are following: |
18 |
|
19 |
.. code-block:: console |
20 |
|
21 |
$kamaki mygrp1 |
22 |
mygrp1 description |
23 |
|
24 |
Options |
25 |
- - - - |
26 |
list |
27 |
$ kamaki mygrp1 list |
28 |
Syntax Error |
29 |
|
30 |
Options |
31 |
- - - - |
32 |
all show a list |
33 |
details show a list of details |
34 |
$ kamaki mygrp1 list all |
35 |
... (mygrp1 client method is called) ... |
36 |
$ kamaki mygrp2 list all 'Z[.]' -l |
37 |
... (mygrp2 client method is called) ... |
38 |
$ |
39 |
|
40 |
The above example will be used throughout the present guide for clarification purposes. |
41 |
|
42 |
The CommandTree structure |
43 |
------------------------- |
44 |
|
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 *store upload*, *store list* and *store info* are stored together as shown bellow:: |
46 |
|
47 |
- store |
48 |
''''''''|- info |
49 |
|- list |
50 |
|- upload |
51 |
|
52 |
The example used in the present, should result to the creation of two trees:: |
53 |
|
54 |
- mygrp1 |
55 |
''''''''|- list |
56 |
'''''''|- all |
57 |
|- details |
58 |
|
59 |
- mygrp2 |
60 |
''''''''|- list |
61 |
'''''''|- all |
62 |
|- info |
63 |
|
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* |
65 |
|
66 |
A command group information (name, description) is provided at CommandTree structure initialization: |
67 |
|
68 |
.. code-block:: python |
69 |
|
70 |
_mygrp1_commands = CommandTree('mygrp', 'mygrp1 description') |
71 |
_mygrp2_commands = CommandTree('mygrp', 'mygrp2 description') |
72 |
|
73 |
_commands = [_mygrp1_commands, _mygrp2_commands] |
74 |
|
75 |
The command decorator |
76 |
--------------------- |
77 |
|
78 |
The *command* decorator mines all the information necessary to build a command specification which is then inserted in a CommanTree instance:: |
79 |
|
80 |
class code ---> command() --> updated CommandTree structure |
81 |
|
82 |
Kamaki interfaces make use of this CommandTree structure. Optimizations are possible by using special parameters on the command decorator method. |
83 |
|
84 |
.. code-block:: python |
85 |
|
86 |
def command(cmd_tree, prefix='', descedants_depth=None): |
87 |
"""Load a class as a command |
88 |
:param cmd_tree: is the CommandTree to be updated with a new command |
89 |
:param prefix: of the commands allowed to be inserted ('' for all) |
90 |
:param descedants_depth: is the depth of the tree descedants of the |
91 |
prefix command. |
92 |
""" |
93 |
|
94 |
Creating a new command specification set |
95 |
---------------------------------------- |
96 |
|
97 |
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*. |
98 |
|
99 |
.. code-block:: python |
100 |
|
101 |
... |
102 |
_commands = [_mygrp1_commands, _mygrp2_commands] |
103 |
|
104 |
@command(_mygrp1_commands) |
105 |
class mygrp1_list_all(): |
106 |
... |
107 |
|
108 |
... |
109 |
|
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. |
111 |
|
112 |
Get command description |
113 |
----------------------- |
114 |
|
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. |
116 |
|
117 |
.. code-block:: python |
118 |
|
119 |
... |
120 |
@command(_mygrp2_commands) |
121 |
class mygrp2_info() |
122 |
"""get information for subject with id""" |
123 |
... |
124 |
|
125 |
Declare run-time argument |
126 |
------------------------- |
127 |
|
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_cli>`_). |
129 |
|
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). |
131 |
|
132 |
.. code-block:: python |
133 |
|
134 |
from kamaki.cli.argument import ValueArgument |
135 |
... |
136 |
|
137 |
@command(_mygrp1_commands) |
138 |
class mygrp1_list_details(): |
139 |
"""list of details""" |
140 |
|
141 |
def __init__(self, global_args={}): |
142 |
global_args['match'] = ValueArgument( |
143 |
'Filter results to match string', |
144 |
'--match') |
145 |
self.arguments = global_args |
146 |
|
147 |
The main method and command parameters |
148 |
-------------------------------------- |
149 |
|
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:: |
151 |
|
152 |
main(self, param) - obligatory parameter <param> |
153 |
main(self, param=None) - optional parameter [param] |
154 |
main(self, param1, param2=42) - <param1> [param2] |
155 |
main(self, param1____param2) - <param1:param2> |
156 |
main(self, param1____param2=[]) - [param1:param2] |
157 |
main(self, param1____param2__) - <param1[:param2]> |
158 |
main(self, param1____param2__='') - [param1[:param2]] |
159 |
main(self, *args) - arbitary number of params [...] |
160 |
main(self, param1____param2, *args) - <param1:param2> [...] |
161 |
|
162 |
The information that can be mined by *command* for each individual command is presented in the following: |
163 |
|
164 |
.. code-block:: python |
165 |
:linenos: |
166 |
|
167 |
from kamaki.cli.argument import FlagArgument |
168 |
... |
169 |
|
170 |
_commands = [_mygrp1_commands, _mygrp2=commands] |
171 |
... |
172 |
|
173 |
@command(_mygrp2_commands) |
174 |
class mygrp2_list_all(object): |
175 |
"""List all subjects""" |
176 |
|
177 |
def __init__(self, global_args={}): |
178 |
global_args['list'] = FlagArgument( |
179 |
'detailed list', |
180 |
'-l, |
181 |
False) |
182 |
|
183 |
self.arguments = global_args |
184 |
|
185 |
def main(self, reg_exp=None): |
186 |
... |
187 |
|
188 |
This will load the following information on the CommandTree: |
189 |
|
190 |
* Syntax (from lines 8,12,19): mygrp list all [reg exp] [-l] |
191 |
* Description (form line 9): List all subjects |
192 |
* Arguments help (from line 13,14): -l: detailed list |
193 |
|
194 |
Letting kamaki know |
195 |
------------------- |
196 |
|
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*. |
198 |
|
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. |
200 |
|
201 |
The user has to use a configuration file where the following is added: |
202 |
:: |
203 |
|
204 |
[mygrp1] |
205 |
cli=grps |
206 |
|
207 |
[mygrp2] |
208 |
cli=grps |
209 |
|
210 |
or alternatively: |
211 |
|
212 |
.. code-block:: console |
213 |
|
214 |
$ kamaki config set mygrp1.cli = grps |
215 |
$ kamaki config set mygrp2.cli = grps |
216 |
|
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:: |
218 |
|
219 |
[mygrp] |
220 |
cli=/another/path/grps.py |
221 |
|
222 |
Summary: create a command set |
223 |
----------------------------- |
224 |
|
225 |
.. code-block:: python |
226 |
|
227 |
# File: grps.py |
228 |
|
229 |
from kamaki.cli.command_tree import CommandTree |
230 |
from kamaki.cli.argument import ValueArgument, FlagArgument |
231 |
... |
232 |
|
233 |
|
234 |
# Initiallize command trees |
235 |
|
236 |
_mygrp1_commands = CommandTree('mygrp', 'mygrp1 description') |
237 |
_mygrp2_commands = CommandTree('mygrp', 'mygrp2 description') |
238 |
|
239 |
_commands = [_mygrp1_commands, _mygrp2_commands] |
240 |
|
241 |
|
242 |
# Define command specifications |
243 |
|
244 |
|
245 |
@command(_mygrp1_commands) |
246 |
class mygrp1_list_all(): |
247 |
"""show a list""" |
248 |
|
249 |
arguments = {} |
250 |
|
251 |
def main(self): |
252 |
... |
253 |
|
254 |
|
255 |
@command(_mygrp1_commands) |
256 |
class mygrp1_list_details(): |
257 |
"""show list of details""" |
258 |
|
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 |
266 |
|
267 |
def main(self): |
268 |
... |
269 |
match_value = self.arguments['list'].value |
270 |
... |
271 |
|
272 |
|
273 |
@command(_mygrp2_commands) |
274 |
class mygrp2_list_all(): |
275 |
"""list all subjects""" |
276 |
|
277 |
arguments = {} |
278 |
|
279 |
def __init__(self, global_args={}) |
280 |
global_args['match'] = FlagArgument('detailed listing', '-l') |
281 |
self.arguments = global_args |
282 |
|
283 |
def main(self, regular_expression=None): |
284 |
... |
285 |
detail_flag = self.arguments['list'].value |
286 |
... |
287 |
if detail_flag: |
288 |
... |
289 |
... |
290 |
if regular_expression: |
291 |
... |
292 |
... |
293 |
|
294 |
|
295 |
@command(_mygrp2_commands) |
296 |
class mygrp2_info(): |
297 |
"""get information for subject with id""" |
298 |
|
299 |
arguments = {} |
300 |
|
301 |
def main(self, id, name=''): |
302 |
... |