changeset 265:b5f1f3dc9ac6

bridge: automatic bridge generator, first draft
author Goffi <goffi@goffi.org>
date Mon, 24 Jan 2011 01:22:00 +0100 (2011-01-24)
parents 27bc5d7732a3
children c4b84a2d2ad1
files src/bridge/bridge_contructor.py src/bridge/bridge_template.ini
diffstat 2 files changed, 550 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge_contructor.py	Mon Jan 24 01:22:00 2011 +0100
@@ -0,0 +1,234 @@
+#!/usr/bin/python
+#-*- coding: utf-8 -*-
+
+"""
+SAT: a jabber client
+Copyright (C) 2009, 2010, 2011  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 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+
+#consts
+NAME = u"bridge_constructor"
+VERSION="0.1.0"
+ABOUT = NAME+u""" v%s (c) Jérôme Poisson (aka Goffi) 2011
+
+---
+"""+NAME+u""" Copyright (C) 2011  Jérôme Poisson (aka Goffi)
+This program comes with ABSOLUTELY NO WARRANTY;
+This is free software, and you are welcome to redistribute it
+under certain conditions.
+---
+
+This script construct a SàT bridge using the given protocol
+"""
+MANAGED_PROTOCOLES=['dbus']
+DEFAULT_PROTOCOLE='dbus'
+
+import sys
+import os
+from os import path
+from optparse import OptionParser
+from ConfigParser import RawConfigParser as Parser
+from ConfigParser import NoOptionError
+
+
+class ParseError(Exception):
+    #Used when the signature parsing is going wrong (invalid signature ?) 
+    pass
+
+class Constructor:
+    
+    def __init__(self, bridge_template, options):
+        self.bridge_template = bridge_template
+        self.options = 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 getArguments(self, signature):
+        """Return arguments to user given a signature
+        @param signature: signature in the short form (using s,a,i,b etc)
+        @return: list of arguments that correspond to a signature (e.g.: "sss" return "arg1, arg2, arg3")""" 
+        i=0
+        idx=0
+        attr_string=""
+        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])
+
+            attr_string += ("" if idx==0 else ", ") + ("arg_%i" % idx)
+            idx+=1
+
+            if signature[i] == 'a':
+                i+=1
+                if signature[i]!='{' and signature[i]!='(': #FIXME: must manage tuples out of arrays
+                    i+=1
+                    continue #we have a simple type for the array
+                opening_car = signature[i]
+                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
+        return attr_string
+
+    def generateCoreSide(self):
+        """create the constructor in SàT core side (backend)"""
+        raise NotImplementedError
+
+class DbusConstructor(Constructor):
+
+    def __init__(self, bridge_template, options):
+        Constructor.__init__(self, bridge_template, options)
+        self.core_template="dbus_core_template.py" 
+        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"]))
+            completion = {
+        'sig_in':function['sig_in'] or '',
+        'sig_out':function['sig_out'] or '',
+        'category':'REQ' if function['category'] == 'request' else 'COMM',
+        'name':section, 
+        'args':self.getArguments(function['sig_in'])
+        }
+
+            if function["type"] == "signal":
+                completion['body'] = "pass" if not self.options.debug else '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.options.debug else 'debug ("%s")\n%s' % (section,8*' ')
+                methods_part.append("""\
+    @dbus.service.method(const_INT_PREFIX+const_%(category)s_SUFFIX,
+                         in_signature='%(sig_in)s', out_signature='%(sig_out)s')
+    def %(name)s(self, %(args)s):
+        %(debug)sreturn self.cb["%(name)s"](%(args)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 = []
+        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:
+                        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
+        if os.path.exists(self.core_dest) and not self.options.force:
+            print ("The destination file [%s] already exists ! Use --force to overwrite it" % self.core_dest) 
+        try:
+            with open(self.core_dest,'w') as dest_file:
+                dest_file.write('\n'.join(core_bridge))
+        except IOError:
+            print ("Can't open destination file [%s]" % self.dest_file)
+
+class ConstructorError(Exception):
+    pass
+
+class ConstructorFactory:
+    def create(self, bridge_template, options):
+       if options.protocole=='dbus':
+           return DbusConstructor(bridge_template, options)
+
+       raise ConstructorError('Unknown constructor type')
+
+class BridgeConstructor:
+    def __init__(self):
+        self.options = None
+    
+    def check_options(self):
+        """Check command line options"""
+        _usage="""
+        %prog [options]
+
+        %prog --help for options list
+        """
+        parser = OptionParser(usage=_usage,version=ABOUT % VERSION)
+
+        parser.add_option("-p", "--protocole", action="store", type="string", default=DEFAULT_PROTOCOLE,
+                    help="Generate bridge using PROTOCOLE (default: %s, possible values: [%s])" % (DEFAULT_PROTOCOLE, ", ".join(MANAGED_PROTOCOLES)))
+        parser.add_option("-s", "--side", action="store", type="string", default="core",
+                    help="Which side of the bridge do you want to make ? (default: %default, possible values: [core, frontend])")
+        parser.add_option("-t", "--template", action="store", type="string", default='bridge_template.ini',
+                    help="Use TEMPLATE to generate bridge (default: %default)")
+        parser.add_option("-f", "--force", action="store_true", default=False,
+                    help=("Force overwritting of existing files"))
+        parser.add_option("-d", "--debug", action="store_true", default=False,
+                    help=("Add debug information printing"))
+
+        (self.options, args) = parser.parse_args()
+        return args
+
+    def go(self):
+        self.check_options()
+        self.template = Parser()
+        try:
+            self.template.readfp(open(self.options.template))
+        except IOError:
+            print ("The template file doesn't exist or is not accessible")
+            exit(1)
+        constructor = ConstructorFactory().create(self.template, self.options)
+        if self.options.side == "core":
+            constructor.generateCoreSide()
+
+if __name__ == "__main__":
+    bc = BridgeConstructor()
+    bc.go()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge_template.ini	Mon Jan 24 01:22:00 2011 +0100
@@ -0,0 +1,316 @@
+;signals
+
+[connected]
+type=signal
+category=communication
+sig_in=s
+doc=Connection is done
+
+[disconnected]
+type=signal
+category=communication
+sig_in=s
+doc=Connection is finished or lost
+
+[connection_error]
+type=signal
+category=communication
+sig_in=ss
+doc=Something went wront with the connection
+
+[newContact]
+type=signal
+category=communication
+sig_in=sa{ss}ass
+doc=New contact received in roster
+
+[newMessage]
+type=signal
+category=communication
+sig_in=sssss
+doc=A message has been received
+
+[newAlert]
+type=signal
+category=communication
+sig_in=ssss
+doc=A message has been received
+
+[presenceUpdate]
+type=signal
+category=communication
+sig_in=ssia{ss}s
+doc=Somebody changed his presence informations.
+
+[subscribe]
+type=signal
+category=communication
+sig_in=sss
+doc=Somebody wants to be added in roster list
+
+[paramUpdate]
+type=signal
+category=communication
+sig_in=ssss
+doc=A parameter has been changed
+
+[contactDeleted]
+type=signal
+category=communication
+sig_in=ss
+doc=A contact has been supressed from roster list
+
+[askConfirmation]
+type=signal
+category=request
+sig_in=ssa{ss}
+doc=A confirmation is needed for an action
+
+[actionResult]
+type=signal
+category=request
+sig_in=ssa{ss}
+doc=Requested result of an action
+
+[actionResultExt]
+type=signal
+category=request
+sig_in=ssa{sa{ss}}
+doc=Requested result of an action (Extended)
+
+[updatedValue]
+type=signal
+category=request
+sig_in=sa{ss}
+doc=A value has been updated
+
+;methods
+
+[getVersion]
+type=method
+category=request
+sig_in=
+sig_out=s
+doc=Get "Salut à Toi" version
+
+[getProfileName]
+type=method
+category=request
+sig_in=s
+sig_out=s
+doc=Get real profile name from profile key
+
+[getProfilesList]
+type=method
+category=request
+sig_in=
+sig_out=as
+doc=Get all profiles
+
+[createProfile]
+type=method
+category=request
+sig_in=s
+sig_out=i
+doc=Create a new profile
+
+[deleteProfile]
+type=method
+category=request
+sig_in=s
+sig_out=i
+doc=Delete a profile
+
+[registerNewAccount]
+type=method
+category=communication
+sig_in=sssi
+sig_out=s
+doc=Register a new account on a given server
+
+[connect]
+type=method
+category=communication
+sig_in=s
+sig_out=
+param_0_default="@DEFAULT@"
+doc=Connect a profile
+
+[disconnect]
+type=method
+category=communication
+sig_in=s
+sig_out=
+param_0_default="@DEFAULT@"
+doc=Disconnect a profile
+
+[isConnected]
+type=method
+category=communication
+sig_in=
+sig_out=b
+param_0_default="@DEFAULT@"
+doc=Tell if a profile is connected
+
+[getContacts]
+type=method
+category=communication
+sig_in=s
+sig_out=a(sa{ss}as)
+param_0_default="@DEFAULT@"
+doc=Return informations about all contacts
+
+[getPresenceStatus]
+type=method
+category=communication
+sig_in=s
+sig_out=a{sa{s(sia{ss})}}
+param_0_default="@DEFAULT@"
+doc=Return presence informations of all contacts
+
+[getWaitingSub]
+type=method
+category=communication
+sig_in=s
+sig_out=a{ss}
+param_0_default="@DEFAULT@"
+doc=Get subscription requests in queue
+
+[sendMessage]
+type=method
+category=communication
+sig_in=sssss
+sig_out=
+param_4_default="@DEFAULT@"
+doc=Send a message
+
+[setPresence]
+type=method
+category=communication
+sig_in=ssia{ss}s
+sig_out=
+param_4_default="@DEFAULT@"
+doc=Set presence information for the profile
+
+[subscription]
+type=method
+category=communication
+sig_in=sss
+sig_out=
+param_2_default="@DEFAULT@"
+doc=Send subscription request/answer to a contact
+
+[setParam]
+type=method
+category=communication
+sig_in=ssss
+sig_out=
+param_3_default="@DEFAULT@"
+doc=Change a parameter
+
+[getParamA]
+type=method
+category=communication
+sig_in=sss
+sig_out=s
+param_2_default="@DEFAULT@"
+doc=Helper method to get a parameter's attribute
+
+[getParamsUI]
+type=method
+category=communication
+sig_in=s
+sig_out=s
+param_0_default="@DEFAULT@"
+doc=Return a SàT XMLUI for parameters
+
+[getParams]
+type=method
+category=communication
+sig_in=s
+sig_out=s
+param_0_default="@DEFAULT@"
+doc=Return XML of parameters
+
+[getParamsForCategory]
+type=method
+category=communication
+sig_in=ss
+sig_out=s
+param_1_default="@DEFAULT@"
+doc=Return a xml of all params in a category 
+
+[getParamsCategories]
+type=method
+category=communication
+sig_in=
+sig_out=as
+doc=Get all categories currently existing in parameters
+
+[getHistory]
+type=method
+category=communication
+sig_in=ssi
+sig_out=a{i(ss)}
+doc=Get history of a communication between two entities
+
+[addContact]
+type=method
+category=communication
+sig_in=ss
+sig_out=
+param_1_default="@DEFAULT@"
+doc=Add a contact to profile's roster list
+
+[delContact]
+type=method
+category=communication
+sig_in=ss
+sig_out=
+param_1_default="@DEFAULT@"
+doc=Remove a contact from profile's roster list
+
+[launchAction]
+type=method
+category=request
+sig_in=sa{ss}s
+sig_out=s
+param_2_default="@DEFAULT@"
+doc=Launch a specific action
+
+[confirmationAnswer]
+type=method
+category=request
+sig_in=sba{ss}
+sig_out=
+doc=Give answer to a confirmation request
+
+[getProgress]
+type=method
+category=request
+sig_in=s
+sig_out=a{ss}
+doc=Get progress information for an action
+
+[getMenus]
+type=method
+category=request
+sig_in=
+sig_out=a(sss)
+doc=Get all additional menus
+
+[getMenuHelp]
+type=method
+category=request
+sig_in=sss
+sig_out=s
+param_2="NORMAL"
+doc=Get help informationd for a menu
+
+[callMenu]
+type=method
+category=request
+sig_in=ssss
+sig_out=s
+doc=Execute action associated with a menu
+