diff --git a/src/bridge/DBus.py b/src/bridge/DBus.py
index 76d7d3b6..73a4ef04 100755
--- a/src/bridge/DBus.py
+++ b/src/bridge/DBus.py
@@ -18,7 +18,6 @@
# along with this program. If not, see .
from sat.core.i18n import _
-from bridge import Bridge
import dbus
import dbus.service
import dbus.mainloop.glib
@@ -556,10 +555,9 @@ class DbusObject(dbus.service.Object):
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()
diff --git a/src/bridge/bridge.py b/src/bridge/bridge.py
deleted file mode 100755
index 1503366f..00000000
--- a/src/bridge/bridge.py
+++ /dev/null
@@ -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 .
-
-from sat.core.log import getLogger
-log = getLogger(__name__)
-
-
-class Bridge(object):
- def __init__(self):
- log.info("Bridge initialization")
diff --git a/src/bridge/bridge_constructor/__init__.py b/src/bridge/bridge_constructor/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/bridge/bridge_constructor/base_constructor.py b/src/bridge/bridge_constructor/base_constructor.py
new file mode 100644
index 00000000..0218ea11
--- /dev/null
+++ b/src/bridge/bridge_constructor/base_constructor.py
@@ -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 .
+
+"""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)
diff --git a/src/bridge/bridge_constructor/bridge_constructor.py b/src/bridge/bridge_constructor/bridge_constructor.py
index 060aef9e..10b6a635 100755
--- a/src/bridge/bridge_constructor/bridge_constructor.py
+++ b/src/bridge/bridge_constructor/bridge_constructor.py
@@ -17,596 +17,54 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-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('
\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 = """
'''This method is asynchronous'''"""
- deprecated_msg = """
'''/!\ WARNING /!\ : This method is deprecated, please don't use it !'''"""
- 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',
- 'a{i(ss)}': 'HistoryT',
- 'a(sss)': 'QList',
- '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 @@ class BridgeConstructor(object):
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()
diff --git a/src/bridge/bridge_constructor/constants.py b/src/bridge/bridge_constructor/constants.py
new file mode 100644
index 00000000..7778a77f
--- /dev/null
+++ b/src/bridge/bridge_constructor/constants.py
@@ -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 .
+
+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
diff --git a/src/bridge/bridge_constructor/constructors/__init__.py b/src/bridge/bridge_constructor/constructors/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/bridge/bridge_constructor/constructors/dbus-xml/__init__.py b/src/bridge/bridge_constructor/constructors/dbus-xml/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/bridge/bridge_constructor/constructors/dbus-xml/constructor.py b/src/bridge/bridge_constructor/constructors/dbus-xml/constructor.py
new file mode 100644
index 00000000..8e94c617
--- /dev/null
+++ b/src/bridge/bridge_constructor/constructors/dbus-xml/constructor.py
@@ -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 .
+
+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',
+ 'a{i(ss)}': 'HistoryT',
+ 'a(sss)': 'QList',
+ '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()])
diff --git a/src/bridge/bridge_constructor/dbus_xml_template.xml b/src/bridge/bridge_constructor/constructors/dbus-xml/dbus_xml_template.xml
similarity index 100%
rename from src/bridge/bridge_constructor/dbus_xml_template.xml
rename to src/bridge/bridge_constructor/constructors/dbus-xml/dbus_xml_template.xml
diff --git a/src/bridge/bridge_constructor/constructors/dbus/__init__.py b/src/bridge/bridge_constructor/constructors/dbus/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/bridge/bridge_constructor/constructors/dbus/constructor.py b/src/bridge/bridge_constructor/constructors/dbus/constructor.py
new file mode 100644
index 00000000..ff8ff312
--- /dev/null
+++ b/src/bridge/bridge_constructor/constructors/dbus/constructor.py
@@ -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 .
+
+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
diff --git a/src/bridge/bridge_constructor/dbus_core_template.py b/src/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py
similarity index 99%
rename from src/bridge/bridge_constructor/dbus_core_template.py
rename to src/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py
index 271f78b4..bbca0293 100644
--- a/src/bridge/bridge_constructor/dbus_core_template.py
+++ b/src/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py
@@ -18,7 +18,6 @@
# along with this program. If not, see .
from sat.core.i18n import _
-from bridge import Bridge
import dbus
import dbus.service
import dbus.mainloop.glib
@@ -217,10 +216,9 @@ class DbusObject(dbus.service.Object):
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()
@@ -231,7 +229,7 @@ class DBusBridge(Bridge):
self.dbus_name = dbus.service.BusName(const_INT_PREFIX, self.session_bus)
self.dbus_bridge = DbusObject(self.session_bus, const_OBJ_PATH)
-##DIRECT_CALLS##
+##SIGNAL_DIRECT_CALLS_PART##
def register(self, name, callback):
log.debug("registering DBus bridge method [%s]" % name)
self.dbus_bridge.register(name, callback)
diff --git a/src/bridge/bridge_constructor/dbus_frontend_template.py b/src/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py
similarity index 100%
rename from src/bridge/bridge_constructor/dbus_frontend_template.py
rename to src/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py
diff --git a/src/bridge/bridge_constructor/constructors/mediawiki/__init__.py b/src/bridge/bridge_constructor/constructors/mediawiki/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/bridge/bridge_constructor/constructors/mediawiki/constructor.py b/src/bridge/bridge_constructor/constructors/mediawiki/constructor.py
new file mode 100644
index 00000000..7fea3963
--- /dev/null
+++ b/src/bridge/bridge_constructor/constructors/mediawiki/constructor.py
@@ -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 .
+
+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('
\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 = """
'''This method is asynchronous'''"""
+ deprecated_msg = """
'''/!\ WARNING /!\ : This method is deprecated, please don't use it !'''"""
+ 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)
+
+
diff --git a/src/bridge/bridge_constructor/mediawiki_template.tpl b/src/bridge/bridge_constructor/constructors/mediawiki/mediawiki_template.tpl
similarity index 100%
rename from src/bridge/bridge_constructor/mediawiki_template.tpl
rename to src/bridge/bridge_constructor/constructors/mediawiki/mediawiki_template.tpl