ExtensionProtobuf/protoclbr.py

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

eric ide

mercurial