struct-frame 0.0.27__py3-none-any.whl → 0.0.30__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.

Potentially problematic release.


This version of struct-frame might be problematic. Click here for more details.

struct_frame/c_gen.py CHANGED
@@ -17,6 +17,7 @@ c_types = {"uint8": "uint8_t",
17
17
  "double": "double",
18
18
  "uint64": 'uint64_t',
19
19
  "int64": 'int64_t',
20
+ "string": "char", # Add string type support
20
21
  }
21
22
 
22
23
 
@@ -60,6 +61,16 @@ class EnumCGen():
60
61
  result += ' %s;\n' % (enumName)
61
62
 
62
63
  result += 'typedef uint8_t %s_t;' % (enumName)
64
+
65
+ # Add module-prefixed enum constants for compatibility
66
+ result += '\n\n/* Enum constants with module prefix */\n'
67
+ module_prefix = CamelToSnakeCase(field.package).upper()
68
+ for d in field.data:
69
+ # Use the already correct enum constant name
70
+ enum_constant = f"{CamelToSnakeCase(field.name).upper()}_{StyleC.enum_entry(d)}"
71
+ module_constant = f"{module_prefix}_{enum_constant}"
72
+ result += f'#define {module_constant:<35} {enum_constant}\n'
73
+
63
74
  return result
64
75
 
65
76
 
@@ -67,18 +78,70 @@ class FieldCGen():
67
78
  @staticmethod
68
79
  def generate(field):
69
80
  result = ''
70
-
71
81
  var_name = field.name
72
82
  type_name = field.fieldType
83
+
84
+ # Handle basic type resolution
73
85
  if type_name in c_types:
74
- type_name = c_types[type_name]
86
+ base_type = c_types[type_name]
75
87
  else:
76
- type_name = '%s%s' % (pascalCase(field.package), type_name)
77
88
  if field.isEnum:
78
- type_name = '%s_t' % type_name
79
-
80
- result += ' %s %s%s;' % (type_name, var_name, "")
89
+ base_type = '%s%s_t' % (pascalCase(field.package), type_name)
90
+ else:
91
+ base_type = '%s%s' % (pascalCase(field.package), type_name)
92
+
93
+ # Handle arrays
94
+ if field.is_array:
95
+ if field.fieldType == "string":
96
+ # String arrays need both array size and individual string size
97
+ if field.size_option is not None:
98
+ # Fixed string array: size_option strings, each element_size chars
99
+ declaration = f"char {var_name}[{field.size_option}][{field.element_size}];"
100
+ comment = f" // Fixed string array: {field.size_option} strings, each max {field.element_size} chars"
101
+ elif field.max_size is not None:
102
+ # Variable string array: count byte + max_size strings of element_size chars each
103
+ declaration = f"struct {{ uint8_t count; char data[{field.max_size}][{field.element_size}]; }} {var_name};"
104
+ comment = f" // Variable string array: up to {field.max_size} strings, each max {field.element_size} chars"
105
+ else:
106
+ declaration = f"char {var_name}[1][1];" # Fallback
107
+ comment = " // String array (error in size specification)"
108
+ else:
109
+ # Non-string arrays
110
+ if field.size_option is not None:
111
+ # Fixed array: always exact size
112
+ declaration = f"{base_type} {var_name}[{field.size_option}];"
113
+ comment = f" // Fixed array: always {field.size_option} elements"
114
+ elif field.max_size is not None:
115
+ # Variable array: count byte + max elements
116
+ declaration = f"struct {{ uint8_t count; {base_type} data[{field.max_size}]; }} {var_name};"
117
+ comment = f" // Variable array: up to {field.max_size} elements"
118
+ else:
119
+ declaration = f"{base_type} {var_name}[1];" # Fallback
120
+ comment = " // Array (error in size specification)"
121
+
122
+ result += f" {declaration}{comment}"
123
+
124
+ # Handle regular strings
125
+ elif field.fieldType == "string":
126
+ if field.size_option is not None:
127
+ # Fixed string: exactly size_option characters
128
+ declaration = f"char {var_name}[{field.size_option}];"
129
+ comment = f" // Fixed string: exactly {field.size_option} chars"
130
+ elif field.max_size is not None:
131
+ # Variable string: length byte + max characters
132
+ declaration = f"struct {{ uint8_t length; char data[{field.max_size}]; }} {var_name};"
133
+ comment = f" // Variable string: up to {field.max_size} chars"
134
+ else:
135
+ declaration = f"char {var_name}[1];" # Fallback
136
+ comment = " // String (error in size specification)"
137
+
138
+ result += f" {declaration}{comment}"
139
+
140
+ # Handle regular fields
141
+ else:
142
+ result += f" {base_type} {var_name};"
81
143
 
144
+ # Add leading comments
82
145
  leading_comment = field.comments
83
146
  if leading_comment:
84
147
  for c in leading_comment:
struct_frame/generate.py CHANGED
@@ -10,6 +10,7 @@ from struct_frame import FilePyGen
10
10
  from struct_frame import FileGqlGen
11
11
  from proto_schema_parser.parser import Parser
12
12
  from proto_schema_parser import ast
13
+ from proto_schema_parser.ast import FieldCardinality
13
14
 
14
15
  import argparse
15
16
 
@@ -27,7 +28,8 @@ default_types = {
27
28
  "float": {"size": 4},
28
29
  "double": {"size": 8},
29
30
  "int64": {"size": 8},
30
- "uint64": {"size": 8}
31
+ "uint64": {"size": 8},
32
+ "string": {"size": 4} # Variable length, estimated size for length prefix
31
33
  }
