-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpbparsen.nim
352 lines (309 loc) · 9.35 KB
/
pbparsen.nim
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
import strutils, streams, strformat, tables, sequtils, os
when NimMinor >= 19 or NimMajor >= 1:
import sugar
else:
import future
import sqlgen
import types, utils
export types, utils
proc isComment(s: Stream): (bool, bool) =
try:
let comment = s.peekStr 2
if comment == "/*":
result = (true, true)
discard s.readStr 2
elif comment == "//":
result = (true, false)
discard s.readStr 2
except IOError:
result = (false, false)
proc purgeComment(s: Stream, multiline: bool) =
while not s.atEnd:
let c = s.readChar
if c == '\n' and not multiline:
return
if c == '*' and s.peekChar == '/':
discard s.readChar
return
proc purgeComment(s: Stream) =
while not s.atEnd:
let (iscomment, multiline) = s.isComment
if iscomment: s.purgeComment multiline
return
proc getExpr(s: Stream): Expr =
var buff = ""
while not s.atEnd:
s.purgeComment
let c = s.readChar
if c in Newlines: buff &= ' '
case c
of ';':
buff &= c
return Expr(kind: SingleLine, line: buff.strip)
of '}':
return Expr(kind: End)
of '{':
let
tokens = buff.splitWhitespace
`type` = tokens[0].toConstruct
name = tokens[1]
var data = newseq[Expr]()
var expr = s.getExpr
while not expr.isEnd:
data.add expr
expr = s.getExpr
when not defined(release):
echo fmt"Bracket: {`type`} {name}"
return Expr(kind: Bracket, `type`:`type`, name:name, lines: data)
else:
buff &= c
proc skipWhitespaces(s: Stream) =
while not s.atEnd:
let c = s.peekChar
if c notin Whitespace:
return
discard s.readChar
template readingParse(s: Stream, ch: char, inclusive = false): untyped =
var buff = ""
while not s.atEnd:
case s.peekChar == ch
of true:
when inclusive: buff &= s.readChar
break
else:
buff &= s.readChar
buff
proc readUntil(s: Stream, ch: char): string =
s.readingParse ch
proc readTo(s: Stream, ch: char): string =
s.readingParse(ch, true)
proc readTo(s: Stream, str: string): string =
result = ""
while not s.atEnd:
var buff = s.readUntil str[0]
if s.peekStr(str.len) == str:
result &= buff & s.readStr str.len
break
else:
result &= buff
proc getArity(str: string): ArityService =
let arity = str.splitWhitespace
if "stream" in arity and arity.len > 1:
result = ArityService(`type`: arity[1], stream: true)
else:
result = ArityService(`type`: arity[0])
proc parseRpc(strexpr: string): RpcProto =
let s = newStringStream strexpr
while not s.atEnd:
let rpc = s.readStr 3
if rpc != "rpc": return RpcProto()
s.skipWhitespaces
result.name = s.readUntil('(').strip
let reqinfo = s.readTo(')').strip(chars = Whitespace + {'(', ')'})
result.request = reqinfo.getArity
discard s.readTo('(')
let resinfo = s.readTo(')').strip(chars = Whitespace + {'(', ')'})
result.response = resinfo.getArity
break
proc parseService(expr: Expr): seq[ServiceProto] =
var svc = ServiceProto(
name: expr.name,
rpcs: newTable[string, RpcProto]()
)
for expr in expr.lines:
let rpc = expr.line.parseRpc
svc.rpcs[rpc.name] = rpc
#result[svc.name] = svc
result.add svc
proc parseField(str: string): FieldProto =
let
keyval = str.split('=')
info = keyval[0].strip().splitWhitespace().mapIt it.strip
pos = keyval[1].strip(chars = Whitespace + {';'})
result.pos = parseInt pos
if "repeated" in info:
result.repeated = true
if info.len >= 3:
result.kind = info[1]
result.name = info[2]
elif info.len >= 2:
result.kind = info[0]
result.name = info[1]
proc parseMessage(expr: Expr): seq[MessageProto] =
var msg = MessageProto(
name: expr.name,
fields: newTable[string, FieldProto]()
)
if expr.lines.len == 0:
return @[msg]
for fld in expr.lines:
case fld.kind
of SingleLine:
let field = fld.line.parseField
msg.fields[field.name] = field
result.add msg
of Bracket:
var msgs = fld.parseMessage
for msg in msgs.mitems:
msg.name = [expr.name, msg.name].join(".")
result.add msg
else:
discard
proc proto(exprs: varargs[Expr]): Proto =
var services = newTable[string, ServiceProto]()
var messages = newTable[string, MessageProto]()
for expr in exprs:
case expr.kind
of SingleLine:
if expr.line.startsWith "syntax":
result.syntax = expr.line.split("=")[1]
continue
of Bracket:
case expr.`type`
of Service:
let svcs = expr.parseService
for svc in svcs:
services[svc.name] = svc
of Message:
when not defined(release):
dump expr
let msgs = expr.parseMessage
for msg in msgs:
messages[msg.name] = msg
of Enum:
# to be added later
discard
of Rpc:
# to be added later
discard
of OneOf:
# to be added later
discard
of End:
discard
result.services = services
result.messages = messages
proc proto(pb: var Proto, expr: Expr) =
let prot = expr.proto
if prot.syntax != "":
pb.syntax = prot.syntax
for name, svc in prot.services:
pb.services[name] = svc
for name, msg in prot.messages:
pb.messages[name] = msg
proc `==`*(a, b: MessageProto): bool =
a.name == b.name
proc normalizeFieldType(msg: var MessageProto,
msgs: TableRef[string, MessageProto]) =
for field in msg.fields.values:
if field.kind in primitiveTypes:
continue
for othermsg in msgs.values:
if msg == othermsg: continue
if othermsg.name.endsWith(field.kind) and othermsg.name.find('.') != -1:
let oldtype = field.kind
var fld = msg.fields[field.name]
fld.kind = othermsg.name
msg.fields[field.name] = fld
proc initPb*(): Proto =
Proto(
services: newTable[string, ServiceProto](),
messages: newTable[string, MessageProto]()
)
proc normalizeFieldType*(pb: var Proto) =
for _, msg in pb.messages.mpairs:
msg.normalizeFieldType pb.messages
proc parsePb*(fname: string): Proto =
result = initPb()
var fs = newFileStream fname
while not fs.atEnd:
let expr = fs.getExpr
if not expr.isNil:
result.proto expr
result.normalizeFieldType
when isMainModule:
import goout/[gousecase, goviewmodel, goservice, gomodel, govars,
goendpoints, gotransport, gorepository, goserverdriver,
goconfig, gousecase_impl]
when defined(release):
include writingtmpl
proc compileProtoc(fname: string, info: GrpcServiceInfo): bool =
if execShellCmd(&"protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative {fname}") != 0:
echo "Error happened when invoking protoc, ensure protoc installed correctly"
return false
let (_, name, _) = splitFile fname
let pbgo = &"{name}.pb.go"
info.writingPrologue(true, &"pb/{info.name}", pbpath)
let newpath = fullpath / pbgo
# workaround because moveFile cannot silently be overwritten on windows
when defined(windows):
if newpath.existsFile:
removeFile newpath
try:
moveFile("." / pbgo, fullpath / pbgo)
except OSError:
echo getCurrentExceptionMsg()
return false
true
proc main =
when not defined(release):
if paramCount() < 1:
quit "Please provide protobuf file"
var pb = paramStr(1).parsePb
dump pb
if pb.services.len < 1:
quit "Invalid protobuf file provided"
let gopath = "GOPATH".getenv((getHomeDir() / "go").unixSep)
dump gopath
let info = GrpcServiceInfo(
name: "payment",
basepath: "paxelit/payment",
gopath: gopath)
echo("===========")
stdout.writeUseCase((gopath / info.basepath / info.name / "viewmodel")
.unixSep, pb)
for msg in pb.messages.values:
echo msg.filename
stdout.writeViewModel msg
echo("===========")
stdout.write writeGoService(info, pb)
echo("=============")
stdout.write writeGoModel(info, pb)
echo("=============")
stdout.write writeGoEndpoints(info, pb)
echo("=============")
stdout.write writeGoTransport(info, pb)
echo("=============")
var tbls = newseq[SqlTable]()
if paramStr(1).endsWith "reg.proto":
let sqlreg = "examples/register-grpc/reg.sql"
tbls = sqlreg.parseSql.parse.getTables
stdout.writeUsecaseImpl(info, pb, tbls)
echo("=============")
stdout.write writeGoServerDriver(info, pb, tbls)
echo gopath
else:
let (info, sqlfile, pbfile) = getConfigCmd()
echo info
writeVarsWith info
writeConfigWith info
writeJsonConfigWith info
var tbls = newseq[SqlTable]()
if sqlfile != "":
tbls = sqlfile.writeGoEntity info
info.writeGoRepository(tbls, version = "0.1.0")
var pb: Proto
if pbfile != "":
pb = pbfile.parsePb
if pb.messages.len <= 0 or pb.services.len < 1:
quit "Whether invalid protobuf or no messages or services available"
if not compileProtoc(pbfile, info):
quit QuitFailure
pb.writeUsecaseWith(info, tbls)
pb.writeViewmodelWith info
pb.writeServiceWith info
pb.writeModelWith info
pb.writeEndpointsWith info
pb.writeTransportWith info
info.writeServerDriver(pb, tbls)
main()