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()