-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathproto2cpp.py
More file actions
executable file
·253 lines (236 loc) · 9.81 KB
/
proto2cpp.py
File metadata and controls
executable file
·253 lines (236 loc) · 9.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#!/usr/bin/python2
##
# Doxygen filter for Google Protocol Buffers .proto files.
# This script converts .proto files into C++ style ones
# and prints the output to standard output.
#
# version 0.6-beta
#
# How to enable this filter in Doxygen:
# 1. Generate Doxygen configuration file with command 'doxygen -g <filename>'
# e.g. doxygen -g doxyfile
# 2. In the Doxygen configuration file, find JAVADOC_AUTOBRIEF and set it enabled
# JAVADOC_AUTOBRIEF = YES
# 3. In the Doxygen configuration file, find FILE_PATTERNS and add *.proto
# FILE_PATTERNS = *.proto
# 4. In the Doxygen configuration file, find EXTENSION_MAPPING and add proto=C
# EXTENSION_MAPPING = proto=C
# 5. In the Doxygen configuration file, find INPUT_FILTER and add this script
# INPUT_FILTER = "python proto2cpp.py"
# 6. Run Doxygen with the modified configuration
# doxygen doxyfile
#
#
# Copyright (C) 2012-2015 Timo Marjoniemi
# All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
##
import os
import sys
import re
import fnmatch
import inspect
## Class for converting Google Protocol Buffers .proto files into C++ style output to enable Doxygen usage.
##
## The C++ style output is printed into standard output.<br />
## There are three different logging levels for the class:
## <ul><li>#logNone: do not log anything</li>
## <li>#logErrors: log errors only</li>
## <li>#logAll: log everything</li></ul>
## Logging level is determined by \c #logLevel.<br />
## Error logs are written to file determined by \c #errorLogFile.<br />
## Debug logs are written to file determined by \c #logFile.
#
class proto2cpp:
## Logging level: do not log anything.
logNone = 0
## Logging level: log errors only.
logErrors = 1
## Logging level: log everything.
logAll = 2
## Constructor
#
def __init__(self):
## Debug log file name.
self.logFile = "proto2cpp.log"
## Error log file name.
self.errorLogFile = "proto2cpp.error.log"
## Logging level.
self.logLevel = self.logNone
## Handles a file.
##
## If @p fileName has .proto suffix, it is processed through parseFile().
## Otherwise it is printed to stdout as is except for file \c proto2cpp.py without
## path since it's the script given to python for processing.
##
## @param fileName Name of the file to be handled.
#
def handleFile(self, fileName):
if fnmatch.fnmatch(filename, '*.proto'):
self.log('\nXXXXXXXXXX\nXX ' + filename + '\nXXXXXXXXXX\n\n')
# Open the file. Use try to detect whether or not we have an actual file.
try:
with open(filename, 'r') as inputFile:
self.parseFile(inputFile)
pass
except IOError as e:
self.logError('the file ' + filename + ' could not be opened for reading')
elif not fnmatch.fnmatch(filename, os.path.basename(inspect.getfile(inspect.currentframe()))):
self.log('\nXXXXXXXXXX\nXX ' + filename + '\nXXXXXXXXXX\n\n')
try:
with open(filename, 'r') as theFile:
output = ''
for theLine in theFile:
output += theLine
print(output)
self.log(output)
pass
except IOError as e:
self.logError('the file ' + filename + ' could not be opened for reading')
else:
self.log('\nXXXXXXXXXX\nXX ' + filename + ' --skipped--\nXXXXXXXXXX\n\n')
## Parser function.
##
## The function takes a .proto file object as input
## parameter and modifies the contents into C++ style.
## The modified data is printed into standard output.
##
## @param inputFile Input file object
#
def parseFile(self, inputFile):
# Go through the input file line by line.
isEnum = False
isClass = False
inNamespace = 0
isMessage = False
# This variable is here as a workaround for not getting extra line breaks (each line
# ends with a line separator and print() method will add another one).
# We will be adding lines into this var and then print the var out at the end.
theOutput = ''
for line in inputFile:
# Search for comment ("//")
matchComment = re.search("//.+", line)
# Search for semicolon and if one is found before comment, add a third slash character
# ("/") and a smaller than ("<") chracter to the comment to make Doxygen detect it.
matchSemicolon = re.search(";", line)
# Search for "enum" and if one is found before comment,
# start changing all semicolons (";") to commas (",").
matchEnum = re.search("enum", line)
if matchEnum is not None and (matchComment is None or matchEnum.start() < matchComment.start()):
isEnum = True
# Search again for semicolon if we have detected an enum, and replace semicolon with comma.
if isEnum is True and matchSemicolon is not None:
line = line[:matchSemicolon.start()] + "," + line[matchSemicolon.end():]
else:
matchImport = re.search("^import(?: public)? (\".+\");", line)
if matchImport is not None:
line = "#include " + matchImport.group(1) + "\n"
else:
matchPackage = re.search("^package (.+);", line)
if matchPackage is not None:
line = ""
for ns in matchPackage.group(1).split("."):
inNamespace += 1
line += "namespace " + ns + " { "
line += "\n"
matchRpc = re.search("rpc( .+)\((.+)\) returns \((.+)\);", line)
if matchRpc is not None:
request = matchRpc.group(2).replace("stream ", "").replace(".", "::")
response = matchRpc.group(3).replace("stream ", "").replace(".", "::")
line = "virtual grpc::Status " + matchRpc.group(1) + "("
if request != "google::protobuf::Empty":
if "stream " in matchRpc.group(2):
line += request + " reader"
else:
line += request + " request"
if response != "google::protobuf::Empty":
if "stream " in matchRpc.group(3):
line += ", " + response + " writer"
else:
line += ", " + response + "* response"
line += ") = 0;\n"
cleanline = line
if matchComment is not None:
cleanline = line[:matchComment.start()]
# Search for a closing brace.
matchClosingBrace = re.search("}", cleanline)
if isEnum is True and matchClosingBrace is not None:
line = line[:matchClosingBrace.start()] + "};" + line[matchClosingBrace.end():]
isEnum = False
elif isEnum is False and re.search("}", cleanline) is not None:
# Message (to be struct) ends => add semicolon so that it'll
# be a proper C(++) struct and Doxygen will handle it correctly.
line = line[:matchClosingBrace.start()] + "};" + line[matchClosingBrace.end():]
isClass = False
isMessage = False
elif isClass and re.search("{", cleanline) is not None:
line += " public: "
elif isClass is False and isEnum is False and isMessage is True:
matchRepeated = re.search("repeated (.+ )(.+=.+;)", line)
if matchRepeated is not None:
line = "vector<" + matchRepeated.group(1).replace(".", "::") + "> " + matchRepeated.group(2) + "\n"
else:
matchVar = re.search("(.+ )(.+=.+;)", line)
line = line.replace(".", "::")
elif re.search("^syntax = \"proto", cleanline):
line = line.strip() + " ///< file syntax version\n"
# Search for 'message' and replace it with 'struct' unless 'message' is behind a comment.
matchMsg = re.search("message", line)
matchService = re.search("service", line)
if matchMsg is not None and (matchComment is None or matchMsg.start() < matchComment.start()):
output = "struct" + line[:matchMsg.start()] + line[matchMsg.end():]
theOutput += output
isMessage = True
elif matchService is not None and (matchComment is None or matchMsg.start() < matchComment.start()):
isClass = True
output = "class" + line[:matchService.start()] + line[matchService.end():]
theOutput += output.replace("{", "{ public:")
else:
matchOneof = re.search("oneof ", line)
if matchOneof is not None:
theOutput += "union" + line[:matchOneof.start()] + line[matchOneof.end():]
else:
theOutput += line
for x in range(0, inNamespace):
theOutput += "}"
theOutput += "\n"
sys.stdout.write(theOutput)
## Writes @p string to log file.
##
## #logLevel must be #logAll or otherwise the logging is skipped.
##
## @param string String to be written to log file.
#
def log(self, string):
if self.logLevel >= self.logAll:
with open(self.logFile, 'a') as theFile:
theFile.write(string)
## Writes @p string to error log file.
##
## #logLevel must be #logError or #logAll or otherwise the logging is skipped.
##
## @param string String to be written to error log file.
#
def logError(self, string):
if self.logLevel >= self.logError:
with open(self.errorLogFile, 'a') as theFile:
theFile.write(string)
converter = proto2cpp()
# Doxygen will give us the file names
for filename in sys.argv:
converter.handleFile(filename)
# end of file