changeset 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 (2016-10-02)
parents e1015a5df6f5
children 4633cfcbcccb
files src/bridge/DBus.py src/bridge/bridge.py src/bridge/bridge_constructor/__init__.py src/bridge/bridge_constructor/base_constructor.py src/bridge/bridge_constructor/bridge_constructor.py src/bridge/bridge_constructor/constants.py src/bridge/bridge_constructor/constructors/__init__.py src/bridge/bridge_constructor/constructors/dbus-xml/__init__.py src/bridge/bridge_constructor/constructors/dbus-xml/constructor.py src/bridge/bridge_constructor/constructors/dbus-xml/dbus_xml_template.xml src/bridge/bridge_constructor/constructors/dbus/__init__.py src/bridge/bridge_constructor/constructors/dbus/constructor.py src/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py src/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py src/bridge/bridge_constructor/constructors/mediawiki/__init__.py src/bridge/bridge_constructor/constructors/mediawiki/constructor.py src/bridge/bridge_constructor/constructors/mediawiki/mediawiki_template.tpl src/bridge/bridge_constructor/dbus_core_template.py src/bridge/bridge_constructor/dbus_frontend_template.py src/bridge/bridge_constructor/dbus_xml_template.xml src/bridge/bridge_constructor/mediawiki_template.tpl
diffstat 16 files changed, 1145 insertions(+), 1012 deletions(-) [+]
line wrap: on
line diff
--- a/src/bridge/DBus.py	Sun Oct 02 15:56:20 2016 +0200
+++ b/src/bridge/DBus.py	Sun Oct 02 22:44:33 2016 +0200
@@ -18,7 +18,6 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.i18n import _
-from bridge import Bridge
 import dbus
 import dbus.service
 import dbus.mainloop.glib
@@ -556,10 +555,9 @@
         func_table[function.__name__] = function  # Needed for introspection
 
 
-class DBusBridge(Bridge):
+class DBusBridge(object):
     def __init__(self):
         dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
-        Bridge.__init__(self)
         log.info("Init DBus...")
         try:
             self.session_bus = dbus.SessionBus()
--- a/src/bridge/bridge.py	Sun Oct 02 15:56:20 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-#!/usr/bin/env python2
-#-*- coding: utf-8 -*-
-
-# SAT: a jabber client
-# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from sat.core.log import getLogger
-log = getLogger(__name__)
-
-
-class Bridge(object):
-    def __init__(self):
-        log.info("Bridge initialization")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge_constructor/base_constructor.py	Sun Oct 02 22:44:33 2016 +0200
@@ -0,0 +1,324 @@
+#!/usr/bin/env python2
+#-*- coding: utf-8 -*-
+
+# SàT: a XMPP client
+# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""base constructor class"""
+
+from sat.bridge.bridge_constructor.constants import Const as C
+from ConfigParser import NoOptionError
+import sys
+import os
+import os.path
+import re
+from importlib import import_module
+
+
+class ParseError(Exception):
+    #Used when the signature parsing is going wrong (invalid signature ?)
+    pass
+
+
+class Constructor(object):
+    NAME = None  # used in arguments parsing, filename will be used if not set
+    # following attribute are used by default generation method
+    # they can be set to dict of strings using python formatting syntax
+    # dict keys will be used to select part to replace (e.g. "signals" key will
+    # replace ##SIGNALS_PART## in template), while the value is the format
+    # keys starting with "signal" will be used for signals, while ones starting with
+    # "method" will be used for methods
+    # check D-Bus constructor for an example
+    CORE_FORMATS = None
+    CORE_TEMPLATE = None
+    CORE_DEST = None
+    FRONTEND_FORMATS = None
+    FRONTEND_TEMPLATE = None
+    FRONTEND_DEST = None
+
+    def __init__(self, bridge_template, options):
+        self.bridge_template = bridge_template
+        self.args = options
+
+    @property
+    def constructor_dir(self):
+        constructor_mod = import_module(self.__module__)
+        return os.path.dirname(constructor_mod.__file__)
+
+    def getValues(self, name):
+        """Return values of a function in a dict
+        @param name: Name of the function to get
+        @return: dict, each key has the config value or None if the value is not set"""
+        function = {}
+        for option in ['type', 'category', 'sig_in', 'sig_out', 'doc']:
+            try:
+                value = self.bridge_template.get(name, option)
+            except NoOptionError:
+                value = None
+            function[option] = value
+        return function
+
+    def getDefault(self, name):
+        """Return default values of a function in a dict
+        @param name: Name of the function to get
+        @return: dict, each key is the integer param number (no key if no default value)"""
+        default_dict = {}
+        def_re = re.compile(r"param_(\d+)_default")
+
+        for option in self.bridge_template.options(name):
+            match = def_re.match(option)
+            if match:
+                try:
+                    idx = int(match.group(1))
+                except ValueError:
+                    raise ParseError("Invalid value [%s] for parameter number" % match.group(1))
+                default_dict[idx] = self.bridge_template.get(name, option)
+
+        return default_dict
+
+    def getFlags(self, name):
+        """Return list of flags set for this function
+
+        @param name: Name of the function to get
+        @return: List of flags (string)
+        """
+        flags = []
+        for option in self.bridge_template.options(name):
+            if option in C.DECLARATION_FLAGS:
+                flags.append(option)
+        return flags
+
+    def getArgumentsDoc(self, name):
+        """Return documentation of arguments
+        @param name: Name of the function to get
+        @return: dict, each key is the integer param number (no key if no argument doc), value is a tuple (name, doc)"""
+        doc_dict = {}
+        option_re = re.compile(r"doc_param_(\d+)")
+        value_re = re.compile(r"^(\w+): (.*)$", re.MULTILINE | re.DOTALL)
+        for option in self.bridge_template.options(name):
+            if option == 'doc_return':
+                doc_dict['return'] = self.bridge_template.get(name, option)
+                continue
+            match = option_re.match(option)
+            if match:
+                try:
+                    idx = int(match.group(1))
+                except ValueError:
+                    raise ParseError("Invalid value [%s] for parameter number" % match.group(1))
+                value_match = value_re.match(self.bridge_template.get(name, option))
+                if not value_match:
+                    raise ParseError("Invalid value for parameter doc [%i]" % idx)
+                doc_dict[idx] = (value_match.group(1), value_match.group(2))
+        return doc_dict
+
+    def getDoc(self, name):
+        """Return documentation of the method
+        @param name: Name of the function to get
+        @return: string documentation, or None"""
+        if self.bridge_template.has_option(name, "doc"):
+            return self.bridge_template.get(name, "doc")
+        return None
+
+    def argumentsParser(self, signature):
+        """Generator which return individual arguments signatures from a global signature"""
+        start = 0
+        i = 0
+
+        while i < len(signature):
+            if signature[i] not in ['b', 'y', 'n', 'i', 'x', 'q', 'u', 't', 'd', 's', 'a']:
+                raise ParseError("Unmanaged attribute type [%c]" % signature[i])
+
+            if signature[i] == 'a':
+                i += 1
+                if signature[i] != '{' and signature[i] != '(':  # FIXME: must manage tuples out of arrays
+                    i += 1
+                    yield signature[start:i]
+                    start = i
+                    continue  # we have a simple type for the array
+                opening_car = signature[i]
+                assert(opening_car in ['{', '('])
+                closing_car = '}' if opening_car == '{' else ')'
+                opening_count = 1
+                while (True):  # we have a dict or a list of tuples
+                    i += 1
+                    if i >= len(signature):
+                        raise ParseError("missing }")
+                    if signature[i] == opening_car:
+                        opening_count += 1
+                    if signature[i] == closing_car:
+                        opening_count -= 1
+                        if opening_count == 0:
+                            break
+            i += 1
+            yield signature[start:i]
+            start = i
+
+    def getArguments(self, signature, name=None, default=None, unicode_protect=False):
+        """Return arguments to user given a signature
+
+        @param signature: signature in the short form (using s,a,i,b etc)
+        @param name: dictionary of arguments name like given by getArguments
+        @param default: dictionary of default values, like given by getDefault
+        @param unicode_protect: activate unicode protection on strings (return strings as unicode(str))
+        @return: list of arguments that correspond to a signature (e.g.: "sss" return "arg1, arg2, arg3")
+        """
+        idx = 0
+        attr_string = []
+
+        for arg in self.argumentsParser(signature):
+            attr_string.append(("unicode(%(name)s)%(default)s" if (unicode_protect and arg == 's') else "%(name)s%(default)s") % {
+                'name': name[idx][0] if (name and idx in name) else "arg_%i" % idx,
+                'default': "=" + default[idx] if (default and idx in default) else ''})
+                # give arg_1, arg2, etc or name1, name2=default, etc.
+                #give unicode(arg_1), unicode(arg_2), etc. if unicode_protect is set and arg is a string
+            idx += 1
+
+        return ", ".join(attr_string)
+
+    def getTemplatePath(self, template_file):
+        """return template path corresponding to file name
+
+        @param template_file(str): name of template file
+        """
+        return os.path.join(self.constructor_dir, template_file)
+
+    def core_completion_method(self, completion, function, default, arg_doc, async_):
+        """override this method to extend completion"""
+        pass
+
+    def core_completion_signal(self, completion, function, default, arg_doc, async_):
+        """override this method to extend completion"""
+        pass
+
+    def frontend_completion_method(self, completion, function, default, arg_doc, async_):
+        """override this method to extend completion"""
+        pass
+
+    def frontend_completion_signal(self, completion, function, default, arg_doc, async_):
+        """override this method to extend completion"""
+        pass
+
+
+    def generate(self, side):
+        """generate bridge
+
+        call generateCoreSide or generateFrontendSide if they exists
+        else call generic self._generate method
+        """
+        try:
+            if side == "core":
+                method = self.generateCoreSide
+            elif side == "frontend":
+                method = self.generateFrontendSide
+        except AttributeError:
+            self._generate(side)
+        else:
+            method()
+
+    def _generate(self, side):
+        """generate the backend
+
+        this is a generic method which will use formats found in self.CORE_SIGNAL_FORMAT
+        and self.CORE_METHOD_FORMAT (standard format method will be used)
+        @param side(str): core or frontend
+        """
+        side_vars = []
+        for var in ('FORMATS', 'TEMPLATE', 'DEST'):
+            attr = "{}_{}".format(side.upper(), var)
+            value = getattr(self, attr)
+            if value is None:
+                raise NotImplementedError
+            side_vars.append(value)
+
+        FORMATS, TEMPLATE, DEST = side_vars
+        del side_vars
+
+        parts = {part.upper():[] for part in FORMATS}
+        sections = self.bridge_template.sections()
+        sections.sort()
+        for section in sections:
+            function = self.getValues(section)
+            print ("Adding %s %s" % (section, function["type"]))
+            default = self.getDefault(section)
+            arg_doc = self.getArgumentsDoc(section)
+            async_ = "async" in self.getFlags(section)
+            completion = {
+                'sig_in': function['sig_in'] or '',
+                'sig_out': function['sig_out'] or '',
+                'category': 'plugin' if function['category'] == 'plugin' else 'core',
+                'name': section,
+                'args': self.getArguments(function['sig_in'], name=arg_doc, default=default)}
+
+            extend_method = getattr(self, "{}_completion_{}".format(side, function["type"]))
+            extend_method(completion, function, default, arg_doc, async_)
+
+            for part, fmt in FORMATS.iteritems():
+                if part.startswith(function["type"]):
+                    parts[part.upper()].append(fmt.format(**completion))
+
+
+        #at this point, signals_part, methods_part and direct_calls should be filled,
+        #we just have to place them in the right part of the template
+        bridge = []
+        const_override = {env[len(C.ENV_OVERRIDE):]:v for env,v in os.environ.iteritems() if env.startswith(C.ENV_OVERRIDE)}
+        template_path = self.getTemplatePath(TEMPLATE)
+        try:
+            with open(template_path) as template:
+                for line in template:
+
+                    for part, extend_list in parts.iteritems():
+                        if line.startswith('##{}_PART##'.format(part)):
+                            bridge.extend(extend_list)
+                            break
+                    else:
+                        # the line is not a magic part replacement
+                        if line.startswith('const_'):
+                            const_name = line[len('const_'):line.find(' = ')].strip()
+                            if const_name in const_override:
+                                print("const {} overriden".format(const_name))
+                                bridge.append('const_{} = {}'.format(const_name, const_override[const_name]))
+                                continue
+                        bridge.append(line.replace('\n', ''))
+        except IOError:
+            print ("can't open template file [{}]".format(template_path))
+            sys.exit(1)
+
+        #now we write to final file
+        self.finalWrite(DEST, bridge)
+
+    def finalWrite(self, filename, file_buf):
+        """Write the final generated file in [dest dir]/filename
+
+        @param filename: name of the file to generate
+        @param file_buf: list of lines (stings) of the file
+        """
+        if os.path.exists(self.args.dest_dir) and not os.path.isdir(self.args.dest_dir):
+            print ("The destination dir [%s] can't be created: a file with this name already exists !")
+            sys.exit(1)
+        try:
+            if not os.path.exists(self.args.dest_dir):
+                os.mkdir(self.args.dest_dir)
+            full_path = os.path.join(self.args.dest_dir, filename)
+            if os.path.exists(full_path) and not self.args.force:
+                print ("The destination file [%s] already exists ! Use --force to overwrite it" % full_path)
+            try:
+                with open(full_path, 'w') as dest_file:
+                    dest_file.write('\n'.join(file_buf))
+            except IOError:
+                print ("Can't open destination file [%s]" % full_path)
+        except OSError:
+            print("It's not possible to generate the file, check your permissions")
+            exit(1)
--- a/src/bridge/bridge_constructor/bridge_constructor.py	Sun Oct 02 15:56:20 2016 +0200
+++ b/src/bridge/bridge_constructor/bridge_constructor.py	Sun Oct 02 22:44:33 2016 +0200
@@ -17,596 +17,54 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from sat.core.constants import Const as C
-import sys
-import os
+
+from sat.bridge import bridge_constructor
+from sat.bridge.bridge_constructor.constants import Const as C
+from sat.bridge.bridge_constructor import constructors, base_constructor
 import argparse
 from ConfigParser import SafeConfigParser as Parser
