1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """
17 Provides a tiny framework with simple, yet powerful and extensible
18 architecture to implement commands in a streight and flexible,
19 declarative way.
20 """
21
22 import re
23 from types import FunctionType
24 from inspect import getargspec, getdoc
25
26 from dispatcher import Host, Container
27 from dispatcher import get_command, list_commands
28 from mapping import parse_arguments, adapt_arguments
29 from errors import DefinitionError, CommandError, NoCommandError
32 """
33 Command host is a hub between numerous command processors and
34 command containers. Aimed to participate in a dispatching process in
35 order to provide clean and transparent architecture.
36
37 The AUTOMATIC class variable, which must be defined by a command
38 host, specifies whether the command host should be automatically
39 dispatched and enabled by the dispatcher or not.
40 """
41 __metaclass__ = Host
42
44 """
45 Command container is an entity which holds defined commands,
46 allowing them to be dispatched and proccessed correctly. Each
47 command container may be bound to a one or more command hosts.
48
49 The AUTOMATIC class variable, which must be defined by a command
50 processor, specifies whether the command processor should be
51 automatically dispatched and enabled by the dispatcher or not.
52
53 Bounding is controlled by the HOSTS class variable, which must be
54 defined by the command container. This variable should contain a
55 sequence of hosts to bound to, as a tuple or list.
56 """
57 __metaclass__ = Container
58
60 """
61 Command processor is an immediate command emitter. It does not
62 participate in the dispatching process directly, but must define a
63 host to bound to.
64
65 Bounding is controlled by the COMMAND_HOST variable, which must be
66 defined in the body of the command processor. This variable should
67 be set to a specific command host.
68 """
69
70
71
72 COMMAND_PREFIX = '/'
73
75 """
76 Try to process text as a command. Returns True if it has been
77 processed as a command and False otherwise.
78 """
79 prefix = text.startswith(self.COMMAND_PREFIX)
80 length = len(text) > len(self.COMMAND_PREFIX)
81 if not (prefix and length):
82 return False
83
84 body = text[len(self.COMMAND_PREFIX):]
85 body = body.strip()
86
87 parts = body.split(None, 1)
88 name, arguments = parts if len(parts) > 1 else (parts[0], None)
89
90 flag = self.looks_like_command(text, body, name, arguments)
91 if flag is not None:
92 return flag
93
94 self.execute_command(name, arguments)
95
96 return True
97
99 command = self.get_command(name)
100
101 args, opts = parse_arguments(arguments) if arguments else ([], [])
102 args, kwargs = adapt_arguments(command, arguments, args, opts)
103
104 if self.command_preprocessor(command, name, arguments, args, kwargs):
105 return
106 value = command(self, *args, **kwargs)
107 self.command_postprocessor(command, name, arguments, args, kwargs, value)
108
110 """
111 Redefine this method in the subclass to execute custom code
112 before command gets executed.
113
114 If returns True then command execution will be interrupted and
115 command will not be executed.
116 """
117 pass
118
119 - def command_postprocessor(self, command, name, arguments, args, kwargs, value):
120 """
121 Redefine this method in the subclass to execute custom code
122 after command gets executed.
123 """
124 pass
125
127 """
128 This hook is being called before any processing, but after it
129 was determined that text looks like a command.
130
131 If returns value other then None - then further processing will
132 be interrupted and that value will be used to return from
133 process_as_command.
134 """
135 pass
136
142
147
149
150 - def __init__(self, handler, *names, **properties):
158
160 try:
161 return self.handler(*args, **kwargs)
162
163
164
165
166
167
168 except CommandError, error:
169 if not error.command and not error.name:
170 raise CommandError(error.message, self)
171 raise
172
173
174
175
176
177 except TypeError:
178 raise CommandError("Command received invalid arguments", self)
179
181 return "<Command %s>" % ', '.join(self.names)
182
185
186 @property
189
190 @property
192 return self.handler.__name__
193
195 """
196 Extract handler's documentation which is a doc-string and
197 transform it to a usable format.
198 """
199 return getdoc(self.handler)
200
202 """
203 Extract handler's description (which is a first line of the
204 documentation). Try to keep them simple yet meaningful.
205 """
206 documentation = self.extract_documentation()
207 return documentation.split('\n', 1)[0] if documentation else None
208
210 """
211 Extract handler's arguments specification, as it was defined
212 preserving their order.
213 """
214 names, var_args, var_kwargs, defaults = getargspec(self.handler)
215
216
217
218 spec_args = names[:-len(defaults) if defaults else len(names)]
219 spec_kwargs = list(zip(names[-len(defaults):], defaults)) if defaults else {}
220
221
222
223
224 if spec_args.pop(0) != 'self':
225 raise DefinitionError("First argument must be self", self)
226
227 return spec_args, spec_kwargs, var_args, var_kwargs
228
230 """
231 A decorator for defining commands in a declarative way. Provides
232 facilities for setting command's names and properties.
233
234 Names should contain a set of names (aliases) by which the command
235 can be reached. If no names are given - the the native name (the one
236 extracted from the command handler) will be used.
237
238 If native=True is given (default) and names is non-empty - then the
239 native name of the command will be prepended in addition to the
240 given names.
241
242 If usage=True is given (default) - then command help will be
243 appended with autogenerated usage info, based of the command handler
244 arguments introspection.
245
246 If source=True is given - then the first argument of the command
247 will receive the source arguments, as a raw, unprocessed string. The
248 further mapping of arguments and options will not be affected.
249
250 If raw=True is given - then command considered to be raw and should
251 define positional arguments only. If it defines only one positional
252 argument - this argument will receive all the raw and unprocessed
253 arguments. If the command defines more then one positional argument
254 - then all the arguments except the last one will be processed
255 normally; the last argument will get what is left after the
256 processing as raw and unprocessed string.
257
258 If empty=True is given - this will allow to call a raw command
259 without arguments.
260
261 If extra=True is given - then all the extra arguments passed to a
262 command will be collected into a sequence and given to the last
263 positional argument.
264
265 If overlap=True is given - then all the extra arguments will be
266 mapped as if they were values for the keyword arguments.
267
268 If expand=True is given (default) - then short, one-letter options
269 will be expanded to a verbose ones, based of the comparison of the
270 first letter. If more then one option with the same first letter is
271 given - then only first one will be used in the expansion.
272 """
273 names = list(names)
274
275 native = properties.get('native', True)
276
277 usage = properties.get('usage', True)
278 source = properties.get('source', False)
279 raw = properties.get('raw', False)
280 empty = properties.get('empty', False)
281 extra = properties.get('extra', False)
282 overlap = properties.get('overlap', False)
283 expand = properties.get('expand', True)
284
285 if empty and not raw:
286 raise DefinitionError("Empty option can be used only with raw commands")
287
288 if extra and overlap:
289 raise DefinitionError("Extra and overlap options can not be used together")
290
291 properties = {
292 'usage': usage,
293 'source': source,
294 'raw': raw,
295 'extra': extra,
296 'overlap': overlap,
297 'empty': empty,
298 'expand': expand
299 }
300
301 def decorator(handler):
302 """
303 Decorator which receives handler as a first argument and then
304 wraps it in the command which then returns back.
305 """
306 command = Command(handler, *names, **properties)
307
308
309
310
311 if not names or native:
312 names.insert(0, command.native_name)
313 command.names = tuple(names)
314
315 return command
316
317
318
319
320 if names and isinstance(names[0], FunctionType):
321 return decorator(names.pop(0))
322
323 return decorator
324
326 """
327 This decorator is used to bind a documentation (a help) to a
328 command.
329 """
330 def decorator(target):
331 if isinstance(target, Command):
332 target.handler.__doc__ = text
333 else:
334 target.__doc__ = text
335 return target
336
337 return decorator
338