Mercurial > libervia-backend
view src/bridge/bridge_constructor/bridge_contructor.py @ 348:8ff113e2abcb
updated README and CHANGELOG
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 30 May 2011 16:16:46 +0200 |
parents | 4402ac630712 |
children | eb9d33ba4e36 |
line wrap: on
line source
#!/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" DEST_DIR="generated" 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','mediawiki'] DEFAULT_PROTOCOLE='dbus' FLAGS=['deprecated', 'async'] import sys import os from os import path from optparse import OptionParser from ConfigParser import SafeConfigParser as Parser from ConfigParser import NoOptionError import re from datetime import datetime 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 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 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 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")""" 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.append(("unicode(%(name)s)%(default)s" if (unicode_protect and signature[i]=='s') else "%(name)s%(default)s") % { 'name':name[idx][0] if (name and name.has_key(idx)) else "arg_%i" % idx, 'default':"="+default[idx] if (default and default.has_key(idx)) 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 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] 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 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(DEST_DIR) and not os.path.isdir(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(DEST_DIR): os.mkdir(DEST_DIR) full_path=os.path.join(DEST_DIR,filename) if os.path.exists(full_path) and not self.options.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 _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 arg_doc.has_key(i): name,doc=arg_doc[i] doc='\n:'.join(doc.rstrip('\n').split('\n')) wiki.append("; %s: %s" % (name, doc)) else: wiki.append("; arg_%d: " % i) if arg_default.has_key(i): 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 arg_doc.has_key('return'): wiki.append('\n|-\n! scope=row | return value\n|') wiki.append('<br />\n'.join(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"])) default = self.getDefault(section) async_msg = """<br />'''This method is asynchrone'''""" 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':'REQ' if function['category'] == 'request' else 'COMM', 'name':section, 'args':self.getArguments(function['sig_in'], name=arg_doc, default=default ) } 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*' ') completion['args_result'] = self.getArguments(function['sig_in'], name=arg_doc, unicode_protect=self.options.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, 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.cb["%(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 = [] 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 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':'req' if function['category'] == 'request' else 'comm', 'name':section, 'args':self.getArguments(function['sig_in'], name=arg_doc, default=default) } if function["type"] == "method": completion['debug'] = "" if not self.options.debug else 'debug ("%s")\n%s' % (section,8*' ') completion['args_result'] = self.getArguments(function['sig_in'], name=arg_doc) completion['async_args'] = ', callback=None, errback=None' if async else '' completion['async_comma'] = ', ' if async and function['sig_in'] else '' completion['async_args_result'] = 'reply_handler=callback, error_handler=errback' if async else '' 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.options.unicode and function['sig_out'] == 's' else "%s") % result methods_part.append("""\ def %(name)s(self, %(args)s%(async_args)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 = [] 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: 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 ConstructorError(Exception): pass class ConstructorFactory: def create(self, bridge_template, options): if options.protocole=='dbus': return DbusConstructor(bridge_template, options) elif options.protocole=='mediawiki': return MediawikiConstructor(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")) parser.add_option("--no_unicode", action="store_false", dest="unicode", default=True, help=("Remove unicode type protection from string results")) (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() elif self.options.side == "frontend": constructor.generateFrontendSide() if __name__ == "__main__": bc = BridgeConstructor() bc.go()