-from ConfigParser import NoOptionError
-import re
-from datetime import datetime
-from xml.dom import minidom
+from importlib  import import_module
+import os
+import os.path
 
 #consts
-NAME = u"bridge_constructor"
 __version__ = C.APP_VERSION
-DEST_DIR_DEFAULT = "generated"
-DESCRIPTION = u"""{name} Copyright (C) 2009-2016 Jérôme Poisson (aka Goffi)
-
-This script construct a SàT bridge using the given protocol
-
-This program comes with ABSOLUTELY NO WARRANTY;
-This is free software, and you are welcome to redistribute it
-under certain conditions.
-""".format(name=NAME, version=__version__)
-# TODO: move protocoles in separate files (plugins?)
-MANAGED_PROTOCOLES = ['dbus', 'mediawiki', 'dbus-xml']
-DEFAULT_PROTOCOLE = 'dbus'
-
-# flags used method/signal declaration (not to be confused with constructor flags)
-DECLARATION_FLAGS = ['deprecated', 'async']
-
-ENV_OVERRIDE = "SAT_BRIDGE_CONST_"  # Prefix used to override a constant
-
-
-
-class ParseError(Exception):
-    #Used when the signature parsing is going wrong (invalid signature ?)
-    pass
-
-
-class Constructor(object):
-
-    def __init__(self, bridge_template, options):
-        self.bridge_template = bridge_template
-        self.args = options
-
-    def getValues(self, name):
-        """Return values of a function in a dict
-        @param name: Name of the function to get
-        @return: dict, each key has the config value or None if the value is not set"""
-        function = {}
-        for option in ['type', 'category', 'sig_in', 'sig_out', 'doc']:
-            try:
-                value = self.bridge_template.get(name, option)
-            except NoOptionError:
-                value = None
-            function[option] = value
-        return function
-
-    def getDefault(self, name):
-        """Return default values of a function in a dict
-        @param name: Name of the function to get
-        @return: dict, each key is the integer param number (no key if no default value)"""
-        default_dict = {}
-        def_re = re.compile(r"param_(\d+)_default")
-
-        for option in self.bridge_template.options(name):
-            match = def_re.match(option)
-            if match:
-                try:
-                    idx = int(match.group(1))
-                except ValueError:
-                    raise ParseError("Invalid value [%s] for parameter number" % match.group(1))
-                default_dict[idx] = self.bridge_template.get(name, option)
-
-        return default_dict
-
-    def getFlags(self, name):
-        """Return list of flags set for this function
-        @param name: Name of the function to get
-        @return: List of flags (string)"""
-        flags = []
-        for option in self.bridge_template.options(name):
-            if option in DECLARATION_FLAGS:
-                flags.append(option)
-        return flags
-
-    def getArgumentsDoc(self, name):
-        """Return documentation of arguments
-        @param name: Name of the function to get
-        @return: dict, each key is the integer param number (no key if no argument doc), value is a tuple (name, doc)"""
-        doc_dict = {}
-        option_re = re.compile(r"doc_param_(\d+)")
-        value_re = re.compile(r"^(\w+): (.*)$", re.MULTILINE | re.DOTALL)
-        for option in self.bridge_template.options(name):
-            if option == 'doc_return':
-                doc_dict['return'] = self.bridge_template.get(name, option)
-                continue
-            match = option_re.match(option)
-            if match:
-                try:
-                    idx = int(match.group(1))
-                except ValueError:
-                    raise ParseError("Invalid value [%s] for parameter number" % match.group(1))
-                value_match = value_re.match(self.bridge_template.get(name, option))
-                if not value_match:
-                    raise ParseError("Invalid value for parameter doc [%i]" % idx)
-                doc_dict[idx] = (value_match.group(1), value_match.group(2))
-        return doc_dict
-
-    def getDoc(self, name):
-        """Return documentation of the method
-        @param name: Name of the function to get
-        @return: string documentation, or None"""
-        if self.bridge_template.has_option(name, "doc"):
-            return self.bridge_template.get(name, "doc")
-        return None
-
-    def argumentsParser(self, signature):
-        """Generator which return individual arguments signatures from a global signature"""
-        start = 0
-        i = 0
-
-        while i < len(signature):
-            if signature[i] not in ['b', 'y', 'n', 'i', 'x', 'q', 'u', 't', 'd', 's', 'a']:
-                raise ParseError("Unmanaged attribute type [%c]" % signature[i])
-
-            if signature[i] == 'a':
-                i += 1
-                if signature[i] != '{' and signature[i] != '(':  # FIXME: must manage tuples out of arrays
-                    i += 1
-                    yield signature[start:i]
-                    start = i
-                    continue  # we have a simple type for the array
-                opening_car = signature[i]
-                assert(opening_car in ['{', '('])
-                closing_car = '}' if opening_car == '{' else ')'
-                opening_count = 1
-                while (True):  # we have a dict or a list of tuples
-                    i += 1
-                    if i >= len(signature):
-                        raise ParseError("missing }")
-                    if signature[i] == opening_car:
-                        opening_count += 1
-                    if signature[i] == closing_car:
-                        opening_count -= 1
-                        if opening_count == 0:
-                            break
-            i += 1
-            yield signature[start:i]
-            start = i
-
-    def getArguments(self, signature, name=None, default=None, unicode_protect=False):
-        """Return arguments to user given a signature
-
-        @param signature: signature in the short form (using s,a,i,b etc)
-        @param name: dictionary of arguments name like given by getArguments
-        @param default: dictionary of default values, like given by getDefault
-        @param unicode_protect: activate unicode protection on strings (return strings as unicode(str))
-        @return: list of arguments that correspond to a signature (e.g.: "sss" return "arg1, arg2, arg3")
-        """
-        idx = 0
-        attr_string = []
-
-        for arg in self.argumentsParser(signature):
-            attr_string.append(("unicode(%(name)s)%(default)s" if (unicode_protect and arg == 's') else "%(name)s%(default)s") % {
-                'name': name[idx][0] if (name and idx in name) else "arg_%i" % idx,
-                'default': "=" + default[idx] if (default and idx in default) else ''})
-                # give arg_1, arg2, etc or name1, name2=default, etc.
-                #give unicode(arg_1), unicode(arg_2), etc. if unicode_protect is set and arg is a string
-            idx += 1
-
-        return ", ".join(attr_string)
-
-    def generateCoreSide(self):
-        """create the constructor in SàT core side (backend)"""
-        raise NotImplementedError
-
-    def generateFrontendSide(self):
-        """create the constructor in SàT frontend side"""
-        raise NotImplementedError
-
-    def finalWrite(self, filename, file_buf):
-        """Write the final generated file in [dest dir]/filename
-
-        @param filename: name of the file to generate
-        @param file_buf: list of lines (stings) of the file
-        """
-        if os.path.exists(self.args.dest_dir) and not os.path.isdir(self.args.dest_dir):
-            print ("The destination dir [%s] can't be created: a file with this name already exists !")
-            sys.exit(1)
-        try:
-            if not os.path.exists(self.args.dest_dir):
-                os.mkdir(self.args.dest_dir)
-            full_path = os.path.join(self.args.dest_dir, filename)
-            if os.path.exists(full_path) and not self.args.force:
-                print ("The destination file [%s] already exists ! Use --force to overwrite it" % full_path)
-            try:
-                with open(full_path, 'w') as dest_file:
-                    dest_file.write('\n'.join(file_buf))
-            except IOError:
-                print ("Can't open destination file [%s]" % full_path)
-        except OSError:
-            print("It's not possible to generate the file, check your permissions")
-            exit(1)
-
-
-class MediawikiConstructor(Constructor):
-
-    def __init__(self, bridge_template, options):
-        Constructor.__init__(self, bridge_template, options)
-        self.core_template = "mediawiki_template.tpl"
-        self.core_dest = "mediawiki.wiki"
-
-    def _addTextDecorations(self, text):
-        """Add text decorations like coloration or shortcuts"""
-
-        def anchor_link(match):
-            link = match.group(1)
-            #we add anchor_link for [method_name] syntax:
-            if link in self.bridge_template.sections():
-                return "[[#%s|%s]]" % (link, link)
-            print ("WARNING: found an anchor link to an unknown method")
-            return link
-
-        return re.sub(r"\[(\w+)\]", anchor_link, text)
-
-    def _wikiParameter(self, name, sig_in):
-        """Format parameters with the wiki syntax
-        @param name: name of the function
-        @param sig_in: signature in
-        @return: string of the formated parameters"""
-        arg_doc = self.getArgumentsDoc(name)
-        arg_default = self.getDefault(name)
-        args_str = self.getArguments(sig_in)
-        args = args_str.split(', ') if args_str else []  # ugly but it works :)
-        wiki = []
-        for i in range(len(args)):
-            if i in arg_doc:
-                name, doc = arg_doc[i]
-                doc = '\n:'.join(doc.rstrip('\n').split('\n'))
-                wiki.append("; %s: %s" % (name, self._addTextDecorations(doc)))
-            else:
-                wiki.append("; arg_%d: " % i)
-            if i in arg_default:
-                wiki.append(":''DEFAULT: %s''" % arg_default[i])
-        return "\n".join(wiki)
-
-    def _wikiReturn(self, name):
-        """Format return doc with the wiki syntax
-        @param name: name of the function
-        """
-        arg_doc = self.getArgumentsDoc(name)
-        wiki = []
-        if 'return' in arg_doc:
-            wiki.append('\n|-\n! scope=row | return value\n|')
-            wiki.append('<br />\n'.join(self._addTextDecorations(arg_doc['return']).rstrip('\n').split('\n')))
-        return "\n".join(wiki)
-
-    def generateCoreSide(self):
-        signals_part = []
-        methods_part = []
-        sections = self.bridge_template.sections()
-        sections.sort()
-        for section in sections:
-            function = self.getValues(section)
-            print ("Adding %s %s" % (section, function["type"]))
-            async_msg = """<br />'''This method is asynchronous'''"""
-            deprecated_msg = """<br />'''<font color="#FF0000">/!\ WARNING /!\ : This method is deprecated, please don't use it !</font>'''"""
-            signature_signal = \
-            """\
-! scope=row | signature
-| %s
-|-\
-""" % function['sig_in']
-            signature_method = \
-            """\
-! scope=row | signature in
-| %s
-|-
-! scope=row | signature out
-| %s
-|-\
-""" % (function['sig_in'], function['sig_out'])
-            completion = {
-                'signature': signature_signal if function['type'] == "signal" else signature_method,
-                'sig_out': function['sig_out'] or '',
-                'category': function['category'],
-                'name': section,
-                'doc': self.getDoc(section) or "FIXME: No description available",
-                'async': async_msg if "async" in self.getFlags(section) else "",
-                'deprecated': deprecated_msg if "deprecated" in self.getFlags(section) else "",
-                'parameters': self._wikiParameter(section, function['sig_in']),
-                'return': self._wikiReturn(section) if function['type'] == 'method' else ''}
-
-            dest = signals_part if function['type'] == "signal" else methods_part
-            dest.append("""\
-== %(name)s ==
-''%(doc)s''
-%(deprecated)s
-%(async)s
-{| class="wikitable" style="text-align:left; width:80%%;"
-! scope=row | category
-| %(category)s
-|-
-%(signature)s
-! scope=row | parameters
-|
-%(parameters)s%(return)s
-|}
-""" % completion)
-
-        #at this point, signals_part, and methods_part should be filled,
-        #we just have to place them in the right part of the template
-        core_bridge = []
-        try:
-            with open(self.core_template) as core_template:
-                for line in core_template:
-                    if line.startswith('##SIGNALS_PART##'):
-                        core_bridge.extend(signals_part)
-                    elif line.startswith('##METHODS_PART##'):
-                        core_bridge.extend(methods_part)
-                    elif line.startswith('##TIMESTAMP##'):
-                        core_bridge.append('Generated on %s' % datetime.now())
-                    else:
-                        core_bridge.append(line.replace('\n', ''))
-        except IOError:
-            print ("Can't open template file [%s]" % self.core_template)
-            sys.exit(1)
-
-        #now we write to final file
-        self.finalWrite(self.core_dest, core_bridge)
-
-
-class DbusConstructor(Constructor):
-
-    def __init__(self, bridge_template, options):
-        Constructor.__init__(self, bridge_template, options)
-        self.core_template = "dbus_core_template.py"
-        self.frontend_template = "dbus_frontend_template.py"
-        self.frontend_dest = self.core_dest = "DBus.py"
-
-    def generateCoreSide(self):
-        signals_part = []
-        methods_part = []
-        direct_calls = []
-        sections = self.bridge_template.sections()
-        sections.sort()
-        for section in sections:
-            function = self.getValues(section)
-            print ("Adding %s %s" % (section, function["type"]))
-            default = self.getDefault(section)
-            arg_doc = self.getArgumentsDoc(section)
-            async = "async" in self.getFlags(section)
-            completion = {
-                'sig_in': function['sig_in'] or '',
-                'sig_out': function['sig_out'] or '',
-                'category': 'PLUGIN' if function['category'] == 'plugin' else 'CORE',
-                'name': section,
-                'args': self.getArguments(function['sig_in'], name=arg_doc, default=default)}
-
-            if function["type"] == "signal":
-                completion['body'] = "pass" if not self.args.debug else 'log.debug ("%s")' % section
-                signals_part.append("""\
-    @dbus.service.signal(const_INT_PREFIX+const_%(category)s_SUFFIX,
-                         signature='%(sig_in)s')
-    def %(name)s(self, %(args)s):
-        %(body)s
-""" % completion)
-                direct_calls.append("""\
-    def %(name)s(self, %(args)s):
-        self.dbus_bridge.%(name)s(%(args)s)
-""" % completion)
-
-            elif function["type"] == "method":
-                completion['debug'] = "" if not self.args.debug else 'log.debug ("%s")\n%s' % (section, 8 * ' ')
-                completion['args_result'] = self.getArguments(function['sig_in'], name=arg_doc, unicode_protect=self.args.unicode)
-                completion['async_comma'] = ', ' if async and function['sig_in'] else ''
-                completion['async_args_def'] = 'callback=None, errback=None' if async else ''
-                completion['async_args_call'] = 'callback=callback, errback=errback' if async else ''
-                completion['async_callbacks'] = "('callback', 'errback')" if async else "None"
-                methods_part.append("""\
-    @dbus.service.method(const_INT_PREFIX+const_%(category)s_SUFFIX,
-                         in_signature='%(sig_in)s', out_signature='%(sig_out)s',
-                         async_callbacks=%(async_callbacks)s)
-    def %(name)s(self, %(args)s%(async_comma)s%(async_args_def)s):
-        %(debug)sreturn self._callback("%(name)s", %(args_result)s%(async_comma)s%(async_args_call)s)
-""" % completion)
-
-        #at this point, signals_part, methods_part and direct_calls should be filled,
-        #we just have to place them in the right part of the template
-        core_bridge = []
-        const_override_pref = filter(lambda env: env.startswith(ENV_OVERRIDE), os.environ)
-        const_override = [env[len(ENV_OVERRIDE):] for env in const_override_pref]
-        try:
-            with open(self.core_template) as core_template:
-                for line in core_template:
-                    if line.startswith('##SIGNALS_PART##'):
-                        core_bridge.extend(signals_part)
-                    elif line.startswith('##METHODS_PART##'):
-                        core_bridge.extend(methods_part)
-                    elif line.startswith('##DIRECT_CALLS##'):
-                        core_bridge.extend(direct_calls)
-                    else:
-                        if line.startswith('const_'):
-                            const_name = line[len('const_'):line.find(' = ')]
-                            if const_name in const_override:
-                                print ("const %s overriden" % const_name)
-                                core_bridge.append('const_%s = %s' % (const_name, os.environ[ENV_OVERRIDE + const_name]))
-                                continue
-                        core_bridge.append(line.replace('\n', ''))
-        except IOError:
-            print ("Can't open template file [%s]" % self.core_template)
-            sys.exit(1)
-
-        #now we write to final file
-        self.finalWrite(self.core_dest, core_bridge)
-
-    def generateFrontendSide(self):
-        methods_part = []
-        sections = self.bridge_template.sections()
-        sections.sort()
-        for section in sections:
-            function = self.getValues(section)
-            print ("Adding %s %s" % (section, function["type"]))
-            default = self.getDefault(section)
-            arg_doc = self.getArgumentsDoc(section)
-            async = "async" in self.getFlags(section)
-            completion = {
-                'sig_in': function['sig_in'] or '',
-                'sig_out': function['sig_out'] or '',
-                'category': 'plugin' if function['category'] == 'plugin' else 'core',
-                'name': section,
-                'args': self.getArguments(function['sig_in'], name=arg_doc, default=default)}
-
-            if function["type"] == "method":
-                # XXX: we can manage blocking call in the same way as async one: if callback is None the call will be blocking
-                completion['debug'] = "" if not self.args.debug else 'log.debug ("%s")\n%s' % (section, 8 * ' ')
-                completion['args_result'] = self.getArguments(function['sig_in'], name=arg_doc)
-                completion['async_args'] = 'callback=None, errback=None'
-                completion['async_comma'] = ', ' if function['sig_in'] else ''
-                completion['error_handler'] = """if callback is None:
-            error_handler = None
-        else:
-            if errback is None:
-                errback = log.error
-            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        """
-                if async:
-                    completion['blocking_call'] = ''
-                    completion['async_args_result'] = 'timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler'
-                else:
-                    # 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
-                    completion['blocking_call'] = """kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        """
-                    completion['async_args_result'] = '**kwargs'
-                result = "self.db_%(category)s_iface.%(name)s(%(args_result)s%(async_comma)s%(async_args_result)s)" % completion
-                completion['result'] = ("unicode(%s)" if self.args.unicode and function['sig_out'] == 's' else "%s") % result
-                methods_part.append("""\
-    def %(name)s(self, %(args)s%(async_comma)s%(async_args)s):
-        %(error_handler)s%(blocking_call)s%(debug)sreturn %(result)s
-""" % completion)
-
-        #at this point, methods_part should be filled,
-        #we just have to place it in the right part of the template
-        frontend_bridge = []
-        const_override_pref = filter(lambda env: env.startswith(ENV_OVERRIDE), os.environ)
-        const_override = [env[len(ENV_OVERRIDE):] for env in const_override_pref]
-        try:
-            with open(self.frontend_template) as frontend_template:
-                for line in frontend_template:
-                    if line.startswith('##METHODS_PART##'):
-                        frontend_bridge.extend(methods_part)
-                    else:
-                        if line.startswith('const_'):
-                            const_name = line[len('const_'):line.find(' = ')]
-                            if const_name in const_override:
-                                print ("const %s overriden" % const_name)
-                                frontend_bridge.append('const_%s = %s' % (const_name, os.environ[ENV_OVERRIDE + const_name]))
-                                continue
-                        frontend_bridge.append(line.replace('\n', ''))
-        except IOError:
-            print ("Can't open template file [%s]" % self.frontend_template)
-            sys.exit(1)
-
-        #now we write to final file
-        self.finalWrite(self.frontend_dest, frontend_bridge)
-
-
-class DbusXmlConstructor(Constructor):
-    """Constructor for DBus XML syntaxt (used by Qt frontend)"""
-
-    def __init__(self, bridge_template, options):
-        Constructor.__init__(self, bridge_template, options)
-
-        self.template = "dbus_xml_template.xml"
-        self.core_dest = "org.goffi.sat.xml"
-        self.default_annotation = {'a{ss}': 'StringDict',
-                                   'a(sa{ss}as)': 'QList<Contact>',
-                                   'a{i(ss)}': 'HistoryT',
-                                   'a(sss)': 'QList<MenuT>',
-                                   'a{sa{s(sia{ss})}}': 'PresenceStatusT',
-                                   }
-
-    def generateCoreSide(self):
-        try:
-            doc = minidom.parse(self.template)
-            interface_elt = doc.getElementsByTagName('interface')[0]
-        except IOError:
-            print ("Can't access template")
-            sys.exit(1)
-        except IndexError:
-            print ("Template error")
-            sys.exit(1)
-
-        sections = self.bridge_template.sections()
-        sections.sort()
-        for section in sections:
-            function = self.getValues(section)
-            print ("Adding %s %s" % (section, function["type"]))
-            new_elt = doc.createElement('method' if function["type"] == 'method' else 'signal')
-            new_elt.setAttribute('name', section)
-
-            idx = 0
-            args_doc = self.getArgumentsDoc(section)
-            for arg in self.argumentsParser(function['sig_in'] or ''):
-                arg_elt = doc.createElement('arg')
-                arg_elt.setAttribute('name', args_doc[idx][0] if idx in args_doc else "arg_%i" % idx)
-                arg_elt.setAttribute('type', arg)
-                _direction = 'in' if function["type"] == 'method' else 'out'
-                arg_elt.setAttribute('direction', _direction)
-                new_elt.appendChild(arg_elt)
-                if "annotation" in self.args.flags:
-                    if arg in self.default_annotation:
-                        annot_elt = doc.createElement("annotation")
-                        annot_elt.setAttribute('name', "com.trolltech.QtDBus.QtTypeName.In%d" % idx)
-                        annot_elt.setAttribute('value', self.default_annotation[arg])
-                        new_elt.appendChild(annot_elt)
-                idx += 1
-
-            if function['sig_out']:
-                arg_elt = doc.createElement('arg')
-                arg_elt.setAttribute('type', function['sig_out'])
-                arg_elt.setAttribute('direction', 'out')
-                new_elt.appendChild(arg_elt)
-                if "annotation" in self.args.flags:
-                    if function['sig_out'] in self.default_annotation:
-                        annot_elt = doc.createElement("annotation")
-                        annot_elt.setAttribute('name', "com.trolltech.QtDBus.QtTypeName.Out0")
-                        annot_elt.setAttribute('value', self.default_annotation[function['sig_out']])
-                        new_elt.appendChild(annot_elt)
-
-            interface_elt.appendChild(new_elt)
-
-        #now we write to final file
-        self.finalWrite(self.core_dest, [doc.toprettyxml()])
-
-
-class ConstructorError(Exception):
-    pass
-
-
-class ConstructorFactory(object):
-    def create(self, bridge_template, options):
-        if options.protocole == 'dbus':
-            return DbusConstructor(bridge_template, options)
-        elif options.protocole == 'mediawiki':
-            return MediawikiConstructor(bridge_template, options)
-        elif options.protocole == 'dbus-xml':
-            return DbusXmlConstructor(bridge_template, options)
-
-        raise ConstructorError('Unknown constructor type')
 
 
 class BridgeConstructor(object):