32
34
 
33
35
 
@@ -80,14 +82,78 @@ class Field:
80
82
  self.comments = comments
81
83
  self.package = package
82
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
83
91
 
84
92
  def parse(self, field):
85
93
  self.name = field.name
86
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
+
87
100
  if self.fieldType in default_types:
88
101
  self.isDefaultType = True
89
102
  self.size = default_types[self.fieldType]["size"]
90
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
91
157
  return True
92
158
 
93
159
  def validate(self, currentPackage, packages):
@@ -100,8 +166,8 @@ class Field:
100
166
  if ret:
101
167
  if ret.validate(currentPackage, packages):
102
168
  self.isEnum = ret.isEnum
103
- self.validate = True
104
- self.size = ret.size
169
+ self.validated = True
170
+ base_size = ret.size
105
171
  else:
106
172
  print(
107
173
  f"Failed to validate Field: {self.name} of Type: {self.fieldType} in Package: {currentPackage.name}")
@@ -110,6 +176,77 @@ class Field:
110
176
  print(
111
177
  f"Failed to find Field: {self.name} of Type: {self.fieldType} in Package: {currentPackage.name}")
112
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}")
113
250
 
114
251
  return True
115
252
 
@@ -117,8 +254,21 @@ class Field:
117
254
  output = ""
118
255
  for c in self.comments:
119
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}]"
120
270
  output = output + \
121
- f"Field: {self.name}, Type:{self.fieldType}, Size:{self.size}"
271
+ f"Field: {self.name}, Type:{self.fieldType}, Size:{self.size}{array_info}"
122
272
  return output
123
273
 
124
274
 
@@ -167,6 +317,44 @@ class Message:
167
317
  return False
168
318
  self.size = self.size + value.size
169
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
+
170
358
  self.validated = True
171
359
  return True
172
360
 
@@ -358,11 +546,17 @@ def main():
358
546
  print("Select at least one build argument")
359
547
  return
360
548
 
549
+ valid = False
361
550
  try:
362
- validatePackages()
551
+ valid = validatePackages()
363
552
  except RecursionError as err:
