# HG changeset patch # User Goffi # Date 1475441073 -7200 # Node ID da4097de5a9510ccf04b0bf06317eb73986326d8 # Parent e1015a5df6f5533a0e5589d791f875df1a2ad924 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 diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/DBus.py --- 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 . 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() diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge.py --- 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 . - -from sat.core.log import getLogger -log = getLogger(__name__) - - -class Bridge(object): - def __init__(self): - log.info("Bridge initialization") diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/__init__.py diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/base_constructor.py --- /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 . + +"""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 -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/bridge_constructor.py --- 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 . -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 @@ 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 -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constants.py --- /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 . + +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 -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constructors/__init__.py diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constructors/dbus-xml/__init__.py diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constructors/dbus-xml/constructor.py --- /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 . + +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 -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constructors/dbus-xml/dbus_xml_template.xml --- /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 @@ + + + + diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constructors/dbus/__init__.py diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constructors/dbus/constructor.py --- /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 . + +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 -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py --- /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 . + +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}, '', '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")', '','exec') #XXX: the log.debug is too annoying with xmllog + code = compile('def ' + name + ' (self,' + attributes + '): pass', '', '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)) diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constructors/dbus/dbus_frontend_template.py --- /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 . + +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## diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constructors/mediawiki/__init__.py diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constructors/mediawiki/constructor.py --- /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 . + +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 -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/constructors/mediawiki/mediawiki_template.tpl --- /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## diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/dbus_core_template.py --- 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 . - -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}, '', '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")', '','exec') #XXX: the log.debug is too annoying with xmllog - code = compile('def ' + name + ' (self,' + attributes + '): pass', '', '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)) diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/dbus_frontend_template.py --- 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 . - -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## diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/dbus_xml_template.xml --- 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 @@ - - - - diff -r e1015a5df6f5 -r da4097de5a95 src/bridge/bridge_constructor/mediawiki_template.tpl --- 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##