-    def __init__(self):
-        self.args = None
+
+    def importConstructors(self):
+        constructors_dir = os.path.dirname(constructors.__file__)
+        self.protocoles = {}
+        for dir_ in os.listdir(constructors_dir):
+            init_path = os.path.join(constructors_dir, dir_, '__init__.py')
+            constructor_path = os.path.join(constructors_dir, dir_, 'constructor.py')
+            module_path = "sat.bridge.bridge_constructor.constructors.{}.constructor".format(dir_)
+            if os.path.isfile(init_path) and os.path.isfile(constructor_path):
+                mod = import_module(module_path)
+                for attr in dir(mod):
+                    obj = getattr(mod, attr)
+                    if not isinstance(obj, type):
+                        continue
+                    if issubclass(obj, base_constructor.Constructor):
+                        name = obj.NAME or dir_
+                        self.protocoles[name] = obj
+                        break
+        if not self.protocoles:
+            raise ValueError("no protocole constructor found")
 
     def parse_args(self):
         """Check command line options"""
-        parser = argparse.ArgumentParser(description=DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter)
+        parser = argparse.ArgumentParser(description=C.DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter)
 
         parser.add_argument("--version", action="version", version= __version__)
-        parser.add_argument("-p", "--protocole", choices=MANAGED_PROTOCOLES, default=DEFAULT_PROTOCOLE,
+        default_protocole = C.DEFAULT_PROTOCOLE if C.DEFAULT_PROTOCOLE in self.protocoles else self.protocoles[0]
+        parser.add_argument("-p", "--protocole", choices=sorted(self.protocoles), default=default_protocole,
             help="generate bridge using PROTOCOLE (default: %(default)s)") # (default: %s, possible values: [%s])" % (DEFAULT_PROTOCOLE, ", ".join(MANAGED_PROTOCOLES)))
         parser.add_argument("-s", "--side", choices=("core", "frontend"), default="core",
             help="which side of the bridge do you want to make ?") # (default: %default, possible values: [core, frontend])")
-        parser.add_argument("-t", "--template", type=file, default='bridge_template.ini',
+        default_template = os.path.join(os.path.dirname(bridge_constructor.__file__), 'bridge_template.ini')
+        parser.add_argument("-t", "--template", type=file, default=default_template,
             help="use TEMPLATE to generate bridge (default: %(default)s)")
         parser.add_argument("-f", "--force", action="store_true",
             help=("force overwritting of existing files"))
@@ -616,24 +74,23 @@
             help=("remove unicode type protection from string results"))
         parser.add_argument("--flags", nargs='+', default=[],
             help=("constructors' specific flags"))
-        parser.add_argument("--dest-dir", default=DEST_DIR_DEFAULT,
-            help=("directory when the generated files will be written (default: %(default)s"))
+        parser.add_argument("--dest-dir", default=C.DEST_DIR_DEFAULT,
+            help=("directory when the generated files will be written (default: %(default)s)"))
 
         return parser.parse_args()
 
     def go(self):
+        self.importConstructors()
         args = self.parse_args()
-        self.template = Parser()
+        template_parser = Parser()
         try:
-            self.template.readfp(args.template)
+            template_parser.readfp(args.template)
         except IOError:
             print ("The template file doesn't exist or is not accessible")
             exit(1)
-        constructor = ConstructorFactory().create(self.template, args)
-        if args.side == "core":
-            constructor.generateCoreSide()
-        elif args.side == "frontend":
-            constructor.generateFrontendSide()
+        constructor = self.protocoles[args.protocole](template_parser, args)
+        constructor.generate(args.side)
+
 
 if __name__ == "__main__":
     bc = BridgeConstructor()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge_constructor/constants.py	Sun Oct 02 22:44:33 2016 +0200
@@ -0,0 +1,41 @@
+#!/usr/bin/env python2
+#-*- coding: utf-8 -*-
+
+# SàT: a XMPP client
+# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from sat.core import constants
+
+
+class Const(constants.Const):
+
+    NAME = u"bridge_constructor"
+    DEST_DIR_DEFAULT = "generated"
+    DESCRIPTION = u"""{name} Copyright (C) 2009-2016 Jérôme Poisson (aka Goffi)
+
+    This script construct a SàT bridge using the given protocol
+
+    This program comes with ABSOLUTELY NO WARRANTY;
+    This is free software, and you are welcome to redistribute it
+    under certain conditions.
+    """.format(name=NAME, version=constants.Const.APP_VERSION)
+# TODO: move protocoles in separate files (plugins?)
+    DEFAULT_PROTOCOLE = 'dbus'
+
+# flags used method/signal declaration (not to be confused with constructor flags)
+    DECLARATION_FLAGS = ['deprecated', 'async']
+
+    ENV_OVERRIDE = "SAT_BRIDGE_CONST_"  # Prefix used to override a constant
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge_constructor/constructors/dbus-xml/constructor.py	Sun Oct 02 22:44:33 2016 +0200
@@ -0,0 +1,91 @@
+#!/usr/bin/env python2
+#-*- coding: utf-8 -*-
+
+# SàT: a XMPP client
+# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from sat.bridge.bridge_constructor import base_constructor
+from xml.dom import minidom
+import sys
+
+
+class DbusXmlConstructor(base_constructor.Constructor):
+    """Constructor for DBus XML syntaxt (used by Qt frontend)"""
+
+    def __init__(self, bridge_template, options):
+        base_constructor.Constructor.__init__(self, bridge_template, options)
+
+        self.template = "dbus_xml_template.xml"
+        self.core_dest = "org.goffi.sat.xml"
+        self.default_annotation = {'a{ss}': 'StringDict',
+                                   'a(sa{ss}as)': 'QList<Contact>',
+                                   'a{i(ss)}': 'HistoryT',
+                                   'a(sss)': 'QList<MenuT>',
+                                   'a{sa{s(sia{ss})}}': 'PresenceStatusT',
+                                   }
+
+    def generateCoreSide(self):
+        try:
+            doc = minidom.parse(self.getTemplatePath(self.template))
+            interface_elt = doc.getElementsByTagName('interface')[0]
+        except IOError:
+            print ("Can't access template")
+            sys.exit(1)
+        except IndexError:
+            print ("Template error")
+            sys.exit(1)
+
+        sections = self.bridge_template.sections()
+        sections.sort()
+        for section in sections:
+            function = self.getValues(section)
+            print ("Adding %s %s" % (section, function["type"]))
+            new_elt = doc.createElement('method' if function["type"] == 'method' else 'signal')
+            new_elt.setAttribute('name', section)
+
+            idx = 0
+            args_doc = self.getArgumentsDoc(section)
+            for arg in self.argumentsParser(function['sig_in'] or ''):
+                arg_elt = doc.createElement('arg')
+                arg_elt.setAttribute('name', args_doc[idx][0] if idx in args_doc else "arg_%i" % idx)
+                arg_elt.setAttribute('type', arg)
+                _direction = 'in' if function["type"] == 'method' else 'out'
+                arg_elt.setAttribute('direction', _direction)
+                new_elt.appendChild(arg_elt)
+                if "annotation" in self.args.flags:
+                    if arg in self.default_annotation:
+                        annot_elt = doc.createElement("annotation")
+                        annot_elt.setAttribute('name', "com.trolltech.QtDBus.QtTypeName.In%d" % idx)
+                        annot_elt.setAttribute('value', self.default_annotation[arg])
+                        new_elt.appendChild(annot_elt)
+                idx += 1
+
+            if function['sig_out']:
+                arg_elt = doc.createElement('arg')
+                arg_elt.setAttribute('type', function['sig_out'])
+                arg_elt.setAttribute('direction', 'out')
+                new_elt.appendChild(arg_elt)
+                if "annotation" in self.args.flags:
+                    if function['sig_out'] in self.default_annotation:
+                        annot_elt = doc.createElement("annotation")
+                        annot_elt.setAttribute('name', "com.trolltech.QtDBus.QtTypeName.Out0")
+                        annot_elt.setAttribute('value', self.default_annotation[function['sig_out']])
+                        new_elt.appendChild(annot_elt)
+
+            interface_elt.appendChild(new_elt)
+
+        #now we write to final file
+        self.finalWrite(self.core_dest, [doc.toprettyxml()])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge_constructor/constructors/dbus-xml/dbus_xml_template.xml	Sun Oct 02 22:44:33 2016 +0200
@@ -0,0 +1,4 @@
+<node>
+  <interface name="org.goffi.SAT.core">
+  </interface>
+</node>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge_constructor/constructors/dbus/constructor.py	Sun Oct 02 22:44:33 2016 +0200
@@ -0,0 +1,97 @@
+#!/usr/bin/env python2
+#-*- coding: utf-8 -*-
+
+# SàT: a XMPP client
+# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from sat.bridge.bridge_constructor import base_constructor
+
+
+class DbusConstructor(base_constructor.Constructor):
+    NAME = "dbus"
+    CORE_TEMPLATE = "dbus_core_template.py"
+    CORE_DEST = "DBus.py"
+    CORE_FORMATS = {
+        'signals': """\
+    @dbus.service.signal(const_INT_PREFIX+const_{category}_SUFFIX,
+                         signature='{sig_in}')
+    def {name}(self, {args}):
+        {body}\n""",
+
+        'methods': """\
+    @dbus.service.method(const_INT_PREFIX+const_{category}_SUFFIX,
+                         in_signature='{sig_in}', out_signature='{sig_out}',
+                         async_callbacks={async_callbacks})
+    def {name}(self, {args}{async_comma}{async_args_def}):
+        {debug}return self._callback("{name}", {args_result}{async_comma}{async_args_call})\n""",
+
+        'signal_direct_calls': """\
+    def {name}(self, {args}):
+        self.dbus_bridge.{name}({args})\n""",
+        }
+
+    FRONTEND_TEMPLATE = "dbus_frontend_template.py"
+    FRONTEND_DEST = CORE_DEST
+    FRONTEND_FORMATS = {
+        'methods': """\
+    def {name}(self, {args}{async_comma}{async_args}):
+        {error_handler}{blocking_call}{debug}return {result}\n""",
+        }
+
+    def core_completion_signal(self, completion, function, default, arg_doc, async_):
+        completion['category'] = completion['category'].upper()
+        completion['body'] = "pass" if not self.args.debug else 'log.debug ("{}")'.format(completion['name'])
+
+    def core_completion_method(self, completion, function, default, arg_doc, async_):
+        completion.update({
+            'debug': "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' '),
+            'args_result': self.getArguments(function['sig_in'], name=arg_doc, unicode_protect=self.args.unicode),
+            'async_comma': ', ' if async_ and function['sig_in'] else '',
+            'async_args_def': 'callback=None, errback=None' if async_ else '',
+            'async_args_call': 'callback=callback, errback=errback' if async_ else '',
+            'async_callbacks': "('callback', 'errback')" if async_ else "None",
+            'category': completion['category'].upper(),
+            })
+
+    def frontend_completion_method(self, completion, function, default, arg_doc, async_):
+        completion.update({
+            # XXX: we can manage blocking call in the same way as async one: if callback is None the call will be blocking
+            'debug': "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' '),
+            'args_result': self.getArguments(function['sig_in'], name=arg_doc),
+            'async_args': 'callback=None, errback=None',
+            'async_comma': ', ' if function['sig_in'] else '',
+            'error_handler': """if callback is None:
+            error_handler = None
+        else:
+            if errback is None:
+                errback = log.error
+            error_handler = lambda err:errback(dbus_to_bridge_exception(err))
+        """,
+            })
+        if async_:
+            completion['blocking_call'] = ''
+            completion['async_args_result'] = 'timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler'
+        else:
+            # 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
+            completion['blocking_call'] = """kwargs={}
+        if callback is not None:
+            kwargs['timeout'] = const_TIMEOUT
+            kwargs['reply_handler'] = callback
+            kwargs['error_handler'] = error_handler
+        """
+            completion['async_args_result'] = '**kwargs'
+        result = "self.db_%(category)s_iface.%(name)s(%(args_result)s%(async_comma)s%(async_args_result)s)" % completion
+        completion['result'] = ("unicode(%s)" if self.args.unicode and function['sig_out'] == 's' else "%s") % result
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py	Sun Oct 02 22:44:33 2016 +0200
@@ -0,0 +1,246 @@
+#!/usr/bin/env python2
+#-*- coding: utf-8 -*-
+
+# SAT: a jabber client
+# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from sat.core.i18n import _
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+import inspect
+from sat.core.log import getLogger
+log = getLogger(__name__)
+from twisted.internet.defer import Deferred
+from sat.core.exceptions import BridgeInitError
+
+const_INT_PREFIX = "org.goffi.SAT"  # Interface prefix
+const_ERROR_PREFIX = const_INT_PREFIX + ".error"
+const_OBJ_PATH = '/org/goffi/SAT/bridge'
+const_CORE_SUFFIX = ".core"
+const_PLUGIN_SUFFIX = ".plugin"
+
+
+class ParseError(Exception):
+    pass
+
+
+class MethodNotRegistered(dbus.DBusException):
+    _dbus_error_name = const_ERROR_PREFIX + ".MethodNotRegistered"
+
+
+class InternalError(dbus.DBusException):
+    _dbus_error_name = const_ERROR_PREFIX + ".InternalError"
+
+
+class AsyncNotDeferred(dbus.DBusException):
+    _dbus_error_name = const_ERROR_PREFIX + ".AsyncNotDeferred"
+
+
+class DeferredNotAsync(dbus.DBusException):
+    _dbus_error_name = const_ERROR_PREFIX + ".DeferredNotAsync"
+
+
+class GenericException(dbus.DBusException):
+    def __init__(self, twisted_error):
+        """
+
+        @param twisted_error (Failure): instance of twisted Failure
+        @return: DBusException
+        """
+        super(GenericException, self).__init__()
+        try:
+            # twisted_error.value is a class
+            class_ = twisted_error.value().__class__
+        except TypeError:
+            # twisted_error.value is an instance
+            class_ = twisted_error.value.__class__
+            message = twisted_error.getErrorMessage()
+            try:
+                self.args = (message, twisted_error.value.condition)
+            except AttributeError:
+                self.args = (message,)
+        self._dbus_error_name = '.'.join([const_ERROR_PREFIX, class_.__module__, class_.__name__])
+
+
+class DbusObject(dbus.service.Object):
+
+    def __init__(self, bus, path):
+        dbus.service.Object.__init__(self, bus, path)
+        log.debug("Init DbusObject...")
+        self.cb = {}
+
+    def register(self, name, cb):
+        self.cb[name] = cb
+
+    def _callback(self, name, *args, **kwargs):
+        """call the callback if it exists, raise an exception else
+        if the callback return a deferred, use async methods"""
+        if not name in self.cb:
+            raise MethodNotRegistered
+
+        if "callback" in kwargs:
+            #we must have errback too
+            if not "errback" in kwargs:
+                log.error("errback is missing in method call [%s]" % name)
+                raise InternalError
+            callback = kwargs.pop("callback")
+            errback = kwargs.pop("errback")
+            async = True
+        else:
+            async = False
+        result = self.cb[name](*args, **kwargs)
+        if async:
+            if not isinstance(result, Deferred):
+                log.error("Asynchronous method [%s] does not return a Deferred." % name)
+                raise AsyncNotDeferred
+            result.addCallback(lambda result: callback() if result is None else callback(result))
+            result.addErrback(lambda err: errback(GenericException(err)))
+        else:
+            if isinstance(result, Deferred):
+                log.error("Synchronous method [%s] return a Deferred." % name)
+                raise DeferredNotAsync
+            return result
+    ### signals ###
+
+    @dbus.service.signal(const_INT_PREFIX + const_PLUGIN_SUFFIX,
+                         signature='')
+    def dummySignal(self):
+        #FIXME: workaround for addSignal (doesn't work if one method doensn't
+        #       already exist for plugins), probably missing some initialisation, need
+        #       further investigations
+        pass
+
+##SIGNALS_PART##
+    ### methods ###
+
+##METHODS_PART##
+    def __attributes(self, in_sign):
+        """Return arguments to user given a in_sign
+        @param in_sign: in_sign in the short form (using s,a,i,b etc)
+        @return: list of arguments that correspond to a in_sign (e.g.: "sss" return "arg1, arg2, arg3")"""
+        i = 0
+        idx = 0
+        attr = []
+        while i < len(in_sign):
+            if in_sign[i] not in ['b', 'y', 'n', 'i', 'x', 'q', 'u', 't', 'd', 's', 'a']:
+                raise ParseError("Unmanaged attribute type [%c]" % in_sign[i])
+
+            attr.append("arg_%i" % idx)
+            idx += 1
+
+            if in_sign[i] == 'a':
+                i += 1
+                if in_sign[i] != '{' and in_sign[i] != '(':  # FIXME: must manage tuples out of arrays
+                    i += 1
+                    continue  # we have a simple type for the array
+                opening_car = in_sign[i]
+                assert(opening_car in ['{', '('])
+                closing_car = '}' if opening_car == '{' else ')'
+                opening_count = 1
+                while (True):  # we have a dict or a list of tuples
+                    i += 1
+                    if i >= len(in_sign):
+                        raise ParseError("missing }")
+                    if in_sign[i] == opening_car:
+                        opening_count += 1
+                    if in_sign[i] == closing_car:
+                        opening_count -= 1
+                        if opening_count == 0:
+                            break
+            i += 1
+        return attr
+
+    def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False):
+        """Dynamically add a method to Dbus Bridge"""
+        inspect_args = inspect.getargspec(method)
+
+        _arguments = inspect_args.args
+        _defaults = list(inspect_args.defaults or [])
+
+        if inspect.ismethod(method):
+            #if we have a method, we don't want the first argument (usually 'self')
+            del(_arguments[0])
+
+        #first arguments are for the _callback method
+        arguments_callback = ', '.join([repr(name)] + ((_arguments + ['callback=callback', 'errback=errback']) if async else _arguments))
+
+        if async:
+            _arguments.extend(['callback', 'errback'])
+            _defaults.extend([None, None])
+
+        #now we create a second list with default values
+        for i in range(1, len(_defaults) + 1):
+            _arguments[-i] = "%s = %s" % (_arguments[-i], repr(_defaults[-i]))
+
+        arguments_defaults = ', '.join(_arguments)
+
+        code = compile('def %(name)s (self,%(arguments_defaults)s): return self._callback(%(arguments_callback)s)' %
+                       {'name': name, 'arguments_defaults': arguments_defaults, 'arguments_callback': arguments_callback}, '<DBus bridge>', 'exec')
+        exec (code)  # FIXME: to the same thing in a cleaner way, without compile/exec
+        method = locals()[name]
+        async_callbacks = ('callback', 'errback') if async else None
+        setattr(DbusObject, name, dbus.service.method(
+            const_INT_PREFIX + int_suffix, in_signature=in_sign, out_signature=out_sign,
+            async_callbacks=async_callbacks)(method))
+        function = getattr(self, name)
+        func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface]
+        func_table[function.__name__] = function  # Needed for introspection
+
+    def addSignal(self, name, int_suffix, signature, doc={}):
+        """Dynamically add a signal to Dbus Bridge"""
+        attributes = ', '.join(self.__attributes(signature))
+        #TODO: use doc parameter to name attributes
+
+        #code = compile ('def '+name+' (self,'+attributes+'): log.debug ("'+name+' signal")', '<DBus bridge>','exec') #XXX: the log.debug is too annoying with xmllog
+        code = compile('def ' + name + ' (self,' + attributes + '): pass', '<DBus bridge>', 'exec')
+        exec (code)
+        signal = locals()[name]
+        setattr(DbusObject, name, dbus.service.signal(
+            const_INT_PREFIX + int_suffix, signature=signature)(signal))
+        function = getattr(self, name)
+        func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface]
+        func_table[function.__name__] = function  # Needed for introspection
+
+
+class DBusBridge(object):
+    def __init__(self):
+        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+        log.info("Init DBus...")
+        try:
+            self.session_bus = dbus.SessionBus()
+        except dbus.DBusException as e:
+            if e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported':
+                log.error(_(u"D-Bus is not launched, please see README to see instructions on how to launch it"))
+            raise BridgeInitError
+        self.dbus_name = dbus.service.BusName(const_INT_PREFIX, self.session_bus)
+        self.dbus_bridge = DbusObject(self.session_bus, const_OBJ_PATH)
+
+##SIGNAL_DIRECT_CALLS_PART##
+    def register(self, name, callback):
+        log.debug("registering DBus bridge method [%s]" % name)
+        self.dbus_bridge.register(name, callback)
+
+    def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}):
+        """Dynamically add a method to Dbus Bridge"""
+        #FIXME: doc parameter is kept only temporary, the time to remove it from calls
+        log.debug("Adding method [%s] to DBus bridge" % name)
+        self.dbus_bridge.addMethod(name, int_suffix, in_sign, out_sign, method, async)
+        self.register(name, method)
+
+    def addSignal(self, name, int_suffix, signature, doc={}):
+        self.dbus_bridge.addSignal(name, int_suffix, signature, doc)
+        setattr(DBusBridge, name, getattr(self.dbus_bridge, name))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py	Sun Oct 02 22:44:33 2016 +0200
@@ -0,0 +1,137 @@
+#!/usr/bin/env python2
+#-*- coding: utf-8 -*-
+
+# SAT communication bridge
+# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from sat.core.i18n import _
+from bridge_frontend import BridgeFrontend, BridgeException
+import dbus
+from sat.core.log import getLogger
+log = getLogger(__name__)
+from sat.core.exceptions import BridgeExceptionNoService, BridgeInitError
+
+from dbus.mainloop.glib import DBusGMainLoop
+DBusGMainLoop(set_as_default=True)
+
+import ast
+
+const_INT_PREFIX = "org.goffi.SAT"  # Interface prefix
+const_ERROR_PREFIX = const_INT_PREFIX + ".error"
+const_OBJ_PATH = '/org/goffi/SAT/bridge'
+const_CORE_SUFFIX = ".core"
+const_PLUGIN_SUFFIX = ".plugin"
+const_TIMEOUT = 120
+
+
+def dbus_to_bridge_exception(dbus_e):
+    """Convert a DBusException to a BridgeException.
+
+    @param dbus_e (DBusException)
+    @return: BridgeException
+    """
+    full_name = dbus_e.get_dbus_name()
+    if full_name.startswith(const_ERROR_PREFIX):
+        name = dbus_e.get_dbus_name()[len(const_ERROR_PREFIX) + 1:]
+    else:
+        name = full_name
+    # XXX: dbus_e.args doesn't contain the original DBusException args, but we
+    # receive its serialized form in dbus_e.args[0]. From that we can rebuild
+    # the original arguments list thanks to ast.literal_eval (secure eval).
+    message = dbus_e.get_dbus_message()  # similar to dbus_e.args[0]
+    try:
+        message, condition = ast.literal_eval(message)
+    except (SyntaxError, ValueError, TypeError):
+        condition = ''
+    return BridgeException(name, message, condition)
+
+
+class DBusBridgeFrontend(BridgeFrontend):
+    def __init__(self):
+        try:
+            self.sessions_bus = dbus.SessionBus()
+            self.db_object = self.sessions_bus.get_object(const_INT_PREFIX,
+                                                          const_OBJ_PATH)
+            self.db_core_iface = dbus.Interface(self.db_object,
+                                                dbus_interface=const_INT_PREFIX + const_CORE_SUFFIX)
+            self.db_plugin_iface = dbus.Interface(self.db_object,
+                                                  dbus_interface=const_INT_PREFIX + const_PLUGIN_SUFFIX)
+        except dbus.exceptions.DBusException, e:
+            if e._dbus_error_name in ('org.freedesktop.DBus.Error.ServiceUnknown',
+                                      'org.freedesktop.DBus.Error.Spawn.ExecFailed'):
+                raise BridgeExceptionNoService
+            elif e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported':
+                log.error(_(u"D-Bus is not launched, please see README to see instructions on how to launch it"))
+                raise BridgeInitError
+            else:
+                raise e
+        #props = self.db_core_iface.getProperties()
+
+    def register(self, functionName, handler, iface="core"):
+        if iface == "core":
+            self.db_core_iface.connect_to_signal(functionName, handler)
+        elif iface == "plugin":
+            self.db_plugin_iface.connect_to_signal(functionName, handler)
+        else:
+            log.error(_('Unknown interface'))
+
+    def __getattribute__(self, name):
+        """ usual __getattribute__ if the method exists, else try to find a plugin method """
+        try:
+            return object.__getattribute__(self, name)
+        except AttributeError:
+            # The attribute is not found, we try the plugin proxy to find the requested method
+
+            def getPluginMethod(*args, **kwargs):
+                # We first check if we have an async call. We detect this in two ways:
+                #   - if we have the 'callback' and 'errback' keyword arguments
+                #   - or if the last two arguments are callable
+
+                async = False
+                args = list(args)
+
+                if kwargs:
+                    if 'callback' in kwargs:
+                        async = True
+                        _callback = kwargs.pop('callback')
+                        _errback = kwargs.pop('errback', lambda failure: log.error(unicode(failure)))
+                    try:
+                        args.append(kwargs.pop('profile'))
+                    except KeyError:
+                        try:
+                            args.append(kwargs.pop('profile_key'))
+                        except KeyError:
+                            pass
+                    # at this point, kwargs should be empty
+                    if kwargs:
+                        log.warnings(u"unexpected keyword arguments, they will be ignored: {}".format(kwargs))
+                elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]):
+                    async = True
+                    _errback = args.pop()
+                    _callback = args.pop()
+
+                method = getattr(self.db_plugin_iface, name)
+
+                if async:
+                    kwargs['timeout'] = const_TIMEOUT
+                    kwargs['reply_handler'] = _callback
+                    kwargs['error_handler'] = lambda err: _errback(dbus_to_bridge_exception(err))
+
+                return method(*args, **kwargs)
+
+            return getPluginMethod
+
+##METHODS_PART##
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge_constructor/constructors/mediawiki/constructor.py	Sun Oct 02 22:44:33 2016 +0200
@@ -0,0 +1,153 @@
+#!/usr/bin/env python2
+#-*- coding: utf-8 -*-
+
+# SàT: a XMPP client
+# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from sat.bridge.bridge_constructor import base_constructor
+import sys
+from datetime import datetime
+import re
+
+
+class MediawikiConstructor(base_constructor.Constructor):
+
+    def __init__(self, bridge_template, options):
+        base_constructor.Constructor.__init__(self, bridge_template, options)
+        self.core_template = "mediawiki_template.tpl"
+        self.core_dest = "mediawiki.wiki"
+
+    def _addTextDecorations(self, text):
+        """Add text decorations like coloration or shortcuts"""
+
+        def anchor_link(match):
+            link = match.group(1)
+            #we add anchor_link for [method_name] syntax:
+            if link in self.bridge_template.sections():
+                return "[[#%s|%s]]" % (link, link)
+            print ("WARNING: found an anchor link to an unknown method")
+            return link
+
+        return re.sub(r"\[(\w+)\]", anchor_link, text)
+
+    def _wikiParameter(self, name, sig_in):
+        """Format parameters with the wiki syntax
+        @param name: name of the function
+        @param sig_in: signature in
+        @return: string of the formated parameters"""
+        arg_doc = self.getArgumentsDoc(name)
+        arg_default = self.getDefault(name)
+        args_str = self.getArguments(sig_in)
+        args = args_str.split(', ') if args_str else []  # ugly but it works :)
+        wiki = []
+        for i in range(len(args)):
+            if i in arg_doc:
+                name, doc = arg_doc[i]
+                doc = '\n:'.join(doc.rstrip('\n').split('\n'))
+                wiki.append("; %s: %s" % (name, self._addTextDecorations(doc)))
+            else:
+                wiki.append("; arg_%d: " % i)
+            if i in arg_default:
+                wiki.append(":''DEFAULT: %s''" % arg_default[i])
+        return "\n".join(wiki)
+
+    def _wikiReturn(self, name):
+        """Format return doc with the wiki syntax
+        @param name: name of the function
+        """
+        arg_doc = self.getArgumentsDoc(name)
+        wiki = []
+        if 'return' in arg_doc:
+            wiki.append('\n|-\n! scope=row | return value\n|')
+            wiki.append('<br />\n'.join(self._addTextDecorations(arg_doc['return']).rstrip('\n').split('\n')))
+        return "\n".join(wiki)
+
+    def generateCoreSide(self):
+        signals_part = []
+        methods_part = []
+        sections = self.bridge_template.sections()
+        sections.sort()
+        for section in sections:
+            function = self.getValues(section)
+            print ("Adding %s %s" % (section, function["type"]))
+            async_msg = """<br />'''This method is asynchronous'''"""
+            deprecated_msg = """<br />'''<font color="#FF0000">/!\ WARNING /!\ : This method is deprecated, please don't use it !</font>'''"""
+            signature_signal = \
+            """\
+! scope=row | signature
+| %s
+|-\
+""" % function['sig_in']
+            signature_method = \
+            """\
+! scope=row | signature in
+| %s
+|-
+! scope=row | signature out
+| %s
+|-\
+""" % (function['sig_in'], function['sig_out'])
+            completion = {
+                'signature': signature_signal if function['type'] == "signal" else signature_method,
+                'sig_out': function['sig_out'] or '',
+                'category': function['category'],
+                'name': section,
+                'doc': self.getDoc(section) or "FIXME: No description available",
+                'async': async_msg if "async" in self.getFlags(section) else "",
+                'deprecated': deprecated_msg if "deprecated" in self.getFlags(section) else "",
+                'parameters': self._wikiParameter(section, function['sig_in']),
+                'return': self._wikiReturn(section) if function['type'] == 'method' else ''}
+
+            dest = signals_part if function['type'] == "signal" else methods_part
+            dest.append("""\
+== %(name)s ==
+''%(doc)s''
+%(deprecated)s
+%(async)s
+{| class="wikitable" style="text-align:left; width:80%%;"
+! scope=row | category
+| %(category)s
+|-
+%(signature)s
+! scope=row | parameters
+|
+%(parameters)s%(return)s
+|}
+""" % completion)
+
+        #at this point, signals_part, and methods_part should be filled,
+        #we just have to place them in the right part of the template
+        core_bridge = []
+        template_path = self.getTemplatePath(self.core_template)
+        try:
+            with open(template_path) as core_template:
+                for line in core_template:
+                    if line.startswith('##SIGNALS_PART##'):
+                        core_bridge.extend(signals_part)
+                    elif line.startswith('##METHODS_PART##'):
+                        core_bridge.extend(methods_part)
+                    elif line.startswith('##TIMESTAMP##'):
+                        core_bridge.append('Generated on %s' % datetime.now())
+                    else:
+                        core_bridge.append(line.replace('\n', ''))
+        except IOError:
+            print ("Can't open template file [%s]" % template_path)
+            sys.exit(1)
+
+        #now we write to final file
+        self.finalWrite(self.core_dest, core_bridge)
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge_constructor/constructors/mediawiki/mediawiki_template.tpl	Sun Oct 02 22:44:33 2016 +0200
@@ -0,0 +1,11 @@
+[[Catégorie:Salut à Toi]]
+[[Catégorie:documentation développeur]]
+
+= Overview =
+This is an autogenerated doc for SàT bridge's API
+= Signals =
+##SIGNALS_PART##
+= Methods =
+##METHODS_PART##
+----
+##TIMESTAMP##
--- a/src/bridge/bridge_constructor/dbus_core_template.py	Sun Oct 02 15:56:20 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,248 +0,0 @@
-#!/usr/bin/env python2
-#-*- coding: utf-8 -*-
-
-# SAT: a jabber client
-# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from sat.core.i18n import _
-from bridge import Bridge
-import dbus
-import dbus.service
-import dbus.mainloop.glib
-import inspect
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from twisted.internet.defer import Deferred
-from sat.core.exceptions import BridgeInitError
-
-const_INT_PREFIX = "org.goffi.SAT"  # Interface prefix
-const_ERROR_PREFIX = const_INT_PREFIX + ".error"
-const_OBJ_PATH = '/org/goffi/SAT/bridge'
-const_CORE_SUFFIX = ".core"
-const_PLUGIN_SUFFIX = ".plugin"
-
-
-class ParseError(Exception):
-    pass
-
-
-class MethodNotRegistered(dbus.DBusException):
-    _dbus_error_name = const_ERROR_PREFIX + ".MethodNotRegistered"
-
-
-class InternalError(dbus.DBusException):
-    _dbus_error_name = const_ERROR_PREFIX + ".InternalError"
-
-
-class AsyncNotDeferred(dbus.DBusException):
-    _dbus_error_name = const_ERROR_PREFIX + ".AsyncNotDeferred"
-
-
-class DeferredNotAsync(dbus.DBusException):
-    _dbus_error_name = const_ERROR_PREFIX + ".DeferredNotAsync"
-
-
-class GenericException(dbus.DBusException):
-    def __init__(self, twisted_error):
-        """
-
-        @param twisted_error (Failure): instance of twisted Failure
-        @return: DBusException
-        """
-        super(GenericException, self).__init__()
-        try:
-            # twisted_error.value is a class
-            class_ = twisted_error.value().__class__
-        except TypeError:
-            # twisted_error.value is an instance
-            class_ = twisted_error.value.__class__
-            message = twisted_error.getErrorMessage()
-            try:
-                self.args = (message, twisted_error.value.condition)
-            except AttributeError:
-                self.args = (message,)
-        self._dbus_error_name = '.'.join([const_ERROR_PREFIX, class_.__module__, class_.__name__])
-
-
-class DbusObject(dbus.service.Object):
-
-    def __init__(self, bus, path):
-        dbus.service.Object.__init__(self, bus, path)
-        log.debug("Init DbusObject...")
-        self.cb = {}
-
-    def register(self, name, cb):
-        self.cb[name] = cb
-
-    def _callback(self, name, *args, **kwargs):
-        """call the callback if it exists, raise an exception else
-        if the callback return a deferred, use async methods"""
-        if not name in self.cb:
-            raise MethodNotRegistered
-
-        if "callback" in kwargs:
-            #we must have errback too
-            if not "errback" in kwargs:
-                log.error("errback is missing in method call [%s]" % name)
-                raise InternalError
-            callback = kwargs.pop("callback")
-            errback = kwargs.pop("errback")
-            async = True
-        else:
-            async = False
-        result = self.cb[name](*args, **kwargs)
-        if async:
-            if not isinstance(result, Deferred):
-                log.error("Asynchronous method [%s] does not return a Deferred." % name)
-                raise AsyncNotDeferred
-            result.addCallback(lambda result: callback() if result is None else callback(result))
-            result.addErrback(lambda err: errback(GenericException(err)))
-        else:
-            if isinstance(result, Deferred):
-                log.error("Synchronous method [%s] return a Deferred." % name)
-                raise DeferredNotAsync
-            return result
-    ### signals ###
-
-    @dbus.service.signal(const_INT_PREFIX + const_PLUGIN_SUFFIX,
-                         signature='')
-    def dummySignal(self):
-        #FIXME: workaround for addSignal (doesn't work if one method doensn't
-        #       already exist for plugins), probably missing some initialisation, need
-        #       further investigations
-        pass
-
-##SIGNALS_PART##
-    ### methods ###
-
-##METHODS_PART##
-    def __attributes(self, in_sign):
-        """Return arguments to user given a in_sign
-        @param in_sign: in_sign in the short form (using s,a,i,b etc)
-        @return: list of arguments that correspond to a in_sign (e.g.: "sss" return "arg1, arg2, arg3")"""
-        i = 0
-        idx = 0
-        attr = []
-        while i < len(in_sign):
-            if in_sign[i] not in ['b', 'y', 'n', 'i', 'x', 'q', 'u', 't', 'd', 's', 'a']:
-                raise ParseError("Unmanaged attribute type [%c]" % in_sign[i])
-
-            attr.append("arg_%i" % idx)
-            idx += 1
-
-            if in_sign[i] == 'a':
-                i += 1
-                if in_sign[i] != '{' and in_sign[i] != '(':  # FIXME: must manage tuples out of arrays
-                    i += 1
-                    continue  # we have a simple type for the array
-                opening_car = in_sign[i]
-                assert(opening_car in ['{', '('])
-                closing_car = '}' if opening_car == '{' else ')'
-                opening_count = 1
-                while (True):  # we have a dict or a list of tuples
-                    i += 1
-                    if i >= len(in_sign):
-                        raise ParseError("missing }")
-                    if in_sign[i] == opening_car:
-                        opening_count += 1
-                    if in_sign[i] == closing_car:
-                        opening_count -= 1
-                        if opening_count == 0:
-                            break
-            i += 1
-        return attr
-
-    def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False):
-        """Dynamically add a method to Dbus Bridge"""
-        inspect_args = inspect.getargspec(method)
-
-        _arguments = inspect_args.args
-        _defaults = list(inspect_args.defaults or [])
-
-        if inspect.ismethod(method):
-            #if we have a method, we don't want the first argument (usually 'self')
-            del(_arguments[0])
-
-        #first arguments are for the _callback method
-        arguments_callback = ', '.join([repr(name)] + ((_arguments + ['callback=callback', 'errback=errback']) if async else _arguments))
-
-        if async:
-            _arguments.extend(['callback', 'errback'])
-            _defaults.extend([None, None])
-
-        #now we create a second list with default values
-        for i in range(1, len(_defaults) + 1):
-            _arguments[-i] = "%s = %s" % (_arguments[-i], repr(_defaults[-i]))
-
-        arguments_defaults = ', '.join(_arguments)
-
-        code = compile('def %(name)s (self,%(arguments_defaults)s): return self._callback(%(arguments_callback)s)' %
-                       {'name': name, 'arguments_defaults': arguments_defaults, 'arguments_callback': arguments_callback}, '<DBus bridge>', 'exec')
-        exec (code)  # FIXME: to the same thing in a cleaner way, without compile/exec
-        method = locals()[name]
-        async_callbacks = ('callback', 'errback') if async else None
-        setattr(DbusObject, name, dbus.service.method(
-            const_INT_PREFIX + int_suffix, in_signature=in_sign, out_signature=out_sign,
-            async_callbacks=async_callbacks)(method))
-        function = getattr(self, name)
-        func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface]
-        func_table[function.__name__] = function  # Needed for introspection
-
-    def addSignal(self, name, int_suffix, signature, doc={}):
-        """Dynamically add a signal to Dbus Bridge"""
-        attributes = ', '.join(self.__attributes(signature))
-        #TODO: use doc parameter to name attributes
-
-        #code = compile ('def '+name+' (self,'+attributes+'): log.debug ("'+name+' signal")', '<DBus bridge>','exec') #XXX: the log.debug is too annoying with xmllog
-        code = compile('def ' + name + ' (self,' + attributes + '): pass', '<DBus bridge>', 'exec')
-        exec (code)
-        signal = locals()[name]
-        setattr(DbusObject, name, dbus.service.signal(
-            const_INT_PREFIX + int_suffix, signature=signature)(signal))
-        function = getattr(self, name)
-        func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface]
-        func_table[function.__name__] = function  # Needed for introspection
-
-
-class DBusBridge(Bridge):
-    def __init__(self):
-        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
-        Bridge.__init__(self)
-        log.info("Init DBus...")
-        try:
-            self.session_bus = dbus.SessionBus()
-        except dbus.DBusException as e:
-            if e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported':
-                log.error(_(u"D-Bus is not launched, please see README to see instructions on how to launch it"))
-            raise BridgeInitError
-        self.dbus_name = dbus.service.BusName(const_INT_PREFIX, self.session_bus)
-        self.dbus_bridge = DbusObject(self.session_bus, const_OBJ_PATH)
-
-##DIRECT_CALLS##
-    def register(self, name, callback):
-        log.debug("registering DBus bridge method [%s]" % name)
-        self.dbus_bridge.register(name, callback)
-
-    def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}):
-        """Dynamically add a method to Dbus Bridge"""
-        #FIXME: doc parameter is kept only temporary, the time to remove it from calls
-        log.debug("Adding method [%s] to DBus bridge" % name)
-        self.dbus_bridge.addMethod(name, int_suffix, in_sign, out_sign, method, async)
-        self.register(name, method)
-
-    def addSignal(self, name, int_suffix, signature, doc={}):
-        self.dbus_bridge.addSignal(name, int_suffix, signature, doc)
-        setattr(DBusBridge, name, getattr(self.dbus_bridge, name))
--- a/src/bridge/bridge_constructor/dbus_frontend_template.py	Sun Oct 02 15:56:20 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,137 +0,0 @@
-#!/usr/bin/env python2
-#-*- coding: utf-8 -*-
-
-# SAT communication bridge
-# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org)
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-from sat.core.i18n import _
-from bridge_frontend import BridgeFrontend, BridgeException
-import dbus
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from sat.core.exceptions import BridgeExceptionNoService, BridgeInitError
-
-from dbus.mainloop.glib import DBusGMainLoop
-DBusGMainLoop(set_as_default=True)
-
-import ast
-
-const_INT_PREFIX = "org.goffi.SAT"  # Interface prefix
-const_ERROR_PREFIX = const_INT_PREFIX + ".error"
-const_OBJ_PATH = '/org/goffi/SAT/bridge'
-const_CORE_SUFFIX = ".core"
-const_PLUGIN_SUFFIX = ".plugin"
-const_TIMEOUT = 120
-
-
-def dbus_to_bridge_exception(dbus_e):
-    """Convert a DBusException to a BridgeException.
-
-    @param dbus_e (DBusException)
-    @return: BridgeException
-    """
-    full_name = dbus_e.get_dbus_name()
-    if full_name.startswith(const_ERROR_PREFIX):
-        name = dbus_e.get_dbus_name()[len(const_ERROR_PREFIX) + 1:]
-    else:
-        name = full_name
-    # XXX: dbus_e.args doesn't contain the original DBusException args, but we
-    # receive its serialized form in dbus_e.args[0]. From that we can rebuild
-    # the original arguments list thanks to ast.literal_eval (secure eval).
-    message = dbus_e.get_dbus_message()  # similar to dbus_e.args[0]
-    try:
-        message, condition = ast.literal_eval(message)
-    except (SyntaxError, ValueError, TypeError):
-        condition = ''
-    return BridgeException(name, message, condition)
-
-
-class DBusBridgeFrontend(BridgeFrontend):
-    def __init__(self):
-        try:
-            self.sessions_bus = dbus.SessionBus()
-            self.db_object = self.sessions_bus.get_object(const_INT_PREFIX,
-                                                          const_OBJ_PATH)
-            self.db_core_iface = dbus.Interface(self.db_object,
-                                                dbus_interface=const_INT_PREFIX + const_CORE_SUFFIX)
-            self.db_plugin_iface = dbus.Interface(self.db_object,
-                                                  dbus_interface=const_INT_PREFIX + const_PLUGIN_SUFFIX)
-        except dbus.exceptions.DBusException, e:
-            if e._dbus_error_name in ('org.freedesktop.DBus.Error.ServiceUnknown',
-                                      'org.freedesktop.DBus.Error.Spawn.ExecFailed'):
-                raise BridgeExceptionNoService
-            elif e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported':
-                log.error(_(u"D-Bus is not launched, please see README to see instructions on how to launch it"))
-                raise BridgeInitError
-            else:
-                raise e
-        #props = self.db_core_iface.getProperties()
-
-    def register(self, functionName, handler, iface="core"):
-        if iface == "core":
-            self.db_core_iface.connect_to_signal(functionName, handler)
-        elif iface == "plugin":
-            self.db_plugin_iface.connect_to_signal(functionName, handler)
-        else:
-            log.error(_('Unknown interface'))
-
-    def __getattribute__(self, name):
-        """ usual __getattribute__ if the method exists, else try to find a plugin method """
-        try:
-            return object.__getattribute__(self, name)
-        except AttributeError:
-            # The attribute is not found, we try the plugin proxy to find the requested method
-
-            def getPluginMethod(*args, **kwargs):
-                # We first check if we have an async call. We detect this in two ways:
-                #   - if we have the 'callback' and 'errback' keyword arguments
-                #   - or if the last two arguments are callable
-
-                async = False
-                args = list(args)
-
-                if kwargs:
-                    if 'callback' in kwargs:
-                        async = True
-                        _callback = kwargs.pop('callback')
-                        _errback = kwargs.pop('errback', lambda failure: log.error(unicode(failure)))
-                    try:
-                        args.append(kwargs.pop('profile'))
-                    except KeyError:
-                        try:
-                            args.append(kwargs.pop('profile_key'))
-                        except KeyError:
-                            pass
-                    # at this point, kwargs should be empty
-                    if kwargs:
-                        log.warnings(u"unexpected keyword arguments, they will be ignored: {}".format(kwargs))
-                elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]):
-                    async = True
-                    _errback = args.pop()
-                    _callback = args.pop()
-
-                method = getattr(self.db_plugin_iface, name)
-
-                if async:
-                    kwargs['timeout'] = const_TIMEOUT
-                    kwargs['reply_handler'] = _callback
-                    kwargs['error_handler'] = lambda err: _errback(dbus_to_bridge_exception(err))
-
-                return method(*args, **kwargs)
-
-            return getPluginMethod
-
-##METHODS_PART##
--- a/src/bridge/bridge_constructor/dbus_xml_template.xml	Sun Oct 02 15:56:20 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-<node>
-  <interface name="org.goffi.SAT.core">
-  </interface>
-</node>
--- a/src/bridge/bridge_constructor/mediawiki_template.tpl	Sun Oct 02 15:56:20 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-[[Catégorie:Salut à Toi]]
-[[Catégorie:documentation développeur]]
-
-= Overview =
-This is an autogenerated doc for SàT bridge's API
-= Signals =
-##SIGNALS_PART##
-= Methods =
-##METHODS_PART##
-----
-##TIMESTAMP##