Utilities/ClassBrowsers/protoclbr.py

changeset 5977
8a0ec75b0f73
child 6048
82ad8ec9548c
equal deleted inserted replaced
5976:549918576245 5977:8a0ec75b0f73
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Parse a ProtoBuf protocol file and retrieve messages, enums, services and
8 rpc methods.
9
10 It is based on the Python class browser found in this package.
11 """
12
13 from __future__ import unicode_literals
14
15 import re
16
17 import Utilities
18 import Utilities.ClassBrowsers as ClassBrowsers
19 from . import ClbrBaseClasses
20
21 SUPPORTED_TYPES = [ClassBrowsers.PROTO_SOURCE]
22
23 _getnext = re.compile(
24 r"""
25 (?P<String>
26 " [^"\\\n]* (?: \\. [^"\\\n]*)* "
27 )
28
29 | (?P<Comment>
30 ^ [ \t]* // .*? $
31 |
32 ^ [ \t]* /\* .*? \*/
33 )
34
35 | (?P<Message>
36 ^
37 (?P<MessageIndent> [ \t]* )
38 message [ \t]+
39 (?P<MessageName> [a-zA-Z_] [a-zA-Z0-9_]* )
40 [ \t]* {
41 )
42
43 | (?P<Enum>
44 ^
45 (?P<EnumIndent> [ \t]* )
46 enum [ \t]+
47 (?P<EnumName> [a-zA-Z_] [a-zA-Z0-9_]* )
48 [ \t]* {
49 )
50
51 | (?P<Service>
52 ^
53 (?P<ServiceIndent> [ \t]* )
54 service [ \t]+
55 (?P<ServiceName> [a-zA-Z_] [a-zA-Z0-9_]* )
56 [ \t]* {
57 )
58
59 | (?P<Method>
60 ^
61 (?P<MethodIndent> [ \t]* )
62 rpc [ \t]+
63 (?P<MethodName> [a-zA-Z_] [a-zA-Z0-9_]* )
64 [ \t]*
65 \(
66 (?P<MethodSignature> [^)]+? )
67 \)
68 [ \t]+
69 returns
70 [ \t]*
71 \(
72 (?P<MethodReturn> [^)]+? )
73 \)
74 [ \t]*
75 )
76
77 | (?P<Begin>
78 [ \t]* {
79 )
80
81 | (?P<End>
82 [ \t]* } [ \t]* ;?
83 )""",
84 re.VERBOSE | re.DOTALL | re.MULTILINE).search
85
86 # function to replace comments
87 _commentsub = re.compile(r"""//[^\n]*\n|//[^\n]*$""").sub
88 # function to normalize whitespace
89 _normalize = re.compile(r"""[ \t]{2,}""").sub
90
91 _modules = {} # cache of modules we've seen
92
93
94 class VisibilityMixin(ClbrBaseClasses.ClbrVisibilityMixinBase):
95 """
96 Mixin class implementing the notion of visibility.
97 """
98 def __init__(self):
99 """
100 Constructor
101 """
102 self.setPublic()
103
104
105 class Message(ClbrBaseClasses.Module, VisibilityMixin):
106 """
107 Class to represent a ProtoBuf Message.
108 """
109 def __init__(self, module, name, file, lineno):
110 """
111 Constructor
112
113 @param module name of the module containing this message
114 @type str
115 @param name name of this message
116 @type str
117 @param file filename containing this message
118 @type str
119 @param lineno linenumber of the message definition
120 @type int
121 """
122 ClbrBaseClasses.Module.__init__(self, module, name, file, lineno)
123 VisibilityMixin.__init__(self)
124
125
126 class Enum(ClbrBaseClasses.Enum, VisibilityMixin):
127 """
128 Class to represent a ProtoBuf Enum.
129 """
130 def __init__(self, module, name, file, lineno):
131 """
132 Constructor
133
134 @param module name of the module containing this enum
135 @type str
136 @param name name of this enum
137 @type str
138 @param file filename containing this enum
139 @type str
140 @param lineno linenumber of the message enum
141 @type int
142 """
143 ClbrBaseClasses.Enum.__init__(self, module, name, file, lineno)
144 VisibilityMixin.__init__(self)
145
146
147 class Service(ClbrBaseClasses.Class, VisibilityMixin):
148 """
149 Class to represent a ProtoBuf Service.
150 """
151 def __init__(self, module, name, file, lineno):
152 """
153 Constructor
154
155 @param module name of the module containing this service
156 @type str
157 @param name name of this service
158 @type str
159 @param file filename containing this service
160 @type str
161 @param lineno linenumber of the service definition
162 @type int
163 """
164 ClbrBaseClasses.Class.__init__(self, module, name, None, file,
165 lineno)
166 VisibilityMixin.__init__(self)
167
168
169 class ServiceMethod(ClbrBaseClasses.Function, VisibilityMixin):
170 """
171 Class to represent a ProtoBuf Service Method.
172 """
173 def __init__(self, name, file, lineno, signature, returns):
174 """
175 Constructor
176
177 @param name name of this service method
178 @type str
179 @param file filename containing this service method
180 @type str
181 @param lineno linenumber of the service method definition
182 @type int
183 @param signature parameter list of the service method
184 @type str
185 @param returns return type of the service method
186 @type str
187 """
188 ClbrBaseClasses.Function.__init__(self, None, name, file, lineno,
189 signature,
190 annotation="-> {0}".format(returns))
191 VisibilityMixin.__init__(self)
192
193
194 def readmodule_ex(module, path=None):
195 """
196 Read a ProtoBuf protocol file and return a dictionary of messages, enums,
197 services and rpc methods.
198
199 @param module name of the ProtoBuf protocol file
200 @type str
201 @param path path the file should be searched in
202 @type list of str
203 @return the resulting dictionary
204 @rtype dict
205 """
206 global _modules
207
208 dictionary = {}
209
210 if module in _modules:
211 # we've seen this file before...
212 return _modules[module]
213
214 # search the path for the file
215 f = None
216 fullpath = [] if path is None else path[:]
217 f, file, (suff, mode, type) = ClassBrowsers.find_module(module, fullpath)
218 if f:
219 f.close()
220 if type not in SUPPORTED_TYPES:
221 # not ProtoBuf protocol source, can't do anything with this module
222 _modules[module] = dictionary
223 return dictionary
224
225 _modules[module] = dictionary
226 classstack = [] # stack of (class, indent) pairs
227 indent = 0
228 try:
229 src = Utilities.readEncodedFile(file)[0]
230 except (UnicodeError, IOError):
231 # can't do anything with this module
232 _modules[module] = dictionary
233 return dictionary
234
235 lineno, last_lineno_pos = 1, 0
236 lastGlobalEntry = None
237 cur_obj = None
238 i = 0
239 while True:
240 m = _getnext(src, i)
241 if not m:
242 break
243 start, i = m.span()
244
245 if m.start("Method") >= 0:
246 # found a method definition or function
247 thisindent = indent
248 meth_name = m.group("MethodName")
249 meth_sig = m.group("MethodSignature")
250 meth_sig = meth_sig and meth_sig.replace('\\\n', '') or ''
251 meth_sig = _commentsub('', meth_sig)
252 meth_sig = _normalize(' ', meth_sig)
253 meth_return = m.group("MethodReturn")
254 meth_return = meth_return and meth_return.replace('\\\n', '') or ''
255 meth_return = _commentsub('', meth_return)
256 meth_return = _normalize(' ', meth_return)
257 lineno = lineno + src.count('\n', last_lineno_pos, start)
258 last_lineno_pos = start
259 # close all interfaces/modules indented at least as much
260 while classstack and \
261 classstack[-1][1] >= thisindent:
262 if classstack[-1][0] is not None:
263 # record the end line
264 classstack[-1][0].setEndLine(lineno - 1)
265 del classstack[-1]
266 if classstack:
267 # it's an interface/module method
268 cur_class = classstack[-1][0]
269 if isinstance(cur_class, Service):
270 # it's a method
271 f = ServiceMethod(meth_name, file, lineno, meth_sig,
272 meth_return)
273 cur_class._addmethod(meth_name, f)
274 # else it's a nested def
275 else:
276 f = None
277 else:
278 # the file is incorrect, ignore the entry
279 continue
280 if not classstack:
281 if lastGlobalEntry:
282 lastGlobalEntry.setEndLine(lineno - 1)
283 lastGlobalEntry = f
284 if cur_obj and isinstance(cur_obj, ServiceMethod):
285 cur_obj.setEndLine(lineno - 1)
286 cur_obj = f
287 classstack.append((f, thisindent)) # Marker for nested fns
288
289 elif m.start("String") >= 0:
290 pass
291
292 elif m.start("Comment") >= 0:
293 pass
294
295 elif m.start("Message") >= 0:
296 # we found a message definition
297 thisindent = indent
298 indent += 1
299 # close all messages/services indented at least as much
300 while classstack and \
301 classstack[-1][1] >= thisindent:
302 if classstack[-1][0] is not None:
303 # record the end line
304 classstack[-1][0].setEndLine(lineno - 1)
305 del classstack[-1]
306 lineno = lineno + src.count('\n', last_lineno_pos, start)
307 last_lineno_pos = start
308 message_name = m.group("MessageName")
309 # remember this message
310 cur_class = Message(module, message_name, file, lineno)
311 if not classstack:
312 dictionary[message_name] = cur_class
313 else:
314 msg = classstack[-1][0]
315 msg._addclass(message_name, cur_class)
316 if not classstack:
317 if lastGlobalEntry:
318 lastGlobalEntry.setEndLine(lineno - 1)
319 lastGlobalEntry = cur_class
320 cur_obj = cur_class
321 classstack.append((cur_class, thisindent))
322
323 elif m.start("Enum") >= 0:
324 # we found a message definition
325 thisindent = indent
326 indent += 1
327 # close all messages/services indented at least as much
328 while classstack and \
329 classstack[-1][1] >= thisindent:
330 if classstack[-1][0] is not None:
331 # record the end line
332 classstack[-1][0].setEndLine(lineno - 1)
333 del classstack[-1]
334 lineno = lineno + src.count('\n', last_lineno_pos, start)
335 last_lineno_pos = start
336 enum_name = m.group("EnumName")
337 # remember this Enum
338 cur_class = Enum(module, enum_name, file, lineno)
339 if not classstack:
340 dictionary[enum_name] = cur_class
341 else:
342 enum = classstack[-1][0]
343 enum._addclass(enum_name, cur_class)
344 if not classstack:
345 if lastGlobalEntry:
346 lastGlobalEntry.setEndLine(lineno - 1)
347 lastGlobalEntry = cur_class
348 cur_obj = cur_class
349 classstack.append((cur_class, thisindent))
350
351 elif m.start("Service") >= 0:
352 # we found a message definition
353 thisindent = indent
354 indent += 1
355 # close all messages/services indented at least as much
356 while classstack and \
357 classstack[-1][1] >= thisindent:
358 if classstack[-1][0] is not None:
359 # record the end line
360 classstack[-1][0].setEndLine(lineno - 1)
361 del classstack[-1]
362 lineno = lineno + src.count('\n', last_lineno_pos, start)
363 last_lineno_pos = start
364 service_name = m.group("ServiceName")
365 # remember this Service
366 cur_class = Service(module, service_name, file, lineno)
367 if not classstack:
368 dictionary[service_name] = cur_class
369 else:
370 service = classstack[-1][0]
371 service._addclass(service_name, cur_class)
372 if not classstack:
373 if lastGlobalEntry:
374 lastGlobalEntry.setEndLine(lineno - 1)
375 lastGlobalEntry = cur_class
376 cur_obj = cur_class
377 classstack.append((cur_class, thisindent))
378
379 elif m.start("Begin") >= 0:
380 # a begin of a block we are not interested in
381 indent += 1
382
383 elif m.start("End") >= 0:
384 # an end of a block
385 indent -= 1
386
387 else:
388 assert 0, "regexp _getnext found something unexpected"
389
390 return dictionary

eric ide

mercurial