364
553
  print(
365
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
366
560
 
367
561
  files = {}
368
562
  if (args.build_c):
struct_frame/gql_gen.py CHANGED
@@ -17,6 +17,7 @@ gql_types = {
17
17
  "bool": "Boolean",
18
18
  "float": "Float",
19
19
  "double": "Float",
20
+ "string": "String",
20
21
  }
21
22
 
22
23
 
@@ -79,24 +80,81 @@ class FieldGqlGen:
79
80
  @staticmethod
80
81
  def type_name(field):
81
82
  t = field.fieldType
82
- if t in gql_types:
83
- return gql_types[t]
84
- return f"{pascalCase(field.package)}{t}"
83
+ base_type = gql_types.get(t, f"{pascalCase(field.package)}{t}")
84
+
85
+ # Handle arrays
86
+ if getattr(field, 'is_array', False):
87
+ # Arrays in GraphQL are represented as [Type!]! for non-null arrays of non-null elements
88
+ # or [Type] for nullable arrays, etc. We'll use [Type!]! as the standard
89
+ return f"[{base_type}!]!"
90
+
91
+ return base_type
85
92
 
86
93
  @staticmethod
87
- def generate(field):
94
+ def generate(field, name_override=None):
88
95
  lines = []
89
- if field.comments:
90
- desc = _single_quote_line(field.comments)
96
+
97
+ # Generate clean comments with size information, preferring our generated descriptions over proto comments
98
+ if getattr(field, 'is_array', False):
99
+ # Array field - use our size descriptions
100
+ if getattr(field, 'size_option', None) is not None:
101
+ # Fixed array
102
+ if field.fieldType == "string":
103
+ comment_lines = [
104
+ f"Fixed string array: {field.size_option} strings, each {getattr(field, 'element_size', 'N/A')} chars"]
105
+ else:
106
+ comment_lines = [
107
+ f"Fixed array: always {field.size_option} elements"]
108
+ else:
109
+ # Variable array
110
+ if field.fieldType == "string":
111
+ comment_lines = [
112
+ f"Variable string array: up to {getattr(field, 'max_size', 'N/A')} strings, each max {getattr(field, 'element_size', 'N/A')} chars"]
113
+ else:
114
+ comment_lines = [
115
+ f"Variable array: up to {getattr(field, 'max_size', 'N/A')} elements"]
116
+ elif field.fieldType == "string":
117
+ # Non-array string field
118
+ if getattr(field, 'size_option', None) is not None:
119
+ comment_lines = [
120
+ f"Fixed string: exactly {field.size_option} characters"]
121
+ elif getattr(field, 'max_size', None) is not None:
122
+ comment_lines = [
123
+ f"Variable string: up to {field.max_size} characters"]
124
+ else:
125
+ comment_lines = field.comments[:] if field.comments else []
126
+ else:
127
+ # Regular field - use original comments
128
+ comment_lines = field.comments[:] if field.comments else []
129
+
130
+ if comment_lines:
131
+ desc = _single_quote_line(comment_lines)
91
132
  if desc:
92
133
  lines.append(f" {desc}")
93
- lines.append(f" {field.name}: {FieldGqlGen.type_name(field)}")
134
+
135
+ fname = name_override if name_override else field.name
136
+ lines.append(f" {fname}: {FieldGqlGen.type_name(field)}")
94
137
  return '\n'.join(lines)
95
138
 
139
+ @staticmethod
140
+ def generate_flattened_children(field, package, parent_msg):
141
+ # Expand a message-typed field into its child fields.
142
+ # If a child field name collides, raise an error and fail generation.
143
+ t = field.fieldType
144
+ child_msg = package.messages.get(t)
145
+ if not child_msg:
146
+ # Fallback to normal generation if unknown
147
+ return [FieldGqlGen.generate(field)]
148
+
149
+ out_lines = []
150
+ for ck, cf in child_msg.fields.items():
151
+ out_lines.append(FieldGqlGen.generate(cf, name_override=ck))
152
+ return out_lines
153
+
96
154
 
97
155
  class MessageGqlGen:
98
156
  @staticmethod
99
- def generate(msg):
157
+ def generate(package, msg):
100
158
  lines = []
101
159
  if msg.comments:
102
160
  desc = _triple_quote_block(msg.comments)
@@ -108,7 +166,11 @@ class MessageGqlGen:
108
166
  lines.append(" _empty: Boolean")
109
167
  else:
110
168
  for key, f in msg.fields.items():
111
- lines.append(FieldGqlGen.generate(f))
169
+ if getattr(f, 'flatten', False) and f.fieldType not in gql_types:
170
+ lines.extend(
171
+ FieldGqlGen.generate_flattened_children(f, package, msg))
172
+ else:
173
+ lines.append(FieldGqlGen.generate(f))
112
174
  lines.append("}\n")
113
175
  return '\n'.join(lines)
114
176
 
@@ -132,7 +194,7 @@ class FileGqlGen:
132
194
  if not first_block:
133
195
  yield '\n'
134
196
  first_block = False
135
- yield MessageGqlGen.generate(msg).rstrip() + '\n'
197
+ yield MessageGqlGen.generate(package, msg).rstrip() + '\n'
136
198
 
137
199
  # Root Query type
138
200
  if package.messages:
struct_frame/py_gen.py CHANGED
@@ -17,6 +17,7 @@ py_types = {"uint8": "uint8",
17
17
  "double": "float64",
18
18
  "uint64": 'uint64',
19
19
  "int64": 'int64',
20
+ "string": "str", # Add string type support
20
21
  }
21
22
 
22
23
 
@@ -58,14 +59,51 @@ class FieldPyGen():
58
59
 
59
60
  var_name = field.name
60
61
  type_name = field.fieldType
62
+
63
+ # Handle basic type resolution
61
64
  if type_name in py_types:
62
- type_name = py_types[type_name]
65
+ base_type = py_types[type_name]
63
66
  else:
64
- type_name = '%s%s' % (pascalCase(field.package), type_name)
65
67
  if field.isEnum:
66
- type_name = 'uint8 #%s' % type_name
68
+ # For enums, use the full enum class name for better type safety
69
+ base_type = '%s%s' % (pascalCase(field.package), type_name)
70
+ else:
71
+ base_type = '%s%s' % (pascalCase(field.package), type_name)
72
+
73
+ # Handle arrays
74
+ if field.is_array:
75
+ if field.fieldType == "string":
76
+ # String arrays require both array size and individual element size
77
+ if field.size_option is not None:
78
+ type_annotation = f"list[{base_type}] # Fixed string array size={field.size_option}, each max {field.element_size} chars"
79
+ elif field.max_size is not None:
80
+ type_annotation = f"list[{base_type}] # Bounded string array max_size={field.max_size}, each max {field.element_size} chars"
81
+ else:
82
+ type_annotation = f"list[{base_type}] # String array"
83
+ else:
84
+ # Non-string arrays
85
+ if field.size_option is not None:
86
+ type_annotation = f"list[{base_type}] # Fixed array size={field.size_option}"
87
+ elif field.max_size is not None:
88
+ type_annotation = f"list[{base_type}] # Bounded array max_size={field.max_size}"
89
+ else:
90
+ type_annotation = f"list[{base_type}] # Array"
91
+ # Handle strings with size info
92
+ elif field.fieldType == "string":
93
+ if field.size_option is not None:
94
+ # Fixed string - exact length
95
+ type_annotation = f"str # Fixed string size={field.size_option}"
96
+ elif field.max_size is not None:
97
+ # Variable string - up to max length
98
+ type_annotation = f"str # Variable string max_size={field.max_size}"
99
+ else:
100
+ # Fallback (shouldn't happen with validation)
101
+ type_annotation = "str # String"
102
+ else:
103
+ # Regular field
104
+ type_annotation = base_type
67
105
 
68
- result += ' %s: %s' % (var_name, type_name)
106
+ result += ' %s: %s' % (var_name, type_annotation)
69
107
 
70
108
  leading_comment = field.comments
71
109
  if leading_comment:
@@ -86,7 +124,7 @@ class MessagePyGen():
86
124
  result = '#%s\n' % c
87
125
 
88
126
  structName = '%s%s' % (pascalCase(msg.package), msg.name)
89
- result += 'class %s(Structured):\n' % structName
127
+ result += 'class %s(Structured, byte_order=ByteOrder.LE, byte_order_mode=ByteOrderMode.OVERRIDE):\n' % structName
90
128
  result += ' msg_size = %s\n' % msg.size
91
129
  if msg.id != None:
92
130
  result += ' msg_id = %s\n' % msg.id
@@ -103,15 +141,31 @@ class MessagePyGen():
103
141
  result += f' return out'
104
142
 
105
143
  result += '\n\n def to_dict(self, include_name = True, include_id = True):\n'
106
- result += f' out = {{'
144
+ result += ' out = {}\n'
145
+ # Handle all field types including arrays
107
146
  for key, f in msg.fields.items():
108
- result += f' "{key}" : self.{key}{"" if f.isDefaultType | f.isEnum else ".to_dict(False, False)" }, '
109
- result += f'}}\n'
110
- result += f' if include_name:\n'
147
+ if f.is_array:
148
+ if f.isDefaultType or f.isEnum or f.fieldType == "string":
149
+ # Array of primitives, enums, or strings
150
+ result += f' out["{key}"] = self.{key}\n'
151
+ else:
152
+ # Array of nested messages - convert each element
153
+ result += f' out["{key}"] = [item.to_dict(False, False) for item in self.{key}]\n'
154
+ elif f.isDefaultType or f.isEnum or f.fieldType == "string":
155
+ # Regular primitive, enum, or string field
156
+ result += f' out["{key}"] = self.{key}\n'
157
+ else:
158
+ # Nested message field
159
+ if getattr(f, 'flatten', False):
160
+ # Merge nested dict into parent
161
+ result += f' out.update(self.{key}.to_dict(False, False))\n'
162
+ else:
163
+ result += f' out["{key}"] = self.{key}.to_dict(False, False)\n'
164
+ result += ' if include_name:\n'
111
165
  result += f' out["name"] = "{msg.name}"\n'
112
- result += f' if include_id:\n'
166
+ result += ' if include_id:\n'
113
167
  result += f' out["msg_id"] = "{msg.id}"\n'
114
- result += f' return out\n'
168
+ result += ' return out\n'
115
169
 
116
170
  return result
117
171
 
struct_frame/ts_gen.py CHANGED
@@ -18,6 +18,23 @@ ts_types = {
18
18
  "uint32": 'UInt32LE',
19
19
  "uint64": 'BigInt64LE',
20
20
  "int64": 'BigUInt64LE',
21
+ "string": 'String',
22
+ }
23
+
24
+ # TypeScript type mappings for array declarations
25
+ ts_array_types = {
26
+ "int8": 'number',
27
+ "uint8": 'number',
28
+ "int16": 'number',
29
+ "uint16": 'number',
30
+ "bool": 'boolean',
31
+ "double": 'number',
32
+ "float": 'number',
33
+ "int32": 'number',
34
+ "uint32": 'number',
35
+ "uint64": 'bigint',
36
+ "int64": 'bigint',
37
+ "string": 'string',
21
38
  }
22
39
 
23
40
 
@@ -65,18 +82,66 @@ class FieldTsGen():
65
82
  def generate(field, packageName):
66
83
  result = ''
67
84
  isEnum = False
68
- # isEnum = field.pbtype in ('ENUM', 'UENUM')
69
85
  var_name = StyleC.var_name(field.name)
70
86
  type_name = field.fieldType
71
- if type_name in ts_types:
72
- type_name = ts_types[type_name]
73
- else:
74
- type_name = '%s_%s' % (packageName, StyleC.struct_name(type_name))
75
87
 
76
- if isEnum:
77
- result += ' .UInt8(\'%s\', typed<%s>())' % (var_name, type_name)
88
+ # Handle arrays
89
+ if field.is_array:
90
+ if field.fieldType == "string":
91
+ if field.size_option is not None: # Fixed size array [size=X]
92
+ # Fixed string array: string[size] -> Array<string> with fixed length
93
+ result += f' // Fixed string array: {field.size_option} strings, each exactly {field.element_size} chars\n'
94
+ result += f' .Array(\'{var_name}\', \'String\', {field.size_option})'
95
+ else: # Variable size array [max_size=X]
96
+ # Variable string array: string[max_size=X, element_size=Y] -> Array<string> with count
97
+ result += f' // Variable string array: up to {field.max_size} strings, each max {field.element_size} chars\n'
98
+ result += f' .UInt8(\'{var_name}_count\')\n'
99
+ result += f' .Array(\'{var_name}_data\', \'String\', {field.max_size})'
100
+ else:
101
+ # Regular type arrays
102
+ if type_name in ts_types:
103
+ base_type = ts_types[type_name]
104
+ else:
105
+ base_type = f'{packageName.lower()}_{StyleC.struct_name(type_name).lower()}'
106
+
107
+ if field.size_option is not None: # Fixed size array [size=X]
108
+ # Fixed array: type[size] -> Array<type> with fixed length
109
+ # For fixed arrays, size_option contains the exact size
110
+ array_size = field.size_option
111
+ result += f' // Fixed array: always {array_size} elements\n'
112
+ result += f' .Array(\'{var_name}\', \'{base_type}\', {array_size})'
113
+ else: # Variable size array [max_size=X]
114
+ # Variable array: type[max_size=X] -> count + Array<type>
115
+ max_count = field.max_size # For variable arrays, max_size is the maximum count
116
+ result += f' // Variable array: up to {max_count} elements\n'
117
+ result += f' .UInt8(\'{var_name}_count\')\n'
118
+ result += f' .Array(\'{var_name}_data\', \'{base_type}\', {max_count})'
78
119
  else:
79
- result += ' .%s(\'%s\')' % (type_name, var_name)
120
+ # Non-array fields (existing logic)
121
+ if field.fieldType == "string":
122
+ if hasattr(field, 'size_option') and field.size_option is not None:
123
+ # Fixed string: string[size] -> fixed length string
124
+ result += f' // Fixed string: exactly {field.size_option} chars\n'
125
+ result += f' .String(\'{var_name}\', {field.size_option})'
126
+ elif hasattr(field, 'max_size') and field.max_size is not None:
127
+ # Variable string: string[max_size=X] -> length + data
128
+ result += f' // Variable string: up to {field.max_size} chars\n'
129
+ result += f' .UInt8(\'{var_name}_length\')\n'
130
+ result += f' .String(\'{var_name}_data\', {field.max_size})'
131
+ else:
132
+ # Default string handling (should not occur with new parser)
133
+ result += f' .String(\'{var_name}\')'
134
+ else:
135
+ # Regular types
136
+ if type_name in ts_types:
137
+ type_name = ts_types[type_name]
138
+ else:
139
+ type_name = f'{packageName}_{StyleC.struct_name(type_name)}'
140
+
141
+ if isEnum:
142
+ result += f' .UInt8(\'{var_name}\', typed<{type_name}>())'
143
+ else:
144
+ result += f' .{type_name}(\'{var_name}\')'
80
145
 
81
146
  leading_comment = field.comments
82
147
  if leading_comment:
@@ -182,11 +247,14 @@ class FileTsGen():
182
247
  yield '\n'
183
248
 
184
249
  if package.messages:
185
- yield 'export function get_message_length(msg_id : number){\n switch (msg_id)\n {\n'
186
- for key, msg in package.sortedMessages().items():
187
-
188
- package_msg_name = '%s_%s' % (package.name, msg.name)
189
- yield ' case %s_msgid: return %s_max_size;\n' % (package_msg_name, package_msg_name)
190
-
191
- yield ' default: break;\n } return 0;\n}'
250
+ # Only generate get_message_length if there are messages with IDs
251
+ messages_with_id = [
252
+ msg for key, msg in package.sortedMessages().items() if msg.id]
253
+ if messages_with_id:
254
+ yield 'export function get_message_length(msg_id : number){\n switch (msg_id)\n {\n'
255
+ for msg in messages_with_id:
256
+ package_msg_name = '%s_%s' % (package.name, msg.name)
257
+ yield ' case %s_msgid: return %s_max_size;\n' % (package_msg_name, package_msg_name)
258
+
259
+ yield ' default: break;\n } return 0;\n}'
192
260
  yield '\n'
@@ -0,0 +1,311 @@
1
+ Metadata-Version: 2.4
2
+ Name: struct-frame
3
+ Version: 0.0.30
4
+ Summary: A framework for serializing data with headers
5
+ Project-URL: Homepage, https://github.com/mylonics/struct-frame
6
+ Project-URL: Issues, https://github.com/mylonics/struct-frame/issues
7
+ Author-email: Rijesh Augustine <rijesh@mylonics.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.8
13
+ Requires-Dist: proto-schema-parser>=1.4.5
14
+ Requires-Dist: structured-classes>=3.1.0
15
+ Description-Content-Type: text/markdown
16
+
17
+
18
+ # Struct Frame
19
+
20
+ A multi-language code generation framework that converts Protocol Buffer (.proto) files into serialization/deserialization code for C, TypeScript, Python, and GraphQL. It provides framing and parsing utilities for structured message communication.
21
+
22
+ ## Quick Start
23
+
24
+ ### Installation
25
+ ```bash
26
+ # Install Python dependencies
27
+ pip install proto-schema-parser structured-classes
28
+
29
+ # Install Node.js dependencies (for TypeScript)
30
+ npm install
31
+ ```
32
+
33
+ ### Basic Usage
34
+ ```bash
35
+ # Generate code for all languages
36
+ PYTHONPATH=src python3 src/main.py examples/myl_vehicle.proto --build_c --build_ts --build_py --build_gql
37
+
38
+ # Run comprehensive test suite
39
+ python test_all.py
40
+
41
+ # Generated files will be in the generated/ directory
42
+ ```
43
+
44
+ ### Test Suite
45
+
46
+ The project includes a comprehensive test suite that validates code generation, compilation, and serialization across all supported languages:
47
+
48
+ ```bash
49
+ # Run all tests
50
+ python test_all.py
51
+
52
+ # Run with verbose output
53
+ python tests/run_tests.py --verbose
54
+
55
+ # Skip specific languages
56
+ python tests/run_tests.py --skip-ts --skip-c
57
+
58
+ # Generate code only (no compilation/execution)
59
+ python tests/run_tests.py --generate-only
60
+ ```
61
+
62
+ See `tests/README.md` for detailed test documentation.
63
+
64
+ ### Language-Specific Examples
65
+
66
+ #### Python
67
+ ```bash
68
+ python src/main.py examples/myl_vehicle.proto --build_py
69
+ # Use generated Python classes directly
70
+ ```
71
+
72
+ #### TypeScript
73
+ ```bash
74
+ python src/main.py examples/myl_vehicle.proto --build_ts
75
+ npx tsc examples/index.ts --outDir generated/
76
+ node generated/examples/index.js
77
+ ```
78
+
79
+ #### C
80
+ ```bash
81
+ python src/main.py examples/myl_vehicle.proto --build_c
82
+ gcc examples/main.c -I generated/c -o main
83
+ ./main
84
+ ```
85
+
86
+ #### GraphQL
87
+ ```bash
88
+ python src/main.py examples/myl_vehicle.proto --build_gql
89
+ # Use generated .graphql schema files
90
+ ```
91
+
92
+ ## Feature Compatibility Matrix
93
+
94
+ | Feature | C | TypeScript | Python | GraphQL | Status |
95
+ |---------|---|------------|--------|---------|--------|
96
+ | **Core Types** | ✓ | ✓ | ✓ | ✓ | Stable |
97
+ | **String** | ✓ | ✓ | ✓ | ✓ | Stable |
98
+ | **Enums** | ✓ | ✓ | ✓ | ✓ | Stable |
99
+ | **Nested Messages** | ✓ | ✓ | ✓ | ✓ | Stable |
100
+ | **Message IDs** | ✓ | ✓ | ✓ | N/A | Stable |
101
+ | **Message Serialization** | ✓ | ✓ | ✓ | N/A | Stable |
102
+ | **Flatten** | N/A | N/A | ✓ | ✓ | Partial |
103
+ | **Arrays** | ✓ | ✓ | ✓ | ✓ | Stable |
104
+
105
+ **Legend:**
106
+ - **✓** - Feature works as documented
107
+ - **Partial** - Basic functionality works, some limitations
108
+ - **✗** - Feature not yet available
109
+ - **N/A** - Not applicable for this language
110
+
111
+ ## Project Structure
112
+
113
+ - `src/struct_frame/` - Core code generation framework
114
+ - `generate.py` - Main parser and validation logic
115
+ - `*_gen.py` - Language-specific code generators
116
+ - `boilerplate/` - Runtime libraries for each language
117
+ - `examples/` - Example .proto files and usage demos
118
+ - `main.c` - C API demonstration (encoding/decoding, parsing)
119
+ - `index.ts` - TypeScript API demonstration (similar functionality)
120
+ - `*.proto` - Protocol Buffer definitions for examples
121
+ - `generated/` - Output directory for generated code (git-ignored)
122
+
123
+ ## Protocol Buffer Schema Reference
124
+
125
+ ### Supported Data Types
126
+
127
+ | Type | Size (bytes) | Description | Range/Notes |
128
+ |------|--------------|-------------|-------------|
129
+ | **Integers** |
130
+ | `int8` | 1 | Signed 8-bit integer | -128 to 127 |
131
+ | `uint8` | 1 | Unsigned 8-bit integer | 0 to 255 |
132
+ | `int16` | 2 | Signed 16-bit integer | -32,768 to 32,767 |
133
+ | `uint16` | 2 | Unsigned 16-bit integer | 0 to 65,535 |
134
+ | `int32` | 4 | Signed 32-bit integer | -2.1B to 2.1B |
135
+ | `uint32` | 4 | Unsigned 32-bit integer | 0 to 4.3B |
136
+ | `int64` | 8 | Signed 64-bit integer | Large integers |
137
+ | `uint64` | 8 | Unsigned 64-bit integer | Large positive integers |
138
+ | **Floating Point** |
139
+ | `float` | 4 | Single precision (IEEE 754) | 7 decimal digits |
140
+ | `double` | 8 | Double precision (IEEE 754) | 15-17 decimal digits |
141
+ | **Other** |
142
+ | `bool` | 1 | Boolean value | `true` or `false` |
143
+ | `string` | Variable | UTF-8 encoded string | Length-prefixed |
144
+ | `EnumType` | 1 | Custom enumeration | Defined in .proto |
145
+ | `MessageType` | Variable | Nested message | User-defined structure |
146
+
147
+ > **Note:** All types use little-endian byte order for cross-platform compatibility.
148
+
149
+ ### Array Support
150
+
151
+ Arrays (repeated fields) support all data types - primitives, enums, and messages across all target languages.
152
+
153
+ | Array Type | Syntax | Memory Usage | Use Case |
154
+ |------------|--------|--------------|----------|
155
+ | **Fixed** | `repeated type field = N [size=X];` | `sizeof(type) * X` | Matrices, buffers (always full) |
156
+ | **Bounded** | `repeated type field = N [max_size=X];` | 1 byte (count) + `sizeof(type) * X` | Dynamic lists with limits |
157
+ | **String Arrays** | `repeated string field = N [max_size=X, element_size=Y];` | 1 byte (count) + `X * Y` bytes | Text collections with size limits |
158
+
159
+ ```proto
160
+ message ArrayExample {
161
+ repeated float matrix = 1 [size=9]; // 3x3 matrix (always 9 elements)
162
+ repeated string names = 2 [max_size=10, element_size=32]; // Up to 10 strings, each max 32 chars
163
+ repeated int32 values = 3 [max_size=100]; // Up to 100 integers (variable count)
164
+ }
165
+ ```
166
+
167
+ **Generated Output** (all languages now supported):
168
+ - **Python**: `matrix: list[float]`, `names: list[str]`, `values: list[int]`
169
+ - **C**: `float matrix[9]`, `struct { uint8_t count; char data[10][32]; } names`
170
+ - **TypeScript**: `Array('matrix', 'Float32LE', 9)`, `Array('names_data', 'String', 10)`
171
+ - **GraphQL**: `matrix: [Float!]!`, `names: [String!]!`, `values: [Int!]!`
172
+
173
+ > **Important**: String arrays require both `max_size` (or `size`) AND `element_size` parameters because they are "arrays of arrays" - you need to specify both how many strings AND the maximum size of each individual string. This ensures predictable memory layout and prevents buffer overflows.
174
+
175
+ ### String Type
176
+
177
+ Strings are a special case of bounded character arrays with built-in UTF-8 encoding and null-termination handling across all target languages.
178
+
179
+ | String Type | Syntax | Memory Usage | Use Case |
180
+ |-------------|--------|--------------|----------|
181
+ | **Fixed String** | `string field = N [size=X];` | `X` bytes | Fixed-width text fields |
182
+ | **Variable String** | `string field = N [max_size=X];` | 1 byte (length) + `X` bytes | Text with known maximum length |
183
+
184
+ ```proto
185
+ message StringExample {
186
+ string device_name = 1 [size=16]; // Exactly 16 characters (pad with nulls)
187
+ string description = 2 [max_size=256]; // Up to 256 characters (length-prefixed)
188
+ string error_msg = 3 [max_size=128]; // Up to 128 characters for error messages
189
+ }
190
+ ```
191
+
192
+ **String Benefits:**
193
+ - **Simplified Schema**: No need to specify `repeated uint8` for text data
194
+ - **Automatic Encoding**: UTF-8 encoding/decoding handled by generators
195
+ - **Null Handling**: Proper null-termination and padding for fixed strings
196
+ - **Type Safety**: Clear distinction between binary data and text
197
+ - **Cross-Language**: Consistent string handling across C, TypeScript, and Python
198
+
199
+ ### Message Options
200
+
201
+ **Message ID (`msgid`)** - Required for serializable messages:
202
+ ```proto
203
+ message MyMessage {
204
+ option msgid = 42; // Must be unique within package (0-65535)
205
+ string content = 1;
206
+ }
207
+ ```
208
+
209
+ ### Field Options
210
+
211
+ **Flatten (`flatten=true`)** - Merge nested message fields into parent:
212
+ ```proto
213
+ message Position {
214
+ double lat = 1;
215
+ double lon = 2;
216
+ }
217
+
218
+ message Status {
219
+ Position pos = 1 [flatten=true]; // lat, lon become direct fields
220
+ float battery = 2;
221
+ }
222
+ ```
223
+
224
+ **Array Options** - Control array behavior:
225
+ ```proto
226
+ message Data {
227
+ repeated int32 fixed_buffer = 1 [size=256]; // Always 256 integers
228
+ repeated int32 var_buffer = 2 [max_size=256]; // Up to 256 integers
229
+ repeated string messages = 3 [max_size=10, element_size=64]; // Up to 10 strings, each max 64 chars
230
+ string device_name = 4 [size=32]; // Always 32 characters
231
+ string description = 5 [max_size=256]; // Up to 256 characters
232
+ }
233
+ ```
234
+
235
+ ## Complete Example
236
+
237
+ ```proto
238
+ package sensor_system;
239
+
240
+ enum SensorType {
241
+ TEMPERATURE = 0;
242
+ HUMIDITY = 1;
243
+ PRESSURE = 2;
244
+ }
245
+
246
+ message Position {
247
+ double lat = 1;
248
+ double lon = 2;
249
+ float alt = 3;
250
+ }
251
+
252
+ message SensorReading {
253
+ option msgid = 1;
254
+
255
+ uint32 device_id = 1;
256
+ int64 timestamp = 2;
257
+ SensorType type = 3;
258
+
259
+ // Device name (fixed 16-character string)
260
+ string device_name = 4 [size=16];
261
+
262
+ // Sensor location (flattened)
263
+ Position location = 5 [flatten=true];
264
+
265
+ // Measurement values (up to 8 readings)
266
+ repeated float values = 6 [max_size=8];
267
+
268
+ // Calibration matrix (always 3x3 = 9 elements)
269
+ repeated float calibration = 7 [size=9];
270
+
271
+ // Error message (up to 128 characters)
272
+ string error_msg = 8 [max_size=128];
273
+
274
+ bool valid = 9;
275
+ }
276
+
277
+ message DeviceStatus {
278
+ option msgid = 2;
279
+
280
+ uint32 device_id = 1;
281
+ repeated SensorReading recent_readings = 2 [max_size=10];
282
+ float battery_level = 3;
283
+ }
284
+ ```
285
+
286
+ ## Schema Validation Rules
287
+
288
+ - **Message IDs**: Must be unique within package (0-65535)
289
+ - **Field numbers**: Must be unique within message
290
+ - **Array requirements**: All `repeated` fields must specify `[size=X]` (fixed) or `[max_size=X]` (bounded)
291
+ - **String requirements**: All `string` fields must specify `[size=X]` (fixed) or `[max_size=X]` (variable)
292
+ - **String array requirements**: `repeated string` fields must specify both array size AND `[element_size=Y]`
293
+ - **Flatten constraints**: No field name collisions after flattening
294
+ - **Size limits**: Arrays limited to 255 elements maximum
295
+
296
+ ## Code Generation
297
+
298
+ ```bash
299
+ # Generate all languages
300
+ python src/main.py schema.proto --build_c --build_ts --build_py --build_gql
301
+
302
+ # Language-specific paths
303
+ python src/main.py schema.proto --build_py --py_path output/python/
304
+ python src/main.py schema.proto --build_c --c_path output/c/
305
+ python src/main.py schema.proto --build_ts --ts_path output/typescript/
306
+ python src/main.py schema.proto --build_gql --gql_path output/graphql/
307
+ ```
308
+
309
+ ## Additional Documentation
310
+
311
+ - **[Array Implementation Guide](ARRAY_IMPLEMENTATION.md)** - Comprehensive documentation of array features, syntax, and generated code examples across all languages
@@ -0,0 +1,12 @@
1
+ struct_frame/__init__.py,sha256=m9hfIbcgCYvt-fIeYrJRQyk6I7rq35jSdZIhrMmZntg,374
2
+ struct_frame/__main__.py,sha256=tIybnBeFHvwiwVhodVOSnxhne5AX_80mtXBx4rneSB4,143
3
+ struct_frame/base.py,sha256=1Z_0vMkwz0X8r2hIVLv5yuhwwD929LwNMzVKBqFxxac,2012
4
+ struct_frame/c_gen.py,sha256=4verO94bPBgxOGb4lkhal2RLWI_308ywbV0-54Mxz64,9539
5
+ struct_frame/generate.py,sha256=8YL4MJ--vvOsv5MFqGjKSATSulanEqX_Qf0Jew6x7UU,23416
6
+ struct_frame/gql_gen.py,sha256=jDLsyZJmfpVBpdXE06mixZ3D01NAmRioGJqk_FoQt90,7543
7
+ struct_frame/py_gen.py,sha256=EQqw8u2o-FszROviQyQCpF45bbx0KqjdqVBefpyDqH4,7932
8
+ struct_frame/ts_gen.py,sha256=glT9eA--j_8vaR8tHQ0ekDg7udIVgE9FGA4MGAwiCsA,10103
9
+ struct_frame-0.0.30.dist-info/METADATA,sha256=j4JF0HSUrtNg8lzW6q8skpYXSKsQDG3ne7kKQd_fkXA,10831
10
+ struct_frame-0.0.30.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ struct_frame-0.0.30.dist-info/licenses/LICENSE,sha256=UjbLtGfcHCIqJg9UzEVGoNW8fyX4Ah9ZbsuAmJ_vhmk,1094
12
+ struct_frame-0.0.30.dist-info/RECORD,,
@@ -1,62 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: struct-frame
3
- Version: 0.0.27
4
- Summary: A framework for serializing data with headers
5
- Project-URL: Homepage, https://github.com/mylonics/struct-frame
6
- Project-URL: Issues, https://github.com/mylonics/struct-frame/issues
7
- Author-email: Rijesh Augustine <rijesh@mylonics.com>
8
- License-Expression: MIT
9
- License-File: LICENSE
10
- Classifier: Operating System :: OS Independent
11
- Classifier: Programming Language :: Python :: 3
12
- Requires-Python: >=3.8
13
- Requires-Dist: proto-schema-parser>=1.4.5
14
- Requires-Dist: structured-classes>=3.1.0
15
- Description-Content-Type: text/markdown
16
-
17
-
18
- # Struct Frame
19
-
20
- A framework for serializing data with headers
21
-
22
- ## Quick Start
23
-
24
- ### Python Usage
25
- ```bash
26
- # Install dependencies
27
- pip install -e .
28
-
29
- # Generate code from proto file
30
- python src/main.py examples/myl_vehicle.proto --build_c --build_ts --build_py
31
-
32
- # Generated files will be in the generated/ directory
33
- ```
34
-
35
- ### TypeScript Example
36
- ```bash
37
- # Install TypeScript dependencies
38
- npm i -D typescript typed-struct @types/node
39
-
40
- # Generate TypeScript code first
41
- python src/main.py examples/myl_vehicle.proto --build_ts
42
-
43
- # Compile and run the example
44
- npx tsc examples/index.ts --outDir generated/
45
- node generated/examples/index.js
46
- ```
47
-
48
- ### C Example
49
- ```bash
50
- # Generate C code first
51
- python src/main.py examples/myl_vehicle.proto --build_c
52
-
53
- # Compile the C example
54
- gcc examples/main.c -I generated/c -o main
55
- ./main
56
- ```
57
-
58
- ## Project Structure
59
-
60
- - `src/` - Source code for the struct-frame library
61
- - `examples/` - Example usage and demo files
62
- - `generated/` - Generated output files (ignored by git)
@@ -1,12 +0,0 @@
1
- struct_frame/__init__.py,sha256=m9hfIbcgCYvt-fIeYrJRQyk6I7rq35jSdZIhrMmZntg,374
2
- struct_frame/__main__.py,sha256=tIybnBeFHvwiwVhodVOSnxhne5AX_80mtXBx4rneSB4,143
3
- struct_frame/base.py,sha256=1Z_0vMkwz0X8r2hIVLv5yuhwwD929LwNMzVKBqFxxac,2012
4
- struct_frame/c_gen.py,sha256=RFzEx0EuMoxBwdRi3O1NZGJ4iw3c3h7g8rRU3zD2ubU,5997
5
- struct_frame/generate.py,sha256=rfz7d0w37UWs0qlylOhYgD3k0GVmBoaZAGWQp_HUmHc,13131
6
- struct_frame/gql_gen.py,sha256=6-zaocD3qgPQyovjAihxl3DJZ9OHXM225VvO5KQmtkI,4488
7
- struct_frame/py_gen.py,sha256=7kvMsWPxAyLzTQg_BRj3g-yICH-6ilIGxMABEQdLayQ,4980
8
- struct_frame/ts_gen.py,sha256=qdJgAMxL2lV_WmGZErjHpgukYHNNpKpECarXdfmngEI,6266
9
- struct_frame-0.0.27.dist-info/METADATA,sha256=LwhsBrTZ0SQOMrMbHgWguP3d796SfASF8QwTe_y9cjU,1578
10
- struct_frame-0.0.27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
- struct_frame-0.0.27.dist-info/licenses/LICENSE,sha256=UjbLtGfcHCIqJg9UzEVGoNW8fyX4Ah9ZbsuAmJ_vhmk,1094
12
- struct_frame-0.0.27.dist-info/RECORD,,