Update docs, rename kamaki.cli.commands/*
[kamaki] / docs / developers / adding-commands.rst
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 *file upload*, *file list* and *file info* are stored together as shown bellow::
46
47     - file
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>`_).
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             ...