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