Mercurial > libervia-backend
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() |