struct-frame 0.0.29__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- struct_frame/__init__.py +11 -0
- struct_frame/__main__.py +8 -0
- struct_frame/base.py +85 -0
- struct_frame/c_gen.py +252 -0
- struct_frame/generate.py +607 -0
- struct_frame/gql_gen.py +207 -0
- struct_frame/py_gen.py +213 -0
- struct_frame/ts_gen.py +260 -0
- struct_frame-0.0.29.dist-info/METADATA +311 -0
- struct_frame-0.0.29.dist-info/RECORD +12 -0
- struct_frame-0.0.29.dist-info/WHEEL +4 -0
- struct_frame-0.0.29.dist-info/licenses/LICENSE +21 -0
struct_frame/generate.py
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# kate: replace-tabs on; indent-width 4;
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
from struct_frame import FileCGen
|
|
8
|
+
from struct_frame import FileTsGen
|
|
9
|
+
from struct_frame import FilePyGen
|
|
10
|
+
from struct_frame import FileGqlGen
|
|
11
|
+
from proto_schema_parser.parser import Parser
|
|
12
|
+
from proto_schema_parser import ast
|
|
13
|
+
from proto_schema_parser.ast import FieldCardinality
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
|
|
17
|
+
recErrCurrentField = ""
|
|
18
|
+
recErrCurrentMessage = ""
|
|
19
|
+
|
|
20
|
+
default_types = {
|
|
21
|
+
"uint8": {"size": 1},
|
|
22
|
+
"int8": {"size": 1},
|
|
23
|
+
"uint16": {"size": 2},
|
|
24
|
+
"int16": {"size": 2},
|
|
25
|
+
"uint32": {"size": 4},
|
|
26
|
+
"int32": {"size": 4},
|
|
27
|
+
"bool": {"size": 1},
|
|
28
|
+
"float": {"size": 4},
|
|
29
|
+
"double": {"size": 8},
|
|
30
|
+
"int64": {"size": 8},
|
|
31
|
+
"uint64": {"size": 8},
|
|
32
|
+
"string": {"size": 4} # Variable length, estimated size for length prefix
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Enum:
|
|
37
|
+
def __init__(self, package, comments):
|
|
38
|
+
self.name = None
|
|
39
|
+
self.data = {}
|
|
40
|
+
self.size = 1
|
|
41
|
+
self.comments = comments
|
|
42
|
+
self.package = package
|
|
43
|
+
self.isEnum = True
|
|
44
|
+
|
|
45
|
+
def parse(self, enum):
|
|
46
|
+
self.name = enum.name
|
|
47
|
+
comments = []
|
|
48
|
+
for e in enum.elements:
|
|
49
|
+
if type(e) == ast.Comment:
|
|
50
|
+
comments.append(e.text)
|
|
51
|
+
else:
|
|
52
|
+
if e.name in self.data:
|
|
53
|
+
print(f"Enum Field Redclaration")
|
|
54
|
+
return False
|
|
55
|
+
self.data[e.name] = (e.number, comments)
|
|
56
|
+
comments = []
|
|
57
|
+
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
def validate(self, currentPackage, packages):
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
def __str__(self):
|
|
64
|
+
output = ""
|
|
65
|
+
for c in self.comments:
|
|
66
|
+
output = output + c + "\n"
|
|
67
|
+
|
|
68
|
+
output = output + f"Enum: {self.name}\n"
|
|
69
|
+
|
|
70
|
+
for key, value in self.data.items():
|
|
71
|
+
output = output + f"Key: {key}, Value: {value}" + "\n"
|
|
72
|
+
return output
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Field:
|
|
76
|
+
def __init__(self, package, comments):
|
|
77
|
+
self.name = None
|
|
78
|
+
self.fieldType = None
|
|
79
|
+
self.isDefaultType = False
|
|
80
|
+
self.size = 0
|
|
81
|
+
self.validated = False
|
|
82
|
+
self.comments = comments
|
|
83
|
+
self.package = package
|
|
84
|
+
self.isEnum = False
|
|
85
|
+
self.flatten = False
|
|
86
|
+
self.is_array = False
|
|
87
|
+
self.size_option = None # Fixed size using [size=X]
|
|
88
|
+
self.max_size = None # Variable size using [max_size=X]
|
|
89
|
+
# Element size for repeated string arrays [element_size=X]
|
|
90
|
+
self.element_size = None
|
|
91
|
+
|
|
92
|
+
def parse(self, field):
|
|
93
|
+
self.name = field.name
|
|
94
|
+
self.fieldType = field.type
|
|
95
|
+
|
|
96
|
+
# Check if this is a repeated field (array)
|
|
97
|
+
if hasattr(field, 'cardinality') and field.cardinality == FieldCardinality.REPEATED:
|
|
98
|
+
self.is_array = True
|
|
99
|
+
|
|
100
|
+
if self.fieldType in default_types:
|
|
101
|
+
self.isDefaultType = True
|
|
102
|
+
self.size = default_types[self.fieldType]["size"]
|
|
103
|
+
self.validated = True
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
if hasattr(field, 'options') and field.options:
|
|
107
|
+
# options is typically a list of ast.Option
|
|
108
|
+
for opt in field.options:
|
|
109
|
+
oname = getattr(opt, 'name', None)
|
|
110
|
+
ovalue = getattr(opt, 'value', None)
|
|
111
|
+
if not oname:
|
|
112
|
+
continue
|
|
113
|
+
lname = str(oname).strip()
|
|
114
|
+
# Support unqualified and a couple of qualified names
|
|
115
|
+
if lname in ('flatten', '(sf.flatten)', '(struct_frame.flatten)'):
|
|
116
|
+
sval = str(ovalue).strip().lower()
|
|
117
|
+
if sval in ('true', '1', 'yes', 'on') or ovalue is True:
|
|
118
|
+
self.flatten = True
|
|
119
|
+
elif lname in ('size', '(sf.size)', '(struct_frame.size)'):
|
|
120
|
+
# Fixed size for arrays or strings
|
|
121
|
+
try:
|
|
122
|
+
self.size_option = int(ovalue)
|
|
123
|
+
if self.size_option <= 0 or self.size_option > 255:
|
|
124
|
+
print(
|
|
125
|
+
f"Invalid size {self.size_option} for field {self.name}, must be 1-255")
|
|
126
|
+
return False
|
|
127
|
+
except (ValueError, TypeError):
|
|
128
|
+
print(
|
|
129
|
+
f"Invalid size value {ovalue} for field {self.name}, must be an integer")
|
|
130
|
+
return False
|
|
131
|
+
elif lname in ('max_size', '(sf.max_size)', '(struct_frame.max_size)'):
|
|
132
|
+
# Variable size for arrays or strings
|
|
133
|
+
try:
|
|
134
|
+
self.max_size = int(ovalue)
|
|
135
|
+
if self.max_size <= 0 or self.max_size > 255:
|
|
136
|
+
print(
|
|
137
|
+
f"Invalid max_size {self.max_size} for field {self.name}, must be 1-255")
|
|
138
|
+
return False
|
|
139
|
+
except (ValueError, TypeError):
|
|
140
|
+
print(
|
|
141
|
+
f"Invalid max_size value {ovalue} for field {self.name}, must be an integer")
|
|
142
|
+
return False
|
|
143
|
+
elif lname in ('element_size', '(sf.element_size)', '(struct_frame.element_size)'):
|
|
144
|
+
# Individual element size for repeated string arrays
|
|
145
|
+
try:
|
|
146
|
+
self.element_size = int(ovalue)
|
|
147
|
+
if self.element_size <= 0 or self.element_size > 255:
|
|
148
|
+
print(
|
|
149
|
+
f"Invalid element_size {self.element_size} for field {self.name}, must be 1-255")
|
|
150
|
+
return False
|
|
151
|
+
except (ValueError, TypeError):
|
|
152
|
+
print(
|
|
153
|
+
f"Invalid element_size value {ovalue} for field {self.name}, must be an integer")
|
|
154
|
+
return False
|
|
155
|
+
except Exception:
|
|
156
|
+
pass
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
def validate(self, currentPackage, packages):
|
|
160
|
+
|
|
161
|
+
global recErrCurrentField
|
|
162
|
+
recErrCurrentField = self.name
|
|
163
|
+
if not self.validated:
|
|
164
|
+
ret = currentPackage.findFieldType(self.fieldType)
|
|
165
|
+
|
|
166
|
+
if ret:
|
|
167
|
+
if ret.validate(currentPackage, packages):
|
|
168
|
+
self.isEnum = ret.isEnum
|
|
169
|
+
self.validated = True
|
|
170
|
+
base_size = ret.size
|
|
171
|
+
else:
|
|
172
|
+
print(
|
|
173
|
+
f"Failed to validate Field: {self.name} of Type: {self.fieldType} in Package: {currentPackage.name}")
|
|
174
|
+
return False
|
|
175
|
+
else:
|
|
176
|
+
print(
|
|
177
|
+
f"Failed to find Field: {self.name} of Type: {self.fieldType} in Package: {currentPackage.name}")
|
|
178
|
+
return False
|
|
179
|
+
else:
|
|
180
|
+
base_size = self.size
|
|
181
|
+
|
|
182
|
+
# Calculate size for arrays and strings
|
|
183
|
+
if self.is_array:
|
|
184
|
+
if self.fieldType == "string":
|
|
185
|
+
# String arrays need both array size AND individual element size
|
|
186
|
+
if self.element_size is None:
|
|
187
|
+
print(
|
|
188
|
+
f"String array field {self.name} missing required element_size option")
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
if self.size_option is not None:
|
|
192
|
+
# Fixed string array: size_option strings, each element_size bytes
|
|
193
|
+
self.size = self.size_option * self.element_size
|
|
194
|
+
elif self.max_size is not None:
|
|
195
|
+
# Variable string array: 1 byte count + max_size strings of element_size bytes each
|
|
196
|
+
self.size = 1 + (self.max_size * self.element_size)
|
|
197
|
+
else:
|
|
198
|
+
print(
|
|
199
|
+
f"String array field {self.name} missing required size or max_size option")
|
|
200
|
+
return False
|
|
201
|
+
else:
|
|
202
|
+
# Non-string arrays
|
|
203
|
+
if self.size_option is not None:
|
|
204
|
+
# Fixed array: always full, no count byte needed
|
|
205
|
+
self.size = base_size * self.size_option
|
|
206
|
+
elif self.max_size is not None:
|
|
207
|
+
# Variable array: 1 byte for count + max space
|
|
208
|
+
self.size = 1 + (base_size * self.max_size)
|
|
209
|
+
else:
|
|
210
|
+
print(
|
|
211
|
+
f"Array field {self.name} missing required size or max_size option")
|
|
212
|
+
return False
|
|
213
|
+
elif self.fieldType == "string":
|
|
214
|
+
if self.size_option is not None:
|
|
215
|
+
# Fixed string: exactly size_option characters
|
|
216
|
+
self.size = self.size_option
|
|
217
|
+
elif self.max_size is not None:
|
|
218
|
+
# Variable string: 1 byte length + max characters
|
|
219
|
+
self.size = 1 + self.max_size
|
|
220
|
+
else:
|
|
221
|
+
print(
|
|
222
|
+
f"String field {self.name} missing required size or max_size option")
|
|
223
|
+
return False
|
|
224
|
+
else:
|
|
225
|
+
self.size = base_size
|
|
226
|
+
|
|
227
|
+
# Debug output
|
|
228
|
+
array_info = ""
|
|
229
|
+
if self.is_array:
|
|
230
|
+
if self.fieldType == "string":
|
|
231
|
+
# String arrays show both array size and individual element size
|
|
232
|
+
if self.size_option is not None:
|
|
233
|
+
array_info = f", fixed_string_array size={self.size_option}, element_size={self.element_size}"
|
|
234
|
+
elif self.max_size is not None:
|
|
235
|
+
array_info = f", bounded_string_array max_size={self.max_size}, element_size={self.element_size}"
|
|
236
|
+
else:
|
|
237
|
+
# Regular arrays
|
|
238
|
+
if self.size_option is not None:
|
|
239
|
+
array_info = f", fixed_array size={self.size_option}"
|
|
240
|
+
elif self.max_size is not None:
|
|
241
|
+
array_info = f", bounded_array max_size={self.max_size}"
|
|
242
|
+
elif self.fieldType == "string":
|
|
243
|
+
# Regular strings
|
|
244
|
+
if self.size_option is not None:
|
|
245
|
+
array_info = f", fixed_string size={self.size_option}"
|
|
246
|
+
elif self.max_size is not None:
|
|
247
|
+
array_info = f", variable_string max_size={self.max_size}"
|
|
248
|
+
print(
|
|
249
|
+
f" Field {self.name}: type={self.fieldType}, is_array={self.is_array}{array_info}, calculated_size={self.size}")
|
|
250
|
+
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
def __str__(self):
|
|
254
|
+
output = ""
|
|
255
|
+
for c in self.comments:
|
|
256
|
+
output = output + c + "\n"
|
|
257
|
+
array_info = ""
|
|
258
|
+
if self.is_array:
|
|
259
|
+
if self.size_option is not None:
|
|
260
|
+
array_info = f", Array[size={self.size_option}]"
|
|
261
|
+
elif self.max_size is not None:
|
|
262
|
+
array_info = f", Array[max_size={self.max_size}]"
|
|
263
|
+
else:
|
|
264
|
+
array_info = ", Array[no size specified]"
|
|
265
|
+
elif self.fieldType == "string":
|
|
266
|
+
if self.size_option is not None:
|
|
267
|
+
array_info = f", String[size={self.size_option}]"
|
|
268
|
+
elif self.max_size is not None:
|
|
269
|
+
array_info = f", String[max_size={self.max_size}]"
|
|
270
|
+
output = output + \
|
|
271
|
+
f"Field: {self.name}, Type:{self.fieldType}, Size:{self.size}{array_info}"
|
|
272
|
+
return output
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class Message:
|
|
276
|
+
def __init__(self, package, comments):
|
|
277
|
+
self.id = None
|
|
278
|
+
self.size = 0
|
|
279
|
+
self.name = None
|
|
280
|
+
self.fields = {}
|
|
281
|
+
self.validated = False
|
|
282
|
+
self.comments = comments
|
|
283
|
+
self.package = package
|
|
284
|
+
self.isEnum = False
|
|
285
|
+
|
|
286
|
+
def parse(self, msg):
|
|
287
|
+
self.name = msg.name
|
|
288
|
+
comments = []
|
|
289
|
+
for e in msg.elements:
|
|
290
|
+
if type(e) == ast.Option:
|
|
291
|
+
if e.name == "msgid":
|
|
292
|
+
if self.id:
|
|
293
|
+
raise Exception(f"Redefinition of msg_id for {e.name}")
|
|
294
|
+
self.id = e.value
|
|
295
|
+
elif type(e) == ast.Comment:
|
|
296
|
+
comments.append(e.text)
|
|
297
|
+
elif type(e) == ast.Field:
|
|
298
|
+
if e.name in self.fields:
|
|
299
|
+
print(f"Field Redclaration")
|
|
300
|
+
return False
|
|
301
|
+
self.fields[e.name] = Field(self.package, comments)
|
|
302
|
+
comments = []
|
|
303
|
+
if not self.fields[e.name].parse(e):
|
|
304
|
+
return False
|
|
305
|
+
return True
|
|
306
|
+
|
|
307
|
+
def validate(self, currentPackage, packages):
|
|
308
|
+
if self.validated:
|
|
309
|
+
return True
|
|
310
|
+
|
|
311
|
+
global recErrCurrentMessage
|
|
312
|
+
recErrCurrentMessage = self.name
|
|
313
|
+
for key, value in self.fields.items():
|
|
314
|
+
if not value.validate(currentPackage, packages):
|
|
315
|
+
print(
|
|
316
|
+
f"Failed To validate Field: {key}, in Message {self.name}\n")
|
|
317
|
+
return False
|
|
318
|
+
self.size = self.size + value.size
|
|
319
|
+
|
|
320
|
+
# Flatten collision detection: if a field is marked as flatten and is a message,
|
|
321
|
+
# ensure none of the child field names collide with fields in this message.
|
|
322
|
+
parent_field_names = set(self.fields.keys())
|
|
323
|
+
for key, value in self.fields.items():
|
|
324
|
+
if getattr(value, 'flatten', False):
|
|
325
|
+
# Only meaningful for non-default, non-enum message types
|
|
326
|
+
if value.isDefaultType or value.isEnum:
|
|
327
|
+
# Flatten has no effect on primitives/enums; skip
|
|
328
|
+
continue
|
|
329
|
+
child = currentPackage.findFieldType(value.fieldType)
|
|
330
|
+
if not child or getattr(child, 'isEnum', False) or not hasattr(child, 'fields'):
|
|
331
|
+
# Unknown or non-message type; skip
|
|
332
|
+
continue
|
|
333
|
+
for ck in child.fields.keys():
|
|
334
|
+
if ck in parent_field_names:
|
|
335
|
+
print(
|
|
336
|
+
f"Flatten collision in Message {self.name}: field '{key}.{ck}' collides with existing field '{ck}'.")
|
|
337
|
+
return False
|
|
338
|
+
|
|
339
|
+
# Array validation
|
|
340
|
+
for key, value in self.fields.items():
|
|
341
|
+
if value.is_array:
|
|
342
|
+
# All arrays must have size or max_size specified
|
|
343
|
+
if value.size_option is None and value.max_size is None:
|
|
344
|
+
print(
|
|
345
|
+
f"Array field {key} in Message {self.name}: must specify size or max_size option")
|
|
346
|
+
return False
|
|
347
|
+
elif value.fieldType == "string":
|
|
348
|
+
# Strings must have size or max_size specified
|
|
349
|
+
if value.size_option is None and value.max_size is None:
|
|
350
|
+
print(
|
|
351
|
+
f"String field {key} in Message {self.name}: must specify size or max_size option")
|
|
352
|
+
return False
|
|
353
|
+
elif value.max_size is not None or value.size_option is not None or value.element_size is not None:
|
|
354
|
+
print(
|
|
355
|
+
f"Field {key} in Message {self.name}: size/max_size/element_size options can only be used with repeated fields or strings")
|
|
356
|
+
return False
|
|
357
|
+
|
|
358
|
+
self.validated = True
|
|
359
|
+
return True
|
|
360
|
+
|
|
361
|
+
def __str__(self):
|
|
362
|
+
output = ""
|
|
363
|
+
for c in self.comments:
|
|
364
|
+
output = output + c + "\n"
|
|
365
|
+
output = output + \
|
|
366
|
+
f"Message: {self.name}, Size: {self.size}, ID: {self.id}\n"
|
|
367
|
+
|
|
368
|
+
for key, value in self.fields.items():
|
|
369
|
+
output = output + value.__str__() + "\n"
|
|
370
|
+
return output
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class Package:
|
|
374
|
+
def __init__(self, name):
|
|
375
|
+
self.name = name
|
|
376
|
+
self.enums = {}
|
|
377
|
+
self.messages = {}
|
|
378
|
+
|
|
379
|
+
def addEnum(self, enum, comments):
|
|
380
|
+
self.comments = comments
|
|
381
|
+
if enum.name in self.enums:
|
|
382
|
+
print(f"Enum Redclaration")
|
|
383
|
+
return False
|
|
384
|
+
self.enums[enum.name] = Enum(self.name, comments)
|
|
385
|
+
return self.enums[enum.name].parse(enum)
|
|
386
|
+
|
|
387
|
+
def addMessage(self, message, comments):
|
|
388
|
+
if message.name in self.messages:
|
|
389
|
+
print(f"Message Redclaration")
|
|
390
|
+
return False
|
|
391
|
+
self.messages[message.name] = Message(self.name, comments)
|
|
392
|
+
return self.messages[message.name].parse(message)
|
|
393
|
+
|
|
394
|
+
def validatePackage(self, allPackages):
|
|
395
|
+
names = []
|
|
396
|
+
for key, value in self.enums.items():
|
|
397
|
+
if value.name in names:
|
|
398
|
+
print(
|
|
399
|
+
f"Name collision with Enum and Message: {value.name} in Packaage {self.name}")
|
|
400
|
+
return False
|
|
401
|
+
names.append(value.name)
|
|
402
|
+
for key, value in self.messages.items():
|
|
403
|
+
if value.name in names:
|
|
404
|
+
print(
|
|
405
|
+
f"Name collision with Enum and Message: {value.name} in Packaage {self.name}")
|
|
406
|
+
return False
|
|
407
|
+
names.append(value.name)
|
|
408
|
+
|
|
409
|
+
for key, value in self.messages.items():
|
|
410
|
+
if not value.validate(self, allPackages):
|
|
411
|
+
print(
|
|
412
|
+
f"Failed To validate Message: {key}, in Package {self.name}\n")
|
|
413
|
+
return False
|
|
414
|
+
|
|
415
|
+
return True
|
|
416
|
+
|
|
417
|
+
def findFieldType(self, name):
|
|
418
|
+
for key, value in self.enums.items():
|
|
419
|
+
if value.name == name:
|
|
420
|
+
return value
|
|
421
|
+
|
|
422
|
+
for key, value in self.messages.items():
|
|
423
|
+
if value.name == name:
|
|
424
|
+
return value
|
|
425
|
+
|
|
426
|
+
def sortedMessages(self):
|
|
427
|
+
# Need to sort messages to ensure no out of order dependencies.
|
|
428
|
+
return self.messages
|
|
429
|
+
|
|
430
|
+
def __str__(self):
|
|
431
|
+
output = "Package: " + self.name + "\n"
|
|
432
|
+
for key, value in self.enums.items():
|
|
433
|
+
output = output + value.__str__() + "\n"
|
|
434
|
+
for key, value in self.messages.items():
|
|
435
|
+
output = output + value.__str__() + "\n"
|
|
436
|
+
return output
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
packages = {}
|
|
440
|
+
processed_file = []
|
|
441
|
+
required_file = []
|
|
442
|
+
|
|
443
|
+
parser = argparse.ArgumentParser(
|
|
444
|
+
prog='struct_frame',
|
|
445
|
+
description='Message serialization and header generation program')
|
|
446
|
+
|
|
447
|
+
parser.add_argument('filename')
|
|
448
|
+
parser.add_argument('--debug', action='store_true')
|
|
449
|
+
parser.add_argument('--build_c', action='store_true')
|
|
450
|
+
parser.add_argument('--build_ts', action='store_true')
|
|
451
|
+
parser.add_argument('--build_py', action='store_true')
|
|
452
|
+
parser.add_argument('--c_path', nargs=1, type=str, default=['generated/c/'])
|
|
453
|
+
parser.add_argument('--ts_path', nargs=1, type=str, default=['generated/ts/'])
|
|
454
|
+
parser.add_argument('--py_path', nargs=1, type=str, default=['generated/py/'])
|
|
455
|
+
parser.add_argument('--build_gql', action='store_true')
|
|
456
|
+
parser.add_argument('--gql_path', nargs=1, type=str,
|
|
457
|
+
default=['generated/gql/'])
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def parseFile(filename):
|
|
461
|
+
processed_file.append(filename)
|
|
462
|
+
with open(filename, "r") as f:
|
|
463
|
+
result = Parser().parse(f.read())
|
|
464
|
+
|
|
465
|
+
foundPackage = False
|
|
466
|
+
package_name = ""
|
|
467
|
+
comments = []
|
|
468
|
+
|
|
469
|
+
for e in result.file_elements:
|
|
470
|
+
if (type(e) == ast.Package):
|
|
471
|
+
if foundPackage:
|
|
472
|
+
print(
|
|
473
|
+
f"Multiple Package declaration found in file {filename} - {package_name}")
|
|
474
|
+
return False
|
|
475
|
+
foundPackage = True
|
|
476
|
+
package_name = e.name
|
|
477
|
+
if package_name not in packages:
|
|
478
|
+
packages[package_name] = Package(package_name)
|
|
479
|
+
packages
|
|
480
|
+
|
|
481
|
+
elif (type(e) == ast.Enum):
|
|
482
|
+
if not packages[package_name].addEnum(e, comments):
|
|
483
|
+
print(
|
|
484
|
+
f"Enum Error in Package: {package_name} FileName: {filename} EnumName: {e.name}")
|
|
485
|
+
return False
|
|
486
|
+
comments = []
|
|
487
|
+
|
|
488
|
+
elif (type(e) == ast.Message):
|
|
489
|
+
if not packages[package_name].addMessage(e, comments):
|
|
490
|
+
print(
|
|
491
|
+
f"Message Error in Package: {package_name} FileName: {filename} MessageName: {e.name}")
|
|
492
|
+
return False
|
|
493
|
+
comments = []
|
|
494
|
+
|
|
495
|
+
elif (type(e) == ast.Comment):
|
|
496
|
+
comments.append(e.text)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def validatePackages():
|
|
500
|
+
for key, value in packages.items():
|
|
501
|
+
if not value.validatePackage(packages):
|
|
502
|
+
print(f"Failed To Validate Package: {key}")
|
|
503
|
+
return False
|
|
504
|
+
|
|
505
|
+
return True
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def printPackages():
|
|
509
|
+
for key, value in packages.items():
|
|
510
|
+
print(value)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def generateCFileStrings(path):
|
|
514
|
+
out = {}
|
|
515
|
+
for key, value in packages.items():
|
|
516
|
+
name = os.path.join(path, value.name + ".sf.h")
|
|
517
|
+
data = ''.join(FileCGen.generate(value))
|
|
518
|
+
out[name] = data
|
|
519
|
+
|
|
520
|
+
return out
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def generateTsFileStrings(path):
|
|
524
|
+
out = {}
|
|
525
|
+
for key, value in packages.items():
|
|
526
|
+
name = os.path.join(path, value.name + ".sf.ts")
|
|
527
|
+
data = ''.join(FileTsGen.generate(value))
|
|
528
|
+
out[name] = data
|
|
529
|
+
return out
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def generatePyFileStrings(path):
|
|
533
|
+
out = {}
|
|
534
|
+
for key, value in packages.items():
|
|
535
|
+
name = os.path.join(path, value.name + "_sf.py")
|
|
536
|
+
data = ''.join(FilePyGen.generate(value))
|
|
537
|
+
out[name] = data
|
|
538
|
+
return out
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def main():
|
|
542
|
+
args = parser.parse_args()
|
|
543
|
+
parseFile(args.filename)
|
|
544
|
+
|
|
545
|
+
if (not args.build_c and not args.build_ts and not args.build_py and not args.build_gql):
|
|
546
|
+
print("Select at least one build argument")
|
|
547
|
+
return
|
|
548
|
+
|
|
549
|
+
valid = False
|
|
550
|
+
try:
|
|
551
|
+
valid = validatePackages()
|
|
552
|
+
except RecursionError as err:
|
|
553
|
+
print(
|
|
554
|
+
f'Recursion Error. Messages most likely have a cyclical dependancy. Check Message: {recErrCurrentMessage} and Field: {recErrCurrentField}')
|
|
555
|
+
return
|
|
556
|
+
|
|
557
|
+
if not valid:
|
|
558
|
+
print("Validation failed; aborting code generation.")
|
|
559
|
+
return
|
|
560
|
+
|
|
561
|
+
files = {}
|
|
562
|
+
if (args.build_c):
|
|
563
|
+
files.update(generateCFileStrings(args.c_path[0]))
|
|
564
|
+
|
|
565
|
+
if (args.build_ts):
|
|
566
|
+
files.update(generateTsFileStrings(args.ts_path[0]))
|
|
567
|
+
|
|
568
|
+
if (args.build_py):
|
|
569
|
+
files.update(generatePyFileStrings(args.py_path[0]))
|
|
570
|
+
|
|
571
|
+
if (args.build_gql):
|
|
572
|
+
for key, value in packages.items():
|
|
573
|
+
name = os.path.join(args.gql_path[0], value.name + '.graphql')
|
|
574
|
+
data = ''.join(FileGqlGen.generate(value))
|
|
575
|
+
files[name] = data
|
|
576
|
+
|
|
577
|
+
for filename, filedata in files.items():
|
|
578
|
+
dirname = os.path.dirname(filename)
|
|
579
|
+
if dirname and not os.path.exists(dirname):
|
|
580
|
+
os.makedirs(dirname)
|
|
581
|
+
|
|
582
|
+
with open(filename, 'w', encoding='utf-8') as f:
|
|
583
|
+
f.write(filedata)
|
|
584
|
+
|
|
585
|
+
dir_path = os.path.dirname(os.path.realpath(__file__))
|
|
586
|
+
|
|
587
|
+
if (args.build_c):
|
|
588
|
+
shutil.copytree(os.path.join(dir_path, "boilerplate/c"),
|
|
589
|
+
args.c_path[0], dirs_exist_ok=True)
|
|
590
|
+
|
|
591
|
+
if (args.build_ts):
|
|
592
|
+
shutil.copytree(os.path.join(dir_path, "boilerplate/ts"),
|
|
593
|
+
args.ts_path[0], dirs_exist_ok=True)
|
|
594
|
+
|
|
595
|
+
if (args.build_py):
|
|
596
|
+
shutil.copytree(os.path.join(dir_path, "boilerplate/py"),
|
|
597
|
+
args.py_path[0], dirs_exist_ok=True)
|
|
598
|
+
|
|
599
|
+
# No boilerplate for GraphQL currently
|
|
600
|
+
|
|
601
|
+
if args.debug:
|
|
602
|
+
printPackages()
|
|
603
|
+
print("Struct Frame successfully completed")
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
if __name__ == '__main__':
|
|
607
|
+
main()
|