comparison src/bridge/bridge_constructor/bridge_contructor.py @ 267:bdcd535e179e

Bridge constructor: - moved constructor files in src/bridge/bridge_constructor - frontend side can now be generated
author Goffi <goffi@goffi.org>
date Mon, 24 Jan 2011 21:19:11 +0100
parents src/bridge/bridge_contructor.py@c4b84a2d2ad1
children 0288f97334f2
comparison
equal deleted inserted replaced
266:c4b84a2d2ad1 267:bdcd535e179e
1 #!/usr/bin/python
2 #-*- coding: utf-8 -*-
3
4 """
5 SAT: a jabber client
6 Copyright (C) 2009, 2010, 2011 Jérôme Poisson (goffi@goffi.org)
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """
21
22
23 #consts
24 NAME = u"bridge_constructor"
25 VERSION="0.1.0"
26 ABOUT = NAME+u""" v%s (c) Jérôme Poisson (aka Goffi) 2011
27
28 ---
29 """+NAME+u""" Copyright (C) 2011 Jérôme Poisson (aka Goffi)
30 This program comes with ABSOLUTELY NO WARRANTY;
31 This is free software, and you are welcome to redistribute it
32 under certain conditions.
33 ---
34
35 This script construct a SàT bridge using the given protocol
36 """
37 MANAGED_PROTOCOLES=['dbus']
38 DEFAULT_PROTOCOLE='dbus'
39
40 import sys
41 import os
42 from os import path
43 from optparse import OptionParser
44 from ConfigParser import SafeConfigParser as Parser
45 from ConfigParser import NoOptionError
46 import re
47
48
49 class ParseError(Exception):
50 #Used when the signature parsing is going wrong (invalid signature ?)
51 pass
52
53 class Constructor:
54
55 def __init__(self, bridge_template, options):
56 self.bridge_template = bridge_template
57 self.options = options
58
59 def getValues(self, name):
60 """Return values of a function in a dict
61 @param name: Name of the function to get
62 @return: dict, each key has the config value or None if the value is not set"""
63 function={}
64 for option in ['type','category','sig_in','sig_out','doc']:
65 try:
66 value = self.bridge_template.get(name, option)
67 except NoOptionError:
68 value = None
69 function[option] = value
70 return function
71
72 def getDefault(self, name):
73 """Return default values of a function in a dict
74 @param name: Name of the function to get
75 @return: dict, each key is the integer param number (no key if no default value)"""
76 default_dict={}
77 def_re = re.compile(r"param_(\d+)_default")
78
79 for option in self.bridge_template.options(name):
80 if option == 'doc_return':
81 default_dict['return'] = self.bridge_template.get(name, option)
82 continue
83 match = def_re.match(option)
84 if match:
85 try:
86 idx = int(match.group(1))
87 except ValueError:
88 raise ParseError("Invalid value [%s] for parameter number" % match.group(1))
89 default_dict[idx] = self.bridge_template.get(name, option)
90
91 return default_dict
92
93 def getArgumentsDoc(self, name):
94 """Return documentation of arguments
95 @param name: Name of the function to get
96 @return: dict, each key is the integer param number (no key if no argument doc), value is a tuple (name, doc)"""
97 doc_dict={}
98 option_re = re.compile(r"doc_param_(\d+)")
99 value_re = re.compile(r"^(\w+): (.*)$", re.MULTILINE | re.DOTALL)
100 for option in self.bridge_template.options(name):
101 match = option_re.match(option)
102 if match:
103 try:
104 idx = int(match.group(1))
105 except ValueError:
106 raise ParseError("Invalid value [%s] for parameter number" % match.group(1))
107 value_match = value_re.match(self.bridge_template.get(name, option))
108 if not value_match:
109 raise ParseError("Invalid value for parameter doc [%i]" % idx)
110 doc_dict[idx]=(value_match.group(1),value_match.group(2))
111 return doc_dict
112
113 def getArguments(self, signature, name=None, default=None, unicode_protect=False):
114 """Return arguments to user given a signature
115 @param signature: signature in the short form (using s,a,i,b etc)
116 @param name: dictionary of arguments name like given by getArguments
117 @param default: dictionary of default values, like given by getDefault
118 @param unicode_protect: activate unicode protection on strings (return strings as unicode(str))
119 @return: list of arguments that correspond to a signature (e.g.: "sss" return "arg1, arg2, arg3")"""
120 i=0
121 idx=0
122 attr_string=[]
123 while i<len(signature):
124 if signature[i] not in ['b','y','n','i','x','q','u','t','d','s','a']:
125 raise ParseError("Unmanaged attribute type [%c]" % signature[i])
126
127 attr_string.append(("unicode(%(name)s)%(default)s" if (unicode_protect and signature[i]=='s') else "%(name)s%(default)s") % {
128 'name':name[idx][0] if (name and name.has_key(idx)) else "arg_%i" % idx,
129 'default':"="+default[idx] if (default and default.has_key(idx)) else ''
130 }) #give arg_1, arg2, etc or name1, name2=default, etc. \
131 #give unicode(arg_1), unicode(arg_2), etc. if unicode_protect is set and arg is a string
132 idx+=1
133
134 if signature[i] == 'a':
135 i+=1
136 if signature[i]!='{' and signature[i]!='(': #FIXME: must manage tuples out of arrays
137 i+=1
138 continue #we have a simple type for the array
139 opening_car = signature[i]
140 assert(opening_car in ['{','('])
141 closing_car = '}' if opening_car == '{' else ')'
142 opening_count = 1
143 while (True): #we have a dict or a list of tuples
144 i+=1
145 if i>=len(signature):
146 raise ParseError("missing }")
147 if signature[i] == opening_car:
148 opening_count+=1
149 if signature[i] == closing_car:
150 opening_count-=1
151 if opening_count == 0:
152 break
153 i+=1
154 return ", ".join(attr_string)
155
156 def generateCoreSide(self):
157 """create the constructor in SàT core side (backend)"""
158 raise NotImplementedError
159
160 class DbusConstructor(Constructor):
161
162 def __init__(self, bridge_template, options):
163 Constructor.__init__(self, bridge_template, options)
164 self.core_template="dbus_core_template.py"
165 self.frontend_template="dbus_frontend_template.py"
166 self.frontend_dest = self.core_dest="DBus.py"
167
168 def generateCoreSide(self):
169 signals_part = []
170 methods_part = []
171 direct_calls = []
172 sections = self.bridge_template.sections()
173 sections.sort()
174 for section in sections:
175 function = self.getValues(section)
176 print ("Adding %s %s" % (section, function["type"]))
177 default = self.getDefault(section)
178 arg_doc = self.getArgumentsDoc(section)
179 completion = {
180 'sig_in':function['sig_in'] or '',
181 'sig_out':function['sig_out'] or '',
182 'category':'REQ' if function['category'] == 'request' else 'COMM',
183 'name':section,
184 'args':self.getArguments(function['sig_in'], name=arg_doc, default=default )
185 }
186
187 if function["type"] == "signal":
188 completion['body'] = "pass" if not self.options.debug else 'debug ("%s")' % section
189 signals_part.append("""\
190 @dbus.service.signal(const_INT_PREFIX+const_%(category)s_SUFFIX,
191 signature='%(sig_in)s')
192 def %(name)s(self, %(args)s):
193 %(body)s
194 """ % completion)
195 direct_calls.append("""\
196 def %(name)s(self, %(args)s):
197 self.dbus_bridge.%(name)s(%(args)s)
198 """ % completion)
199
200 elif function["type"] == "method":
201 completion['debug'] = "" if not self.options.debug else 'debug ("%s")\n%s' % (section,8*' ')
202 completion['args_result'] = self.getArguments(function['sig_in'], name=arg_doc, unicode_protect=self.options.unicode)
203 methods_part.append("""\
204 @dbus.service.method(const_INT_PREFIX+const_%(category)s_SUFFIX,
205 in_signature='%(sig_in)s', out_signature='%(sig_out)s')
206 def %(name)s(self, %(args)s):
207 %(debug)sreturn self.cb["%(name)s"](%(args_result)s)
208 """ % completion)
209
210 #at this point, signals_part, methods_part and direct_calls should be filled,
211 #we just have to place them in the right part of the template
212 core_bridge = []
213 try:
214 with open(self.core_template) as core_template:
215 for line in core_template:
216 if line.startswith('##SIGNALS_PART##'):
217 core_bridge.extend(signals_part)
218 elif line.startswith('##METHODS_PART##'):
219 core_bridge.extend(methods_part)
220 elif line.startswith('##DIRECT_CALLS##'):
221 core_bridge.extend(direct_calls)
222 else:
223 core_bridge.append(line.replace('\n',''))
224 except IOError:
225 print ("Can't open template file [%s]" % self.core_template)
226 sys.exit(1)
227
228 #now we write to final file
229 if os.path.exists(self.core_dest) and not self.options.force:
230 print ("The destination file [%s] already exists ! Use --force to overwrite it" % self.core_dest)
231 try:
232 with open(self.core_dest,'w') as dest_file:
233 dest_file.write('\n'.join(core_bridge))
234 except IOError:
235 print ("Can't open destination file [%s]" % self.core_dest)
236
237 def generateFrontendSide(self):
238 methods_part = []
239 sections = self.bridge_template.sections()
240 sections.sort()
241 for section in sections:
242 function = self.getValues(section)
243 print ("Adding %s %s" % (section, function["type"]))
244 default = self.getDefault(section)
245 arg_doc = self.getArgumentsDoc(section)
246 completion = {
247 'sig_in':function['sig_in'] or '',
248 'sig_out':function['sig_out'] or '',
249 'category':'req' if function['category'] == 'request' else 'comm',
250 'name':section,
251 'args':self.getArguments(function['sig_in'], name=arg_doc, default=default )
252 }
253
254 if function["type"] == "method":
255 completion['debug'] = "" if not self.options.debug else 'debug ("%s")\n%s' % (section,8*' ')
256 completion['args_result'] = self.getArguments(function['sig_in'], name=arg_doc)
257 methods_part.append("""\
258 def %(name)s(self, %(args)s):
259 %(debug)sreturn self.db_%(category)s_iface.%(name)s(%(args_result)s)
260 """ % completion)
261
262 #at this point, methods_part should be filled,
263 #we just have to place it in the right part of the template
264 frontend_bridge = []
265 try:
266 with open(self.frontend_template) as frontend_template:
267 for line in frontend_template:
268 if line.startswith('##METHODS_PART##'):
269 frontend_bridge.extend(methods_part)
270 else:
271 frontend_bridge.append(line.replace('\n',''))
272 except IOError:
273 print ("Can't open template file [%s]" % self.frontend_template)
274 sys.exit(1)
275
276 #now we write to final file
277 if os.path.exists(self.frontend_dest) and not self.options.force:
278 print ("The destination file [%s] already exists ! Use --force to overwrite it" % self.frontend_dest)
279 try:
280 with open(self.frontend_dest,'w') as dest_file:
281 dest_file.write('\n'.join(frontend_bridge))
282 except IOError:
283 print ("Can't open destination file [%s]" % self.frontend_dest)
284
285
286 class ConstructorError(Exception):
287 pass
288
289 class ConstructorFactory:
290 def create(self, bridge_template, options):
291 if options.protocole=='dbus':
292 return DbusConstructor(bridge_template, options)
293
294 raise ConstructorError('Unknown constructor type')
295
296 class BridgeConstructor:
297 def __init__(self):
298 self.options = None
299
300 def check_options(self):
301 """Check command line options"""
302 _usage="""
303 %prog [options]
304
305 %prog --help for options list
306 """
307 parser = OptionParser(usage=_usage,version=ABOUT % VERSION)
308
309 parser.add_option("-p", "--protocole", action="store", type="string", default=DEFAULT_PROTOCOLE,
310 help="Generate bridge using PROTOCOLE (default: %s, possible values: [%s])" % (DEFAULT_PROTOCOLE, ", ".join(MANAGED_PROTOCOLES)))
311 parser.add_option("-s", "--side", action="store", type="string", default="core",
312 help="Which side of the bridge do you want to make ? (default: %default, possible values: [core, frontend])")
313 parser.add_option("-t", "--template", action="store", type="string", default='bridge_template.ini',
314 help="Use TEMPLATE to generate bridge (default: %default)")
315 parser.add_option("-f", "--force", action="store_true", default=False,
316 help=("Force overwritting of existing files"))
317 parser.add_option("-d", "--debug", action="store_true", default=False,
318 help=("Add debug information printing"))
319 parser.add_option("--no_unicode", action="store_false", dest="unicode", default=True,
320 help=("Remove unicode type protection from string results"))
321
322
323 (self.options, args) = parser.parse_args()
324 return args
325
326 def go(self):
327 self.check_options()
328 self.template = Parser()
329 try:
330 self.template.readfp(open(self.options.template))
331 except IOError:
332 print ("The template file doesn't exist or is not accessible")
333 exit(1)
334 constructor = ConstructorFactory().create(self.template, self.options)
335 if self.options.side == "core":
336 constructor.generateCoreSide()
337 elif self.options.side == "frontend":
338 constructor.generateFrontendSide()
339
340 if __name__ == "__main__":
341 bc = BridgeConstructor()
342 bc.go()