Mercurial > libervia-backend
comparison src/bridge/bridge_constructor/bridge_constructor.py @ 2085:da4097de5a95
bridge (constructor): refactoring:
- constructors are now in separate modules
- constructors are discovered dynamically
- factorised generation code from D-Bus in base Constructor.
- A generic generation method is now available in base Constructor, using python formatting.
- removed bridge/bridge.py in core as it was useless, may come back in the future if needed
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 02 Oct 2016 22:44:33 +0200 |
parents | e1015a5df6f5 |
children | 8b37a62336c3 |
comparison
equal
deleted
inserted
replaced
2084:e1015a5df6f5 | 2085:da4097de5a95 |
---|---|
15 # GNU Affero General Public License for more details. | 15 # GNU Affero General Public License for more details. |
16 | 16 |
17 # You should have received a copy of the GNU Affero General Public License | 17 # You should have received a copy of the GNU Affero General Public License |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 from sat.core.constants import Const as C | 20 |
21 import sys | 21 from sat.bridge import bridge_constructor |
22 import os | 22 from sat.bridge.bridge_constructor.constants import Const as C |
23 from sat.bridge.bridge_constructor import constructors, base_constructor | |
23 import argparse | 24 import argparse |
24 from ConfigParser import SafeConfigParser as Parser | 25 from ConfigParser import SafeConfigParser as Parser |
25 from ConfigParser import NoOptionError | 26 from importlib import import_module |
26 import re | 27 import os |
27 from datetime import datetime | 28 import os.path |
28 from xml.dom import minidom | |
29 | 29 |
30 #consts | 30 #consts |
31 NAME = u"bridge_constructor" | |
32 __version__ = C.APP_VERSION | 31 __version__ = C.APP_VERSION |
33 DEST_DIR_DEFAULT = "generated" | |
34 DESCRIPTION = u"""{name} Copyright (C) 2009-2016 Jérôme Poisson (aka Goffi) | |
35 | |
36 This script construct a SàT bridge using the given protocol | |
37 | |
38 This program comes with ABSOLUTELY NO WARRANTY; | |
39 This is free software, and you are welcome to redistribute it | |
40 under certain conditions. | |
41 """.format(name=NAME, version=__version__) | |
42 # TODO: move protocoles in separate files (plugins?) | |
43 MANAGED_PROTOCOLES = ['dbus', 'mediawiki', 'dbus-xml'] | |
44 DEFAULT_PROTOCOLE = 'dbus' | |
45 | |
46 # flags used method/signal declaration (not to be confused with constructor flags) | |
47 DECLARATION_FLAGS = ['deprecated', 'async'] | |
48 | |
49 ENV_OVERRIDE = "SAT_BRIDGE_CONST_" # Prefix used to override a constant | |
50 | |
51 | |
52 | |
53 class ParseError(Exception): | |
54 #Used when the signature parsing is going wrong (invalid signature ?) | |
55 pass | |
56 | |
57 | |
58 class Constructor(object): | |
59 | |
60 def __init__(self, bridge_template, options): | |
61 self.bridge_template = bridge_template | |
62 self.args = options | |
63 | |
64 def getValues(self, name): | |
65 """Return values of a function in a dict | |
66 @param name: Name of the function to get | |
67 @return: dict, each key has the config value or None if the value is not set""" | |
68 function = {} | |
69 for option in ['type', 'category', 'sig_in', 'sig_out', 'doc']: | |
70 try: | |
71 value = self.bridge_template.get(name, option) | |
72 except NoOptionError: | |
73 value = None | |
74 function[option] = value | |
75 return function | |
76 | |
77 def getDefault(self, name): | |
78 """Return default values of a function in a dict | |
79 @param name: Name of the function to get | |
80 @return: dict, each key is the integer param number (no key if no default value)""" | |
81 default_dict = {} | |
82 def_re = re.compile(r"param_(\d+)_default") | |
83 | |
84 for option in self.bridge_template.options(name): | |
85 match = def_re.match(option) | |
86 if match: | |
87 try: | |
88 idx = int(match.group(1)) | |
89 except ValueError: | |
90 raise ParseError("Invalid value [%s] for parameter number" % match.group(1)) | |
91 default_dict[idx] = self.bridge_template.get(name, option) | |
92 | |
93 return default_dict | |
94 | |
95 def getFlags(self, name): | |
96 """Return list of flags set for this function | |
97 @param name: Name of the function to get | |
98 @return: List of flags (string)""" | |
99 flags = [] | |
100 for option in self.bridge_template.options(name): | |
101 if option in DECLARATION_FLAGS: | |
102 flags.append(option) | |
103 return flags | |
104 | |
105 def getArgumentsDoc(self, name): | |
106 """Return documentation of arguments | |
107 @param name: Name of the function to get | |
108 @return: dict, each key is the integer param number (no key if no argument doc), value is a tuple (name, doc)""" | |
109 doc_dict = {} | |
110 option_re = re.compile(r"doc_param_(\d+)") | |
111 value_re = re.compile(r"^(\w+): (.*)$", re.MULTILINE | re.DOTALL) | |
112 for option in self.bridge_template.options(name): | |
113 if option == 'doc_return': | |
114 doc_dict['return'] = self.bridge_template.get(name, option) | |
115 continue | |
116 match = option_re.match(option) | |
117 if match: | |
118 try: | |
119 idx = int(match.group(1)) | |
120 except ValueError: | |
121 raise ParseError("Invalid value [%s] for parameter number" % match.group(1)) | |
122 value_match = value_re.match(self.bridge_template.get(name, option)) | |
123 if not value_match: | |
124 raise ParseError("Invalid value for parameter doc [%i]" % idx) | |
125 doc_dict[idx] = (value_match.group(1), value_match.group(2)) | |
126 return doc_dict | |
127 | |
128 def getDoc(self, name): | |
129 """Return documentation of the method | |
130 @param name: Name of the function to get | |
131 @return: string documentation, or None""" | |
132 if self.bridge_template.has_option(name, "doc"): | |
133 return self.bridge_template.get(name, "doc") | |
134 return None | |
135 | |
136 def argumentsParser(self, signature): | |
137 """Generator which return individual arguments signatures from a global signature""" | |
138 start = 0 | |
139 i = 0 | |
140 | |
141 while i < len(signature): | |
142 if signature[i] not in ['b', 'y', 'n', 'i', 'x', 'q', 'u', 't', 'd', 's', 'a']: | |
143 raise ParseError("Unmanaged attribute type [%c]" % signature[i]) | |
144 | |
145 if signature[i] == 'a': | |
146 i += 1 | |
147 if signature[i] != '{' and signature[i] != '(': # FIXME: must manage tuples out of arrays | |
148 i += 1 | |
149 yield signature[start:i] | |
150 start = i | |
151 continue # we have a simple type for the array | |
152 opening_car = signature[i] | |
153 assert(opening_car in ['{', '(']) | |
154 closing_car = '}' if opening_car == '{' else ')' | |
155 opening_count = 1 | |
156 while (True): # we have a dict or a list of tuples | |
157 i += 1 | |
158 if i >= len(signature): | |
159 raise ParseError("missing }") | |
160 if signature[i] == opening_car: | |
161 opening_count += 1 | |
162 if signature[i] == closing_car: | |
163 opening_count -= 1 | |
164 if opening_count == 0: | |
165 break | |
166 i += 1 | |
167 yield signature[start:i] | |
168 start = i | |
169 | |
170 def getArguments(self, signature, name=None, default=None, unicode_protect=False): | |
171 """Return arguments to user given a signature | |
172 | |
173 @param signature: signature in the short form (using s,a,i,b etc) | |
174 @param name: dictionary of arguments name like given by getArguments | |
175 @param default: dictionary of default values, like given by getDefault | |
176 @param unicode_protect: activate unicode protection on strings (return strings as unicode(str)) | |
177 @return: list of arguments that correspond to a signature (e.g.: "sss" return "arg1, arg2, arg3") | |
178 """ | |
179 idx = 0 | |
180 attr_string = [] | |
181 | |
182 for arg in self.argumentsParser(signature): | |
183 attr_string.append(("unicode(%(name)s)%(default)s" if (unicode_protect and arg == 's') else "%(name)s%(default)s") % { | |
184 'name': name[idx][0] if (name and idx in name) else "arg_%i" % idx, | |
185 'default': "=" + default[idx] if (default and idx in default) else ''}) | |
186 # give arg_1, arg2, etc or name1, name2=default, etc. | |
187 #give unicode(arg_1), unicode(arg_2), etc. if unicode_protect is set and arg is a string | |
188 idx += 1 | |
189 | |
190 return ", ".join(attr_string) | |
191 | |
192 def generateCoreSide(self): | |
193 """create the constructor in SàT core side (backend)""" | |
194 raise NotImplementedError | |
195 | |
196 def generateFrontendSide(self): | |
197 """create the constructor in SàT frontend side""" | |
198 raise NotImplementedError | |
199 | |
200 def finalWrite(self, filename, file_buf): | |
201 """Write the final generated file in [dest dir]/filename | |
202 | |
203 @param filename: name of the file to generate | |
204 @param file_buf: list of lines (stings) of the file | |
205 """ | |
206 if os.path.exists(self.args.dest_dir) and not os.path.isdir(self.args.dest_dir): | |
207 print ("The destination dir [%s] can't be created: a file with this name already exists !") | |
208 sys.exit(1) | |
209 try: | |
210 if not os.path.exists(self.args.dest_dir): | |
211 os.mkdir(self.args.dest_dir) | |
212 full_path = os.path.join(self.args.dest_dir, filename) | |
213 if os.path.exists(full_path) and not self.args.force: | |
214 print ("The destination file [%s] already exists ! Use --force to overwrite it" % full_path) | |
215 try: | |
216 with open(full_path, 'w') as dest_file: | |
217 dest_file.write('\n'.join(file_buf)) | |
218 except IOError: | |
219 print ("Can't open destination file [%s]" % full_path) | |
220 except OSError: | |
221 print("It's not possible to generate the file, check your permissions") | |
222 exit(1) | |
223 | |
224 | |
225 class MediawikiConstructor(Constructor): | |
226 | |
227 def __init__(self, bridge_template, options): | |
228 Constructor.__init__(self, bridge_template, options) | |
229 self.core_template = "mediawiki_template.tpl" | |
230 self.core_dest = "mediawiki.wiki" | |
231 | |
232 def _addTextDecorations(self, text): | |
233 """Add text decorations like coloration or shortcuts""" | |
234 | |
235 def anchor_link(match): | |
236 link = match.group(1) | |
237 #we add anchor_link for [method_name] syntax: | |
238 if link in self.bridge_template.sections(): | |
239 return "[[#%s|%s]]" % (link, link) | |
240 print ("WARNING: found an anchor link to an unknown method") | |
241 return link | |
242 | |
243 return re.sub(r"\[(\w+)\]", anchor_link, text) | |
244 | |
245 def _wikiParameter(self, name, sig_in): | |
246 """Format parameters with the wiki syntax | |
247 @param name: name of the function | |
248 @param sig_in: signature in | |
249 @return: string of the formated parameters""" | |
250 arg_doc = self.getArgumentsDoc(name) | |
251 arg_default = self.getDefault(name) | |
252 args_str = self.getArguments(sig_in) | |
253 args = args_str.split(', ') if args_str else [] # ugly but it works :) | |
254 wiki = [] | |
255 for i in range(len(args)): | |
256 if i in arg_doc: | |
257 name, doc = arg_doc[i] | |
258 doc = '\n:'.join(doc.rstrip('\n').split('\n')) | |
259 wiki.append("; %s: %s" % (name, self._addTextDecorations(doc))) | |
260 else: | |
261 wiki.append("; arg_%d: " % i) | |
262 if i in arg_default: | |
263 wiki.append(":''DEFAULT: %s''" % arg_default[i]) | |
264 return "\n".join(wiki) | |
265 | |
266 def _wikiReturn(self, name): | |
267 """Format return doc with the wiki syntax | |
268 @param name: name of the function | |
269 """ | |
270 arg_doc = self.getArgumentsDoc(name) | |
271 wiki = [] | |
272 if 'return' in arg_doc: | |
273 wiki.append('\n|-\n! scope=row | return value\n|') | |
274 wiki.append('<br />\n'.join(self._addTextDecorations(arg_doc['return']).rstrip('\n').split('\n'))) | |
275 return "\n".join(wiki) | |
276 | |
277 def generateCoreSide(self): | |
278 signals_part = [] | |
279 methods_part = [] | |
280 sections = self.bridge_template.sections() | |
281 sections.sort() | |
282 for section in sections: | |
283 function = self.getValues(section) | |
284 print ("Adding %s %s" % (section, function["type"])) | |
285 async_msg = """<br />'''This method is asynchronous'''""" | |
286 deprecated_msg = """<br />'''<font color="#FF0000">/!\ WARNING /!\ : This method is deprecated, please don't use it !</font>'''""" | |
287 signature_signal = \ | |
288 """\ | |
289 ! scope=row | signature | |
290 | %s | |
291 |-\ | |
292 """ % function['sig_in'] | |
293 signature_method = \ | |
294 """\ | |
295 ! scope=row | signature in | |
296 | %s | |
297 |- | |
298 ! scope=row | signature out | |
299 | %s | |
300 |-\ | |
301 """ % (function['sig_in'], function['sig_out']) | |
302 completion = { | |
303 'signature': signature_signal if function['type'] == "signal" else signature_method, | |
304 'sig_out': function['sig_out'] or '', | |
305 'category': function['category'], | |
306 'name': section, | |
307 'doc': self.getDoc(section) or "FIXME: No description available", | |
308 'async': async_msg if "async" in self.getFlags(section) else "", | |
309 'deprecated': deprecated_msg if "deprecated" in self.getFlags(section) else "", | |
310 'parameters': self._wikiParameter(section, function['sig_in']), | |
311 'return': self._wikiReturn(section) if function['type'] == 'method' else ''} | |
312 | |
313 dest = signals_part if function['type'] == "signal" else methods_part | |
314 dest.append("""\ | |
315 == %(name)s == | |
316 ''%(doc)s'' | |
317 %(deprecated)s | |
318 %(async)s | |
319 {| class="wikitable" style="text-align:left; width:80%%;" | |
320 ! scope=row | category | |
321 | %(category)s | |
322 |- | |
323 %(signature)s | |
324 ! scope=row | parameters | |
325 | | |
326 %(parameters)s%(return)s | |
327 |} | |
328 """ % completion) | |
329 | |
330 #at this point, signals_part, and methods_part should be filled, | |
331 #we just have to place them in the right part of the template | |
332 core_bridge = [] | |
333 try: | |
334 with open(self.core_template) as core_template: | |
335 for line in core_template: | |
336 if line.startswith('##SIGNALS_PART##'): | |
337 core_bridge.extend(signals_part) | |
338 elif line.startswith('##METHODS_PART##'): | |
339 core_bridge.extend(methods_part) | |
340 elif line.startswith('##TIMESTAMP##'): | |
341 core_bridge.append('Generated on %s' % datetime.now()) | |
342 else: | |
343 core_bridge.append(line.replace('\n', '')) | |
344 except IOError: | |
345 print ("Can't open template file [%s]" % self.core_template) | |
346 sys.exit(1) | |
347 | |
348 #now we write to final file | |
349 self.finalWrite(self.core_dest, core_bridge) | |
350 | |
351 | |
352 class DbusConstructor(Constructor): | |
353 | |
354 def __init__(self, bridge_template, options): | |
355 Constructor.__init__(self, bridge_template, options) | |
356 self.core_template = "dbus_core_template.py" | |
357 self.frontend_template = "dbus_frontend_template.py" | |
358 self.frontend_dest = self.core_dest = "DBus.py" | |
359 | |
360 def generateCoreSide(self): | |
361 signals_part = [] | |
362 methods_part = [] | |
363 direct_calls = [] | |
364 sections = self.bridge_template.sections() | |
365 sections.sort() | |
366 for section in sections: | |
367 function = self.getValues(section) | |
368 print ("Adding %s %s" % (section, function["type"])) | |
369 default = self.getDefault(section) | |
370 arg_doc = self.getArgumentsDoc(section) | |
371 async = "async" in self.getFlags(section) | |
372 completion = { | |
373 'sig_in': function['sig_in'] or '', | |
374 'sig_out': function['sig_out'] or '', | |
375 'category': 'PLUGIN' if function['category'] == 'plugin' else 'CORE', | |
376 'name': section, | |
377 'args': self.getArguments(function['sig_in'], name=arg_doc, default=default)} | |
378 | |
379 if function["type"] == "signal": | |
380 completion['body'] = "pass" if not self.args.debug else 'log.debug ("%s")' % section | |
381 signals_part.append("""\ | |
382 @dbus.service.signal(const_INT_PREFIX+const_%(category)s_SUFFIX, | |
383 signature='%(sig_in)s') | |
384 def %(name)s(self, %(args)s): | |
385 %(body)s | |
386 """ % completion) | |
387 direct_calls.append("""\ | |
388 def %(name)s(self, %(args)s): | |
389 self.dbus_bridge.%(name)s(%(args)s) | |
390 """ % completion) | |
391 | |
392 elif function["type"] == "method": | |
393 completion['debug'] = "" if not self.args.debug else 'log.debug ("%s")\n%s' % (section, 8 * ' ') | |
394 completion['args_result'] = self.getArguments(function['sig_in'], name=arg_doc, unicode_protect=self.args.unicode) | |
395 completion['async_comma'] = ', ' if async and function['sig_in'] else '' | |
396 completion['async_args_def'] = 'callback=None, errback=None' if async else '' | |
397 completion['async_args_call'] = 'callback=callback, errback=errback' if async else '' | |
398 completion['async_callbacks'] = "('callback', 'errback')" if async else "None" | |
399 methods_part.append("""\ | |
400 @dbus.service.method(const_INT_PREFIX+const_%(category)s_SUFFIX, | |
401 in_signature='%(sig_in)s', out_signature='%(sig_out)s', | |
402 async_callbacks=%(async_callbacks)s) | |
403 def %(name)s(self, %(args)s%(async_comma)s%(async_args_def)s): | |
404 %(debug)sreturn self._callback("%(name)s", %(args_result)s%(async_comma)s%(async_args_call)s) | |
405 """ % completion) | |
406 | |
407 #at this point, signals_part, methods_part and direct_calls should be filled, | |
408 #we just have to place them in the right part of the template | |
409 core_bridge = [] | |
410 const_override_pref = filter(lambda env: env.startswith(ENV_OVERRIDE), os.environ) | |
411 const_override = [env[len(ENV_OVERRIDE):] for env in const_override_pref] | |
412 try: | |
413 with open(self.core_template) as core_template: | |
414 for line in core_template: | |
415 if line.startswith('##SIGNALS_PART##'): | |
416 core_bridge.extend(signals_part) | |
417 elif line.startswith('##METHODS_PART##'): | |
418 core_bridge.extend(methods_part) | |
419 elif line.startswith('##DIRECT_CALLS##'): | |
420 core_bridge.extend(direct_calls) | |
421 else: | |
422 if line.startswith('const_'): | |
423 const_name = line[len('const_'):line.find(' = ')] | |
424 if const_name in const_override: | |
425 print ("const %s overriden" % const_name) | |
426 core_bridge.append('const_%s = %s' % (const_name, os.environ[ENV_OVERRIDE + const_name])) | |
427 continue | |
428 core_bridge.append(line.replace('\n', '')) | |
429 except IOError: | |
430 print ("Can't open template file [%s]" % self.core_template) | |
431 sys.exit(1) | |
432 | |
433 #now we write to final file | |
434 self.finalWrite(self.core_dest, core_bridge) | |
435 | |
436 def generateFrontendSide(self): | |
437 methods_part = [] | |
438 sections = self.bridge_template.sections() | |
439 sections.sort() | |
440 for section in sections: | |
441 function = self.getValues(section) | |
442 print ("Adding %s %s" % (section, function["type"])) | |
443 default = self.getDefault(section) | |
444 arg_doc = self.getArgumentsDoc(section) | |
445 async = "async" in self.getFlags(section) | |
446 completion = { | |
447 'sig_in': function['sig_in'] or '', | |
448 'sig_out': function['sig_out'] or '', | |
449 'category': 'plugin' if function['category'] == 'plugin' else 'core', | |
450 'name': section, | |
451 'args': self.getArguments(function['sig_in'], name=arg_doc, default=default)} | |
452 | |
453 if function["type"] == "method": | |
454 # XXX: we can manage blocking call in the same way as async one: if callback is None the call will be blocking | |
455 completion['debug'] = "" if not self.args.debug else 'log.debug ("%s")\n%s' % (section, 8 * ' ') | |
456 completion['args_result'] = self.getArguments(function['sig_in'], name=arg_doc) | |
457 completion['async_args'] = 'callback=None, errback=None' | |
458 completion['async_comma'] = ', ' if function['sig_in'] else '' | |
459 completion['error_handler'] = """if callback is None: | |
460 error_handler = None | |
461 else: | |
462 if errback is None: | |
463 errback = log.error | |
464 error_handler = lambda err:errback(dbus_to_bridge_exception(err)) | |
465 """ | |
466 if async: | |
467 completion['blocking_call'] = '' | |
468 completion['async_args_result'] = 'timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler' | |
469 else: | |
470 # XXX: To have a blocking call, we must have not reply_handler, so we test if callback exists, and add reply_handler only in this case | |
471 completion['blocking_call'] = """kwargs={} | |
472 if callback is not None: | |
473 kwargs['timeout'] = const_TIMEOUT | |
474 kwargs['reply_handler'] = callback | |
475 kwargs['error_handler'] = error_handler | |
476 """ | |
477 completion['async_args_result'] = '**kwargs' | |
478 result = "self.db_%(category)s_iface.%(name)s(%(args_result)s%(async_comma)s%(async_args_result)s)" % completion | |
479 completion['result'] = ("unicode(%s)" if self.args.unicode and function['sig_out'] == 's' else "%s") % result | |
480 methods_part.append("""\ | |
481 def %(name)s(self, %(args)s%(async_comma)s%(async_args)s): | |
482 %(error_handler)s%(blocking_call)s%(debug)sreturn %(result)s | |
483 """ % completion) | |
484 | |
485 #at this point, methods_part should be filled, | |
486 #we just have to place it in the right part of the template | |
487 frontend_bridge = [] | |
488 const_override_pref = filter(lambda env: env.startswith(ENV_OVERRIDE), os.environ) | |
489 const_override = [env[len(ENV_OVERRIDE):] for env in const_override_pref] | |
490 try: | |
491 with open(self.frontend_template) as frontend_template: | |
492 for line in frontend_template: | |
493 if line.startswith('##METHODS_PART##'): | |
494 frontend_bridge.extend(methods_part) | |
495 else: | |
496 if line.startswith('const_'): | |
497 const_name = line[len('const_'):line.find(' = ')] | |
498 if const_name in const_override: | |
499 print ("const %s overriden" % const_name) | |
500 frontend_bridge.append('const_%s = %s' % (const_name, os.environ[ENV_OVERRIDE + const_name])) | |
501 continue | |
502 frontend_bridge.append(line.replace('\n', '')) | |
503 except IOError: | |
504 print ("Can't open template file [%s]" % self.frontend_template) | |
505 sys.exit(1) | |
506 | |
507 #now we write to final file | |
508 self.finalWrite(self.frontend_dest, frontend_bridge) | |
509 | |
510 | |
511 class DbusXmlConstructor(Constructor): | |
512 """Constructor for DBus XML syntaxt (used by Qt frontend)""" | |
513 | |
514 def __init__(self, bridge_template, options): | |
515 Constructor.__init__(self, bridge_template, options) | |
516 | |
517 self.template = "dbus_xml_template.xml" | |
518 self.core_dest = "org.goffi.sat.xml" | |
519 self.default_annotation = {'a{ss}': 'StringDict', | |
520 'a(sa{ss}as)': 'QList<Contact>', | |
521 'a{i(ss)}': 'HistoryT', | |
522 'a(sss)': 'QList<MenuT>', | |
523 'a{sa{s(sia{ss})}}': 'PresenceStatusT', | |
524 } | |
525 | |
526 def generateCoreSide(self): | |
527 try: | |
528 doc = minidom.parse(self.template) | |
529 interface_elt = doc.getElementsByTagName('interface')[0] | |
530 except IOError: | |
531 print ("Can't access template") | |
532 sys.exit(1) | |
533 except IndexError: | |
534 print ("Template error") | |
535 sys.exit(1) | |
536 | |
537 sections = self.bridge_template.sections() | |
538 sections.sort() | |
539 for section in sections: | |
540 function = self.getValues(section) | |
541 print ("Adding %s %s" % (section, function["type"])) | |
542 new_elt = doc.createElement('method' if function["type"] == 'method' else 'signal') | |
543 new_elt.setAttribute('name', section) | |
544 | |
545 idx = 0 | |
546 args_doc = self.getArgumentsDoc(section) | |
547 for arg in self.argumentsParser(function['sig_in'] or ''): | |
548 arg_elt = doc.createElement('arg') | |
549 arg_elt.setAttribute('name', args_doc[idx][0] if idx in args_doc else "arg_%i" % idx) | |
550 arg_elt.setAttribute('type', arg) | |
551 _direction = 'in' if function["type"] == 'method' else 'out' | |
552 arg_elt.setAttribute('direction', _direction) | |
553 new_elt.appendChild(arg_elt) | |
554 if "annotation" in self.args.flags: | |
555 if arg in self.default_annotation: | |
556 annot_elt = doc.createElement("annotation") | |
557 annot_elt.setAttribute('name', "com.trolltech.QtDBus.QtTypeName.In%d" % idx) | |
558 annot_elt.setAttribute('value', self.default_annotation[arg]) | |
559 new_elt.appendChild(annot_elt) | |
560 idx += 1 | |
561 | |
562 if function['sig_out']: | |
563 arg_elt = doc.createElement('arg') | |
564 arg_elt.setAttribute('type', function['sig_out']) | |
565 arg_elt.setAttribute('direction', 'out') | |
566 new_elt.appendChild(arg_elt) | |
567 if "annotation" in self.args.flags: | |
568 if function['sig_out'] in self.default_annotation: | |
569 annot_elt = doc.createElement("annotation") | |
570 annot_elt.setAttribute('name', "com.trolltech.QtDBus.QtTypeName.Out0") | |
571 annot_elt.setAttribute('value', self.default_annotation[function['sig_out']]) | |
572 new_elt.appendChild(annot_elt) | |
573 | |
574 interface_elt.appendChild(new_elt) | |
575 | |
576 #now we write to final file | |
577 self.finalWrite(self.core_dest, [doc.toprettyxml()]) | |
578 | |
579 | |
580 class ConstructorError(Exception): | |
581 pass | |
582 | |
583 | |
584 class ConstructorFactory(object): | |
585 def create(self, bridge_template, options): | |
586 if options.protocole == 'dbus': | |
587 return DbusConstructor(bridge_template, options) | |
588 elif options.protocole == 'mediawiki': | |
589 return MediawikiConstructor(bridge_template, options) | |
590 elif options.protocole == 'dbus-xml': | |
591 return DbusXmlConstructor(bridge_template, options) | |
592 | |
593 raise ConstructorError('Unknown constructor type') | |
594 | 32 |
595 | 33 |
596 class BridgeConstructor(object): | 34 class BridgeConstructor(object): |
597 def __init__(self): | 35 |
598 self.args = None | 36 def importConstructors(self): |
37 constructors_dir = os.path.dirname(constructors.__file__) | |
38 self.protocoles = {} | |
39 for dir_ in os.listdir(constructors_dir): | |
40 init_path = os.path.join(constructors_dir, dir_, '__init__.py') | |
41 constructor_path = os.path.join(constructors_dir, dir_, 'constructor.py') | |
42 module_path = "sat.bridge.bridge_constructor.constructors.{}.constructor".format(dir_) | |
43 if os.path.isfile(init_path) and os.path.isfile(constructor_path): | |
44 mod = import_module(module_path) | |
45 for attr in dir(mod): | |
46 obj = getattr(mod, attr) | |
47 if not isinstance(obj, type): | |
48 continue | |
49 if issubclass(obj, base_constructor.Constructor): | |
50 name = obj.NAME or dir_ | |
51 self.protocoles[name] = obj | |
52 break | |
53 if not self.protocoles: | |
54 raise ValueError("no protocole constructor found") | |
599 | 55 |
600 def parse_args(self): | 56 def parse_args(self): |
601 """Check command line options""" | 57 """Check command line options""" |
602 parser = argparse.ArgumentParser(description=DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter) | 58 parser = argparse.ArgumentParser(description=C.DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter) |
603 | 59 |
604 parser.add_argument("--version", action="version", version= __version__) | 60 parser.add_argument("--version", action="version", version= __version__) |
605 parser.add_argument("-p", "--protocole", choices=MANAGED_PROTOCOLES, default=DEFAULT_PROTOCOLE, | 61 default_protocole = C.DEFAULT_PROTOCOLE if C.DEFAULT_PROTOCOLE in self.protocoles else self.protocoles[0] |
62 parser.add_argument("-p", "--protocole", choices=sorted(self.protocoles), default=default_protocole, | |
606 help="generate bridge using PROTOCOLE (default: %(default)s)") # (default: %s, possible values: [%s])" % (DEFAULT_PROTOCOLE, ", ".join(MANAGED_PROTOCOLES))) | 63 help="generate bridge using PROTOCOLE (default: %(default)s)") # (default: %s, possible values: [%s])" % (DEFAULT_PROTOCOLE, ", ".join(MANAGED_PROTOCOLES))) |
607 parser.add_argument("-s", "--side", choices=("core", "frontend"), default="core", | 64 parser.add_argument("-s", "--side", choices=("core", "frontend"), default="core", |
608 help="which side of the bridge do you want to make ?") # (default: %default, possible values: [core, frontend])") | 65 help="which side of the bridge do you want to make ?") # (default: %default, possible values: [core, frontend])") |
609 parser.add_argument("-t", "--template", type=file, default='bridge_template.ini', | 66 default_template = os.path.join(os.path.dirname(bridge_constructor.__file__), 'bridge_template.ini') |
67 parser.add_argument("-t", "--template", type=file, default=default_template, | |
610 help="use TEMPLATE to generate bridge (default: %(default)s)") | 68 help="use TEMPLATE to generate bridge (default: %(default)s)") |
611 parser.add_argument("-f", "--force", action="store_true", | 69 parser.add_argument("-f", "--force", action="store_true", |
612 help=("force overwritting of existing files")) | 70 help=("force overwritting of existing files")) |
613 parser.add_argument("-d", "--debug", action="store_true", | 71 parser.add_argument("-d", "--debug", action="store_true", |
614 help=("add debug information printing")) | 72 help=("add debug information printing")) |
615 parser.add_argument("--no-unicode", action="store_false", dest="unicode", | 73 parser.add_argument("--no-unicode", action="store_false", dest="unicode", |
616 help=("remove unicode type protection from string results")) | 74 help=("remove unicode type protection from string results")) |
617 parser.add_argument("--flags", nargs='+', default=[], | 75 parser.add_argument("--flags", nargs='+', default=[], |
618 help=("constructors' specific flags")) | 76 help=("constructors' specific flags")) |
619 parser.add_argument("--dest-dir", default=DEST_DIR_DEFAULT, | 77 parser.add_argument("--dest-dir", default=C.DEST_DIR_DEFAULT, |
620 help=("directory when the generated files will be written (default: %(default)s")) | 78 help=("directory when the generated files will be written (default: %(default)s)")) |
621 | 79 |
622 return parser.parse_args() | 80 return parser.parse_args() |
623 | 81 |
624 def go(self): | 82 def go(self): |
83 self.importConstructors() | |
625 args = self.parse_args() | 84 args = self.parse_args() |
626 self.template = Parser() | 85 template_parser = Parser() |
627 try: | 86 try: |
628 self.template.readfp(args.template) | 87 template_parser.readfp(args.template) |
629 except IOError: | 88 except IOError: |
630 print ("The template file doesn't exist or is not accessible") | 89 print ("The template file doesn't exist or is not accessible") |
631 exit(1) | 90 exit(1) |
632 constructor = ConstructorFactory().create(self.template, args) | 91 constructor = self.protocoles[args.protocole](template_parser, args) |
633 if args.side == "core": | 92 constructor.generate(args.side) |
634 constructor.generateCoreSide() | 93 |
635 elif args.side == "frontend": | |
636 constructor.generateFrontendSide() | |
637 | 94 |
638 if __name__ == "__main__": | 95 if __name__ == "__main__": |
639 bc = BridgeConstructor() | 96 bc = BridgeConstructor() |
640 bc.go() | 97 bc.go() |