structurize 2.19.0__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.
- avrotize/__init__.py +64 -0
- avrotize/__main__.py +6 -0
- avrotize/_version.py +34 -0
- avrotize/asn1toavro.py +160 -0
- avrotize/avrotize.py +152 -0
- avrotize/avrotocpp.py +483 -0
- avrotize/avrotocsharp.py +1075 -0
- avrotize/avrotocsv.py +121 -0
- avrotize/avrotodatapackage.py +173 -0
- avrotize/avrotodb.py +1383 -0
- avrotize/avrotogo.py +476 -0
- avrotize/avrotographql.py +197 -0
- avrotize/avrotoiceberg.py +210 -0
- avrotize/avrotojava.py +2156 -0
- avrotize/avrotojs.py +250 -0
- avrotize/avrotojsons.py +481 -0
- avrotize/avrotojstruct.py +345 -0
- avrotize/avrotokusto.py +364 -0
- avrotize/avrotomd.py +137 -0
- avrotize/avrotools.py +168 -0
- avrotize/avrotoparquet.py +208 -0
- avrotize/avrotoproto.py +359 -0
- avrotize/avrotopython.py +624 -0
- avrotize/avrotorust.py +435 -0
- avrotize/avrotots.py +598 -0
- avrotize/avrotoxsd.py +344 -0
- avrotize/cddltostructure.py +1841 -0
- avrotize/commands.json +3337 -0
- avrotize/common.py +834 -0
- avrotize/constants.py +72 -0
- avrotize/csvtoavro.py +132 -0
- avrotize/datapackagetoavro.py +76 -0
- avrotize/dependencies/cpp/vcpkg/vcpkg.json +19 -0
- avrotize/dependencies/typescript/node22/package.json +16 -0
- avrotize/dependency_resolver.py +348 -0
- avrotize/dependency_version.py +432 -0
- avrotize/jsonstoavro.py +2167 -0
- avrotize/jsonstostructure.py +2642 -0
- avrotize/jstructtoavro.py +878 -0
- avrotize/kstructtoavro.py +93 -0
- avrotize/kustotoavro.py +455 -0
- avrotize/parquettoavro.py +157 -0
- avrotize/proto2parser.py +498 -0
- avrotize/proto3parser.py +403 -0
- avrotize/prototoavro.py +382 -0
- avrotize/structuretocddl.py +597 -0
- avrotize/structuretocpp.py +697 -0
- avrotize/structuretocsharp.py +2295 -0
- avrotize/structuretocsv.py +365 -0
- avrotize/structuretodatapackage.py +659 -0
- avrotize/structuretodb.py +1125 -0
- avrotize/structuretogo.py +720 -0
- avrotize/structuretographql.py +502 -0
- avrotize/structuretoiceberg.py +355 -0
- avrotize/structuretojava.py +853 -0
- avrotize/structuretojsons.py +498 -0
- avrotize/structuretokusto.py +639 -0
- avrotize/structuretomd.py +322 -0
- avrotize/structuretoproto.py +764 -0
- avrotize/structuretopython.py +772 -0
- avrotize/structuretorust.py +714 -0
- avrotize/structuretots.py +653 -0
- avrotize/structuretoxsd.py +679 -0
- avrotize/xsdtoavro.py +413 -0
- structurize-2.19.0.dist-info/METADATA +107 -0
- structurize-2.19.0.dist-info/RECORD +70 -0
- structurize-2.19.0.dist-info/WHEEL +5 -0
- structurize-2.19.0.dist-info/entry_points.txt +2 -0
- structurize-2.19.0.dist-info/licenses/LICENSE +201 -0
- structurize-2.19.0.dist-info/top_level.txt +1 -0
avrotize/avrotocpp.py
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-statements, line-too-long
|
|
2
|
+
|
|
3
|
+
"""Generates C++ code from Avro schema"""
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from typing import Dict, List, Union
|
|
7
|
+
|
|
8
|
+
from avrotize.common import is_generic_avro_type, pascal, process_template
|
|
9
|
+
|
|
10
|
+
INDENT = ' '
|
|
11
|
+
|
|
12
|
+
JsonNode = Dict[str, 'JsonNode'] | List['JsonNode'] | str | None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AvroToCpp:
|
|
16
|
+
"""Converts Avro schema to C++ code, including JSON and Avro methods"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, base_namespace: str = '') -> None:
|
|
19
|
+
self.base_namespace = base_namespace
|
|
20
|
+
self.output_dir = os.getcwd()
|
|
21
|
+
self.generated_types_avro_namespace: Dict[str, str] = {}
|
|
22
|
+
self.generated_types_cpp_namespace: Dict[str, str] = {}
|
|
23
|
+
self.avro_annotation = False
|
|
24
|
+
self.json_annotation = False
|
|
25
|
+
self.generated_files: List[str] = []
|
|
26
|
+
self.test_files: List[str] = []
|
|
27
|
+
|
|
28
|
+
def safe_identifier(self, name: str) -> str:
|
|
29
|
+
"""Converts a name to a safe C++ identifier"""
|
|
30
|
+
reserved_words = [
|
|
31
|
+
'alignas', 'alignof', 'and', 'and_eq', 'asm', 'atomic_cancel', 'atomic_commit', 'atomic_noexcept', 'auto',
|
|
32
|
+
'bitand', 'bitor', 'bool', 'break', 'case', 'catch', 'char', 'char8_t', 'char16_t', 'char32_t', 'class',
|
|
33
|
+
'compl', 'concept', 'const', 'consteval', 'constexpr', 'constinit', 'const_cast', 'continue', 'co_await',
|
|
34
|
+
'co_return', 'co_yield', 'decltype', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum',
|
|
35
|
+
'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long',
|
|
36
|
+
'mutable', 'namespace', 'new', 'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq', 'private',
|
|
37
|
+
'protected', 'public', 'reflexpr', 'register', 'reinterpret_cast', 'requires', 'return', 'short', 'signed',
|
|
38
|
+
'sizeof', 'static', 'static_assert', 'static_cast', 'struct', 'switch', 'synchronized', 'template', 'this',
|
|
39
|
+
'thread_local', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using',
|
|
40
|
+
'virtual', 'void', 'volatile', 'wchar_t', 'while', 'xor', 'xor_eq'
|
|
41
|
+
]
|
|
42
|
+
if name in reserved_words:
|
|
43
|
+
return f"{name}_"
|
|
44
|
+
return name
|
|
45
|
+
|
|
46
|
+
def map_primitive_to_cpp(self, avro_type: str, is_optional: bool) -> str:
|
|
47
|
+
"""Maps Avro primitive types to C++ types"""
|
|
48
|
+
optional_mapping = {
|
|
49
|
+
'null': 'std::optional<std::monostate>',
|
|
50
|
+
'boolean': 'std::optional<bool>',
|
|
51
|
+
'int': 'std::optional<int>',
|
|
52
|
+
'long': 'std::optional<long long>',
|
|
53
|
+
'float': 'std::optional<float>',
|
|
54
|
+
'double': 'std::optional<double>',
|
|
55
|
+
'bytes': 'std::optional<std::vector<uint8_t>>',
|
|
56
|
+
'string': 'std::optional<std::string>'
|
|
57
|
+
}
|
|
58
|
+
required_mapping = {
|
|
59
|
+
'null': 'std::monostate',
|
|
60
|
+
'boolean': 'bool',
|
|
61
|
+
'int': 'int',
|
|
62
|
+
'long': 'long long',
|
|
63
|
+
'float': 'float',
|
|
64
|
+
'double': 'double',
|
|
65
|
+
'bytes': 'std::vector<uint8_t>',
|
|
66
|
+
'string': 'std::string'
|
|
67
|
+
}
|
|
68
|
+
if '.' in avro_type:
|
|
69
|
+
type_name = avro_type.split('.')[-1]
|
|
70
|
+
package_name = '::'.join(avro_type.split('.')[:-1]).lower()
|
|
71
|
+
avro_type = self.get_qualified_name(package_name, type_name)
|
|
72
|
+
if avro_type in self.generated_types_avro_namespace:
|
|
73
|
+
kind = self.generated_types_avro_namespace[avro_type]
|
|
74
|
+
qualified_class_name = self.get_qualified_name(self.base_namespace, avro_type)
|
|
75
|
+
return qualified_class_name
|
|
76
|
+
else:
|
|
77
|
+
return required_mapping.get(avro_type, avro_type) if not is_optional else optional_mapping.get(avro_type, avro_type)
|
|
78
|
+
|
|
79
|
+
def get_qualified_name(self, namespace: str, name: str) -> str:
|
|
80
|
+
"""Concatenates namespace and name using a double colon separator"""
|
|
81
|
+
return f"{namespace}::{name}" if namespace else name
|
|
82
|
+
|
|
83
|
+
def concat_namespace(self, namespace: str, name: str) -> str:
|
|
84
|
+
"""Concatenates namespace and name using a double colon separator"""
|
|
85
|
+
if namespace and name:
|
|
86
|
+
return f"{namespace}::{name}"
|
|
87
|
+
elif namespace:
|
|
88
|
+
return namespace
|
|
89
|
+
return name
|
|
90
|
+
|
|
91
|
+
def convert_avro_type_to_cpp(self, field_name: str, avro_type: Union[str, Dict, List], nullable: bool = False, parent_namespace: str = '') -> str:
|
|
92
|
+
"""Converts Avro type to C++ type"""
|
|
93
|
+
if isinstance(avro_type, str):
|
|
94
|
+
return self.map_primitive_to_cpp(avro_type, nullable)
|
|
95
|
+
elif isinstance(avro_type, list):
|
|
96
|
+
if is_generic_avro_type(avro_type):
|
|
97
|
+
return 'nlohmann::json'
|
|
98
|
+
non_null_types = [t for t in avro_type if t != 'null']
|
|
99
|
+
if len(non_null_types) == 1:
|
|
100
|
+
if isinstance(non_null_types[0], str):
|
|
101
|
+
return self.map_primitive_to_cpp(non_null_types[0], True)
|
|
102
|
+
else:
|
|
103
|
+
return self.convert_avro_type_to_cpp(field_name, non_null_types[0], parent_namespace=parent_namespace)
|
|
104
|
+
else:
|
|
105
|
+
types: List[str] = [self.convert_avro_type_to_cpp(field_name, t, parent_namespace=parent_namespace) for t in non_null_types]
|
|
106
|
+
return 'std::variant<' + ', '.join(types) + '>'
|
|
107
|
+
elif isinstance(avro_type, dict):
|
|
108
|
+
if avro_type['type'] in ['record', 'enum']:
|
|
109
|
+
return self.generate_class_or_enum(avro_type, parent_namespace)
|
|
110
|
+
elif avro_type['type'] == 'fixed' or avro_type['type'] == 'bytes' and 'logicalType' in avro_type:
|
|
111
|
+
if avro_type['logicalType'] == 'decimal':
|
|
112
|
+
return 'std::string' # Handle decimal as string for simplicity
|
|
113
|
+
elif avro_type['type'] == 'array':
|
|
114
|
+
item_type = self.convert_avro_type_to_cpp(field_name, avro_type['items'], nullable=True, parent_namespace=parent_namespace)
|
|
115
|
+
return f"std::vector<{item_type}>"
|
|
116
|
+
elif avro_type['type'] == 'map':
|
|
117
|
+
values_type = self.convert_avro_type_to_cpp(field_name, avro_type['values'], nullable=True, parent_namespace=parent_namespace)
|
|
118
|
+
return f"std::map<std::string, {values_type}>"
|
|
119
|
+
elif 'logicalType' in avro_type:
|
|
120
|
+
if avro_type['logicalType'] == 'date':
|
|
121
|
+
return 'std::chrono::system_clock::time_point'
|
|
122
|
+
elif avro_type['logicalType'] == 'time-millis' or avro_type['logicalType'] == 'time-micros':
|
|
123
|
+
return 'std::chrono::milliseconds'
|
|
124
|
+
elif avro_type['logicalType'] == 'timestamp-millis' or avro_type['logicalType'] == 'timestamp-micros':
|
|
125
|
+
return 'std::chrono::system_clock::time_point'
|
|
126
|
+
elif avro_type['logicalType'] == 'uuid':
|
|
127
|
+
return 'boost::uuids::uuid'
|
|
128
|
+
return self.convert_avro_type_to_cpp(field_name, avro_type['type'], parent_namespace=parent_namespace)
|
|
129
|
+
return 'nlohmann::json'
|
|
130
|
+
|
|
131
|
+
def generate_class_or_enum(self, avro_schema: Dict, parent_namespace: str) -> str:
|
|
132
|
+
"""Generates a C++ class or enum from an Avro schema"""
|
|
133
|
+
if avro_schema['type'] == 'record':
|
|
134
|
+
return self.generate_class(avro_schema, parent_namespace)
|
|
135
|
+
elif avro_schema['type'] == 'enum':
|
|
136
|
+
return self.generate_enum(avro_schema, parent_namespace)
|
|
137
|
+
return 'nlohmann::json'
|
|
138
|
+
|
|
139
|
+
def generate_class(self, avro_schema: Dict, parent_namespace: str) -> str:
|
|
140
|
+
"""Generates a C++ class from an Avro record schema"""
|
|
141
|
+
class_definition = ''
|
|
142
|
+
if 'doc' in avro_schema:
|
|
143
|
+
class_definition += f"// {avro_schema['doc']}\n"
|
|
144
|
+
avro_namespace = avro_schema.get('namespace', parent_namespace)
|
|
145
|
+
namespace = self.concat_namespace(self.base_namespace, avro_namespace.replace('.', '::'))
|
|
146
|
+
class_name = self.safe_identifier(avro_schema['name'])
|
|
147
|
+
qualified_class_name = self.get_qualified_name(namespace, class_name)
|
|
148
|
+
if qualified_class_name in self.generated_types_avro_namespace:
|
|
149
|
+
return qualified_class_name
|
|
150
|
+
self.generated_types_avro_namespace[qualified_class_name] = avro_namespace
|
|
151
|
+
self.generated_types_cpp_namespace[qualified_class_name] = "class"
|
|
152
|
+
|
|
153
|
+
# Track the includes for member types
|
|
154
|
+
member_includes = set()
|
|
155
|
+
|
|
156
|
+
class_definition += f"class {class_name} {{\n"
|
|
157
|
+
class_definition += "public:\n"
|
|
158
|
+
for field in avro_schema.get('fields', []):
|
|
159
|
+
field_name = self.safe_identifier(field['name'])
|
|
160
|
+
|
|
161
|
+
# Track the Avro type before conversion to C++ type
|
|
162
|
+
avro_field_type = field['type']
|
|
163
|
+
|
|
164
|
+
# Convert to C++ type
|
|
165
|
+
field_type = self.convert_avro_type_to_cpp(field_name, avro_field_type, parent_namespace=avro_namespace)
|
|
166
|
+
|
|
167
|
+
# Check if the field_type is a custom type that requires an include
|
|
168
|
+
if isinstance(avro_field_type, dict) and avro_field_type['type'] in ['record', 'enum']:
|
|
169
|
+
include_namespace = self.concat_namespace(
|
|
170
|
+
self.base_namespace,
|
|
171
|
+
avro_field_type.get('namespace', avro_namespace).replace('.', '::')
|
|
172
|
+
)
|
|
173
|
+
include_name = avro_field_type['name']
|
|
174
|
+
member_includes.add(self.get_qualified_name(include_namespace, include_name))
|
|
175
|
+
|
|
176
|
+
class_definition += f"{INDENT}{field_type} {field_name};\n"
|
|
177
|
+
class_definition += "public:\n"
|
|
178
|
+
class_definition += f"{INDENT}{class_name}() = default;\n"
|
|
179
|
+
|
|
180
|
+
class_definition += process_template("avrotocpp/dataclass_body.jinja", class_name=class_name, avro_annotation=self.avro_annotation, json_annotation=self.json_annotation)
|
|
181
|
+
if self.json_annotation:
|
|
182
|
+
class_definition += self.generate_is_json_match_method(class_name, avro_schema)
|
|
183
|
+
class_definition += self.generate_to_json_method(class_name)
|
|
184
|
+
class_definition += "};\n\n"
|
|
185
|
+
|
|
186
|
+
# Create includes
|
|
187
|
+
includes = self.generate_includes(member_includes)
|
|
188
|
+
|
|
189
|
+
self.write_to_file(namespace, class_name, includes, class_definition)
|
|
190
|
+
self.generate_unit_test(class_name, avro_schema.get('fields', []), namespace)
|
|
191
|
+
return qualified_class_name
|
|
192
|
+
|
|
193
|
+
def generate_enum(self, avro_schema: Dict, parent_namespace: str) -> str:
|
|
194
|
+
"""Generates a C++ enum from an Avro enum schema"""
|
|
195
|
+
enum_definition = ''
|
|
196
|
+
if 'doc' in avro_schema:
|
|
197
|
+
enum_definition += f"// {avro_schema['doc']}\n"
|
|
198
|
+
avro_namespace = avro_schema.get('namespace', parent_namespace)
|
|
199
|
+
namespace = self.concat_namespace(self.base_namespace, avro_namespace.replace('.', '::'))
|
|
200
|
+
enum_name = self.safe_identifier(avro_schema['name'])
|
|
201
|
+
qualified_enum_name = self.get_qualified_name(namespace, enum_name)
|
|
202
|
+
self.generated_types_avro_namespace[qualified_enum_name] = avro_namespace
|
|
203
|
+
self.generated_types_cpp_namespace[qualified_enum_name] = "enum"
|
|
204
|
+
symbols = avro_schema.get('symbols', [])
|
|
205
|
+
enum_definition += f"enum class {enum_name} {{\n"
|
|
206
|
+
for symbol in symbols:
|
|
207
|
+
enum_definition += f"{INDENT}{symbol},\n"
|
|
208
|
+
enum_definition += "};\n\n"
|
|
209
|
+
self.write_to_file(namespace, enum_name, "", enum_definition)
|
|
210
|
+
return qualified_enum_name
|
|
211
|
+
|
|
212
|
+
def generate_to_byte_array_method(self, class_name: str) -> str:
|
|
213
|
+
"""Generates the to_byte_array method for the class"""
|
|
214
|
+
return process_template("avrotocpp/to_byte_array_method.jinja", class_name=class_name, avro_annotation=self.avro_annotation, json_annotation=self.json_annotation)
|
|
215
|
+
|
|
216
|
+
def generate_from_data_method(self, class_name: str) -> str:
|
|
217
|
+
"""Generates the from_data method for the class"""
|
|
218
|
+
return process_template("avrotocpp/from_data_method.jinja", class_name=class_name, avro_annotation=self.avro_annotation, json_annotation=self.json_annotation)
|
|
219
|
+
|
|
220
|
+
def generate_is_json_match_method(self, class_name: str, avro_schema: Dict) -> str:
|
|
221
|
+
"""Generates the is_json_match method for the class"""
|
|
222
|
+
method_definition = f"\nstatic bool is_json_match(const nlohmann::json& node) {{\n"
|
|
223
|
+
predicates = []
|
|
224
|
+
for field in avro_schema.get('fields', []):
|
|
225
|
+
field_name = self.safe_identifier(field['name'])
|
|
226
|
+
field_type = self.convert_avro_type_to_cpp(field_name, field['type'])
|
|
227
|
+
predicates.append(self.get_is_json_match_clause(field_name, field_type))
|
|
228
|
+
method_definition += f"{INDENT}return " + " && ".join(predicates) + ";\n"
|
|
229
|
+
method_definition += f"}}\n"
|
|
230
|
+
return method_definition
|
|
231
|
+
|
|
232
|
+
def get_is_json_match_clause(self, field_name: str, field_type: str) -> str:
|
|
233
|
+
"""Generates the is_json_match clause for a field"""
|
|
234
|
+
if field_type == 'std::string' or field_type == 'std::optional<std::string>':
|
|
235
|
+
return f"node.contains(\"{field_name}\") && node[\"{field_name}\"].is_string()"
|
|
236
|
+
elif field_type == 'bool' or field_type == 'std::optional<bool>':
|
|
237
|
+
return f"node.contains(\"{field_name}\") && node[\"{field_name}\"].is_boolean()"
|
|
238
|
+
elif field_type == 'int' or field_type == 'std::optional<int>':
|
|
239
|
+
return f"node.contains(\"{field_name}\") && node[\"{field_name}\"].is_number_integer()"
|
|
240
|
+
elif field_type == 'long long' or field_type == 'std::optional<long long>':
|
|
241
|
+
return f"node.contains(\"{field_name}\") && node[\"{field_name}\"].is_number_integer()"
|
|
242
|
+
elif field_type == 'float' or field_type == 'std::optional<float>':
|
|
243
|
+
return f"node.contains(\"{field_name}\") && node[\"{field_name}\"].is_number_float()"
|
|
244
|
+
elif field_type == 'double' or field_type == 'std::optional<double>':
|
|
245
|
+
return f"node.contains(\"{field_name}\") && node[\"{field_name}\"].is_number_float()"
|
|
246
|
+
elif field_type == 'std::vector<uint8_t>' or field_type == 'std::optional<std::vector<uint8_t>>':
|
|
247
|
+
return f"node.contains(\"{field_name}\") && node[\"{field_name}\"].is_binary()"
|
|
248
|
+
elif field_type == 'nlohmann::json':
|
|
249
|
+
return f"node.contains(\"{field_name}\") && node[\"{field_name}\"].is_object()"
|
|
250
|
+
elif field_type.startswith('std::map<std::string, '):
|
|
251
|
+
return f"node.contains(\"{field_name}\") && node[\"{field_name}\"].is_object()"
|
|
252
|
+
elif field_type.startswith('std::vector<'):
|
|
253
|
+
return f"node.contains(\"{field_name}\") && node[\"{field_name}\"].is_array()"
|
|
254
|
+
else:
|
|
255
|
+
return f"{field_type}::is_json_match(node[\"{field_name}\"])"
|
|
256
|
+
|
|
257
|
+
def generate_to_json_method(self, class_name: str) -> str:
|
|
258
|
+
"""Generates the to_object method for the class"""
|
|
259
|
+
method_definition = f"\nnlohmann::json to_json() const {{\n"
|
|
260
|
+
method_definition += f"{INDENT}return nlohmann::json(*this);\n"
|
|
261
|
+
method_definition += f"}}\n"
|
|
262
|
+
return method_definition
|
|
263
|
+
|
|
264
|
+
def generate_avro_schema(self, class_name: str, avro_schema: Dict) -> str:
|
|
265
|
+
"""Generates the AVRO_SCHEMA static variable and initialization code"""
|
|
266
|
+
schema_json = json.dumps(avro_schema, indent=4)
|
|
267
|
+
return process_template("avrotocpp/avro_schema.jinja", class_name=class_name, schema_json=schema_json)
|
|
268
|
+
|
|
269
|
+
def generate_serialize_avro_method(self, class_name: str) -> str:
|
|
270
|
+
"""Generates the serialize_avro method for the class"""
|
|
271
|
+
return process_template("avrotocpp/serialize_avro_method.jinja", class_name=class_name)
|
|
272
|
+
|
|
273
|
+
def generate_deserialize_avro_method(self, class_name: str) -> str:
|
|
274
|
+
"""Generates the deserialize_avro method for the class"""
|
|
275
|
+
return process_template("avrotocpp/deserialize_avro_method.jinja", class_name=class_name)
|
|
276
|
+
|
|
277
|
+
def generate_union_class(self, class_name: str, field_name: str, avro_type: List) -> str:
|
|
278
|
+
"""Generates a union class for C++"""
|
|
279
|
+
union_class_name = class_name + pascal(field_name) + 'Union'
|
|
280
|
+
class_definition = f"class {union_class_name} {{\n"
|
|
281
|
+
class_definition += "public:\n"
|
|
282
|
+
union_types = [self.convert_avro_type_to_cpp(field_name + "Option" + str(i), t) for i, t in enumerate(avro_type)]
|
|
283
|
+
for union_type in union_types:
|
|
284
|
+
field_name = self.safe_identifier(union_type.split('::')[-1])
|
|
285
|
+
class_definition += f"{INDENT}{union_type} get_{field_name}() const;\n"
|
|
286
|
+
class_definition += "};\n\n"
|
|
287
|
+
|
|
288
|
+
# Write the union class to a separate file
|
|
289
|
+
namespace = self.get_qualified_name(self.base_namespace, class_name.lower())
|
|
290
|
+
includes = self.generate_includes(set(union_types))
|
|
291
|
+
self.write_to_file(namespace, union_class_name, includes, class_definition)
|
|
292
|
+
|
|
293
|
+
return union_class_name
|
|
294
|
+
|
|
295
|
+
def generate_includes(self, member_includes: set) -> str:
|
|
296
|
+
"""Generates the include statements for the member types"""
|
|
297
|
+
includes = '\n'.join([f'#include "{include.replace("::", "/")}.hpp"' for include in member_includes])
|
|
298
|
+
return includes
|
|
299
|
+
|
|
300
|
+
def generate_unit_test(self, class_name: str, fields: List[Dict[str, str]], namespace: str):
|
|
301
|
+
"""Generates a unit test for a given class"""
|
|
302
|
+
test_definition = f'#include <gtest/gtest.h>\n'
|
|
303
|
+
test_definition += f'#include "{namespace.replace("::", "/")}/{class_name}.hpp"\n\n'
|
|
304
|
+
test_definition += f'TEST({class_name}Test, PropertiesTest) {{\n'
|
|
305
|
+
test_definition += f'{INDENT}{namespace}::{class_name} instance;\n'
|
|
306
|
+
|
|
307
|
+
for field in fields:
|
|
308
|
+
field_name = self.safe_identifier(field['name'])
|
|
309
|
+
test_value = self.get_test_value(field['type'])
|
|
310
|
+
test_definition += f'{INDENT}instance.{field_name} = {test_value};\n'
|
|
311
|
+
test_definition += f'{INDENT}EXPECT_EQ(instance.{field_name}, {test_value});\n'
|
|
312
|
+
|
|
313
|
+
test_definition += '}\n'
|
|
314
|
+
|
|
315
|
+
test_dir = os.path.join(self.output_dir, "tests")
|
|
316
|
+
if not os.path.exists(test_dir):
|
|
317
|
+
os.makedirs(test_dir, exist_ok=True)
|
|
318
|
+
|
|
319
|
+
test_file_path = os.path.join(test_dir, f"{class_name}_test.cpp")
|
|
320
|
+
with open(test_file_path, 'w', encoding='utf-8') as file:
|
|
321
|
+
file.write(test_definition)
|
|
322
|
+
|
|
323
|
+
self.test_files.append(test_file_path.replace(os.sep, '/'))
|
|
324
|
+
|
|
325
|
+
def get_test_value(self, avro_type: Union[str, Dict, List]) -> str:
|
|
326
|
+
"""Returns a default test value based on the Avro type"""
|
|
327
|
+
if isinstance(avro_type, str):
|
|
328
|
+
test_values = {
|
|
329
|
+
'string': '"test_string"',
|
|
330
|
+
'boolean': 'true',
|
|
331
|
+
'int': '42',
|
|
332
|
+
'long': '42LL',
|
|
333
|
+
'float': '3.14f',
|
|
334
|
+
'double': '3.14',
|
|
335
|
+
'bytes': '{0x01, 0x02, 0x03}',
|
|
336
|
+
'null': 'std::monostate()',
|
|
337
|
+
}
|
|
338
|
+
return test_values.get(avro_type, '/* Unknown type */')
|
|
339
|
+
|
|
340
|
+
elif isinstance(avro_type, list):
|
|
341
|
+
# For unions, use the first non-null type
|
|
342
|
+
non_null_types = [t for t in avro_type if t != 'null']
|
|
343
|
+
if non_null_types:
|
|
344
|
+
return self.get_test_value(non_null_types[0])
|
|
345
|
+
return '/* Unknown union type */'
|
|
346
|
+
|
|
347
|
+
elif isinstance(avro_type, dict):
|
|
348
|
+
avro_type_name = avro_type['type']
|
|
349
|
+
if avro_type_name == 'record':
|
|
350
|
+
return f"{avro_type['name']}()"
|
|
351
|
+
elif avro_type_name == 'enum':
|
|
352
|
+
return f"{avro_type['name']}::{avro_type['symbols'][0]}"
|
|
353
|
+
elif avro_type_name == 'array':
|
|
354
|
+
item_type = self.get_test_value(avro_type['items'])
|
|
355
|
+
return f"std::vector<{item_type}>{{{item_type}}}"
|
|
356
|
+
elif avro_type_name == 'map':
|
|
357
|
+
value_type = self.get_test_value(avro_type['values'])
|
|
358
|
+
return f"std::map<std::string, {value_type}>{{{{\"key\", {value_type}}}}}"
|
|
359
|
+
elif avro_type_name == 'fixed' or (avro_type_name == 'bytes' and 'logicalType' in avro_type and avro_type['logicalType'] == 'decimal'):
|
|
360
|
+
return '"fixed_bytes"'
|
|
361
|
+
elif 'logicalType' in avro_type:
|
|
362
|
+
logical_type = avro_type['logicalType']
|
|
363
|
+
if logical_type == 'date':
|
|
364
|
+
return 'std::chrono::system_clock::now()'
|
|
365
|
+
elif logical_type in ['time-millis', 'time-micros']:
|
|
366
|
+
return 'std::chrono::milliseconds(123456)'
|
|
367
|
+
elif logical_type in ['timestamp-millis', 'timestamp-micros']:
|
|
368
|
+
return 'std::chrono::system_clock::now()'
|
|
369
|
+
elif logical_type == 'uuid':
|
|
370
|
+
return 'boost::uuids::random_generator()()'
|
|
371
|
+
return '/* Unknown complex type */'
|
|
372
|
+
|
|
373
|
+
return '/* Unknown type */'
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def write_to_file(self, namespace: str, name: str, includes: str, definition: str):
|
|
378
|
+
"""Writes a C++ class or enum to a file"""
|
|
379
|
+
directory_path = os.path.join(
|
|
380
|
+
self.output_dir, "include", namespace.replace('::', os.sep))
|
|
381
|
+
if not os.path.exists(directory_path):
|
|
382
|
+
os.makedirs(directory_path, exist_ok=True)
|
|
383
|
+
file_path = os.path.join(directory_path, f"{name}.hpp")
|
|
384
|
+
|
|
385
|
+
with open(file_path, 'w', encoding='utf-8') as file:
|
|
386
|
+
file.write("#pragma once\n")
|
|
387
|
+
if self.json_annotation:
|
|
388
|
+
file.write("#include <nlohmann/json.hpp>\n")
|
|
389
|
+
if self.avro_annotation:
|
|
390
|
+
file.write("#include <avro/Specific.hh>\n")
|
|
391
|
+
file.write("#include <avro/Encoder.hh>\n")
|
|
392
|
+
file.write("#include <avro/Decoder.hh>\n")
|
|
393
|
+
file.write("#include <avro/Compiler.hh>\n")
|
|
394
|
+
file.write("#include <avro/Stream.hh>\n")
|
|
395
|
+
if "std::vector" in definition:
|
|
396
|
+
file.write("#include <vector>\n")
|
|
397
|
+
if "std::map" in definition:
|
|
398
|
+
file.write("#include <map>\n")
|
|
399
|
+
if "std::optional" in definition:
|
|
400
|
+
file.write("#include <optional>\n")
|
|
401
|
+
file.write("#include <stdexcept>\n")
|
|
402
|
+
if "std::chrono" in definition:
|
|
403
|
+
file.write("#include <chrono>\n")
|
|
404
|
+
if "boost::uuid" in definition:
|
|
405
|
+
file.write("#include <boost/uuid/uuid.hpp>\n")
|
|
406
|
+
file.write("#include <boost/uuid/uuid_io.hpp>\n")
|
|
407
|
+
if includes:
|
|
408
|
+
file.write(includes + '\n')
|
|
409
|
+
if namespace:
|
|
410
|
+
file.write(f"namespace {namespace} {{\n\n")
|
|
411
|
+
file.write(definition)
|
|
412
|
+
if namespace:
|
|
413
|
+
file.write(f"}} // namespace {namespace}\n")
|
|
414
|
+
|
|
415
|
+
# Collect the generated file names
|
|
416
|
+
self.generated_files.append(file_path.replace(os.sep, '/'))
|
|
417
|
+
|
|
418
|
+
def generate_cmake_lists(self, project_name: str):
|
|
419
|
+
"""Generates a CMakeLists.txt file"""
|
|
420
|
+
|
|
421
|
+
# get the current file dir
|
|
422
|
+
cmake_content = process_template("avrotocpp/CMakeLists.txt.jinja", project_name=project_name, avro_annotation=self.avro_annotation, json_annotation=self.json_annotation)
|
|
423
|
+
cmake_path = os.path.join(self.output_dir, 'CMakeLists.txt')
|
|
424
|
+
with open(cmake_path, 'w', encoding='utf-8') as file:
|
|
425
|
+
file.write(cmake_content)
|
|
426
|
+
|
|
427
|
+
def generate_vcpkg_json(self):
|
|
428
|
+
"""Generates a vcpkg.json file"""
|
|
429
|
+
vcpkg_json = process_template("avrotocpp/vcpkg.json.jinja", project_name=self.base_namespace, avro_annotation=self.avro_annotation, json_annotation=self.json_annotation)
|
|
430
|
+
vcpkg_json_path = os.path.join(self.output_dir, 'vcpkg.json')
|
|
431
|
+
with open(vcpkg_json_path, 'w', encoding='utf-8') as file:
|
|
432
|
+
file.write(vcpkg_json)
|
|
433
|
+
|
|
434
|
+
def generate_build_scripts(self):
|
|
435
|
+
"""Generates build scripts for Windows and Linux"""
|
|
436
|
+
build_script_linux = process_template("avrotocpp/build.sh.jinja")
|
|
437
|
+
build_script_windows = process_template("avrotocpp/build.bat.jinja")
|
|
438
|
+
script_path_linux = os.path.join(self.output_dir, 'build.sh')
|
|
439
|
+
script_path_windows = os.path.join(self.output_dir, 'build.bat')
|
|
440
|
+
|
|
441
|
+
with open(script_path_linux, 'w', encoding='utf-8') as file:
|
|
442
|
+
file.write(build_script_linux)
|
|
443
|
+
|
|
444
|
+
with open(script_path_windows, 'w', encoding='utf-8') as file:
|
|
445
|
+
file.write(build_script_windows)
|
|
446
|
+
|
|
447
|
+
def convert_schema(self, schema: Union[Dict, List], output_dir: str):
|
|
448
|
+
"""Converts Avro schema to C++"""
|
|
449
|
+
if not isinstance(schema, list):
|
|
450
|
+
schema = [schema]
|
|
451
|
+
if not os.path.exists(output_dir):
|
|
452
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
453
|
+
self.output_dir = output_dir
|
|
454
|
+
for avro_schema in (x for x in schema if isinstance(x, dict)):
|
|
455
|
+
self.generate_class_or_enum(avro_schema, '')
|
|
456
|
+
self.generate_cmake_lists(self.base_namespace)
|
|
457
|
+
self.generate_build_scripts()
|
|
458
|
+
self.generate_vcpkg_json()
|
|
459
|
+
|
|
460
|
+
def convert(self, avro_schema_path: str, output_dir: str):
|
|
461
|
+
"""Converts Avro schema to C++"""
|
|
462
|
+
with open(avro_schema_path, 'r', encoding='utf-8') as file:
|
|
463
|
+
schema = json.load(file)
|
|
464
|
+
self.convert_schema(schema, output_dir)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def convert_avro_to_cpp(avro_schema_path, output_dir, namespace='', avro_annotation=False, json_annotation=False):
|
|
468
|
+
"""Converts Avro schema to C++ classes"""
|
|
469
|
+
|
|
470
|
+
if not namespace:
|
|
471
|
+
namespace = os.path.splitext(os.path.basename(avro_schema_path))[0].replace('-', '_')
|
|
472
|
+
|
|
473
|
+
avroToCpp = AvroToCpp(namespace)
|
|
474
|
+
avroToCpp.avro_annotation = avro_annotation
|
|
475
|
+
avroToCpp.json_annotation = json_annotation
|
|
476
|
+
avroToCpp.convert(avro_schema_path, output_dir)
|
|
477
|
+
|
|
478
|
+
def convert_avro_schema_to_cpp(avro_schema: Dict, output_dir: str, namespace: str = '', avro_annotation: bool = False, json_annotation: bool = False):
|
|
479
|
+
"""Converts Avro schema to C++ classes"""
|
|
480
|
+
avroToCpp = AvroToCpp(namespace)
|
|
481
|
+
avroToCpp.avro_annotation = avro_annotation
|
|
482
|
+
avroToCpp.json_annotation = json_annotation
|
|
483
|
+
avroToCpp.convert_schema(avro_schema, output_dir)
|