avrotize 2.21.1__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 +66 -0
- avrotize/__main__.py +6 -0
- avrotize/_version.py +34 -0
- avrotize/asn1toavro.py +160 -0
- avrotize/avrotize.py +152 -0
- avrotize/avrotocpp/CMakeLists.txt.jinja +77 -0
- avrotize/avrotocpp/build.bat.jinja +7 -0
- avrotize/avrotocpp/build.sh.jinja +7 -0
- avrotize/avrotocpp/dataclass_body.jinja +108 -0
- avrotize/avrotocpp/vcpkg.json.jinja +21 -0
- avrotize/avrotocpp.py +483 -0
- avrotize/avrotocsharp/README.md.jinja +166 -0
- avrotize/avrotocsharp/class_test.cs.jinja +266 -0
- avrotize/avrotocsharp/dataclass_core.jinja +293 -0
- avrotize/avrotocsharp/enum_test.cs.jinja +20 -0
- avrotize/avrotocsharp/project.csproj.jinja +30 -0
- avrotize/avrotocsharp/project.sln.jinja +34 -0
- avrotize/avrotocsharp/run_coverage.ps1.jinja +98 -0
- avrotize/avrotocsharp/run_coverage.sh.jinja +149 -0
- avrotize/avrotocsharp/testproject.csproj.jinja +19 -0
- avrotize/avrotocsharp.py +1180 -0
- avrotize/avrotocsv.py +121 -0
- avrotize/avrotodatapackage.py +173 -0
- avrotize/avrotodb.py +1383 -0
- avrotize/avrotogo/go_enum.jinja +12 -0
- avrotize/avrotogo/go_helpers.jinja +31 -0
- avrotize/avrotogo/go_struct.jinja +151 -0
- avrotize/avrotogo/go_test.jinja +47 -0
- avrotize/avrotogo/go_union.jinja +38 -0
- avrotize/avrotogo.py +476 -0
- avrotize/avrotographql.py +197 -0
- avrotize/avrotoiceberg.py +210 -0
- avrotize/avrotojava/class_test.java.jinja +212 -0
- avrotize/avrotojava/enum_test.java.jinja +21 -0
- avrotize/avrotojava/testproject.pom.jinja +54 -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/README.md.jinja +49 -0
- avrotize/avrotomd.py +137 -0
- avrotize/avrotools.py +168 -0
- avrotize/avrotoparquet.py +208 -0
- avrotize/avrotoproto.py +359 -0
- avrotize/avrotopython/dataclass_core.jinja +241 -0
- avrotize/avrotopython/enum_core.jinja +87 -0
- avrotize/avrotopython/pyproject_toml.jinja +18 -0
- avrotize/avrotopython/test_class.jinja +97 -0
- avrotize/avrotopython/test_enum.jinja +23 -0
- avrotize/avrotopython.py +626 -0
- avrotize/avrotorust/dataclass_enum.rs.jinja +74 -0
- avrotize/avrotorust/dataclass_struct.rs.jinja +204 -0
- avrotize/avrotorust/dataclass_union.rs.jinja +105 -0
- avrotize/avrotorust.py +435 -0
- avrotize/avrotots/class_core.ts.jinja +140 -0
- avrotize/avrotots/class_test.ts.jinja +77 -0
- avrotize/avrotots/enum_core.ts.jinja +46 -0
- avrotize/avrotots/gitignore.jinja +34 -0
- avrotize/avrotots/index.ts.jinja +0 -0
- avrotize/avrotots/package.json.jinja +23 -0
- avrotize/avrotots/tsconfig.json.jinja +21 -0
- avrotize/avrotots.py +687 -0
- avrotize/avrotoxsd.py +344 -0
- avrotize/cddltostructure.py +1841 -0
- avrotize/commands.json +3496 -0
- avrotize/common.py +834 -0
- avrotize/constants.py +87 -0
- avrotize/csvtoavro.py +132 -0
- avrotize/datapackagetoavro.py +76 -0
- avrotize/dependencies/cpp/vcpkg/vcpkg.json +19 -0
- avrotize/dependencies/cs/net90/dependencies.csproj +29 -0
- avrotize/dependencies/go/go121/go.mod +6 -0
- avrotize/dependencies/java/jdk21/pom.xml +91 -0
- avrotize/dependencies/python/py312/requirements.txt +13 -0
- avrotize/dependencies/rust/stable/Cargo.toml +17 -0
- avrotize/dependencies/typescript/node22/package.json +16 -0
- avrotize/dependency_resolver.py +348 -0
- avrotize/dependency_version.py +432 -0
- avrotize/generic/generic.avsc +57 -0
- avrotize/jsonstoavro.py +2167 -0
- avrotize/jsonstostructure.py +2864 -0
- avrotize/jstructtoavro.py +878 -0
- avrotize/kstructtoavro.py +93 -0
- avrotize/kustotoavro.py +455 -0
- avrotize/openapitostructure.py +717 -0
- avrotize/parquettoavro.py +157 -0
- avrotize/proto2parser.py +498 -0
- avrotize/proto3parser.py +403 -0
- avrotize/prototoavro.py +382 -0
- avrotize/prototypes/any.avsc +19 -0
- avrotize/prototypes/api.avsc +106 -0
- avrotize/prototypes/duration.avsc +20 -0
- avrotize/prototypes/field_mask.avsc +18 -0
- avrotize/prototypes/struct.avsc +60 -0
- avrotize/prototypes/timestamp.avsc +20 -0
- avrotize/prototypes/type.avsc +253 -0
- avrotize/prototypes/wrappers.avsc +117 -0
- avrotize/structuretocddl.py +597 -0
- avrotize/structuretocpp/CMakeLists.txt.jinja +76 -0
- avrotize/structuretocpp/build.bat.jinja +3 -0
- avrotize/structuretocpp/build.sh.jinja +3 -0
- avrotize/structuretocpp/dataclass_body.jinja +50 -0
- avrotize/structuretocpp/vcpkg.json.jinja +11 -0
- avrotize/structuretocpp.py +697 -0
- avrotize/structuretocsharp/class_test.cs.jinja +180 -0
- avrotize/structuretocsharp/dataclass_core.jinja +156 -0
- avrotize/structuretocsharp/enum_test.cs.jinja +36 -0
- avrotize/structuretocsharp/json_structure_converters.cs.jinja +399 -0
- avrotize/structuretocsharp/program.cs.jinja +49 -0
- avrotize/structuretocsharp/project.csproj.jinja +17 -0
- avrotize/structuretocsharp/project.sln.jinja +34 -0
- avrotize/structuretocsharp/testproject.csproj.jinja +18 -0
- avrotize/structuretocsharp/tuple_converter.cs.jinja +121 -0
- avrotize/structuretocsharp.py +2295 -0
- avrotize/structuretocsv.py +365 -0
- avrotize/structuretodatapackage.py +659 -0
- avrotize/structuretodb.py +1125 -0
- avrotize/structuretogo/go_enum.jinja +12 -0
- avrotize/structuretogo/go_helpers.jinja +26 -0
- avrotize/structuretogo/go_interface.jinja +18 -0
- avrotize/structuretogo/go_struct.jinja +187 -0
- avrotize/structuretogo/go_test.jinja +70 -0
- avrotize/structuretogo.py +729 -0
- avrotize/structuretographql.py +502 -0
- avrotize/structuretoiceberg.py +355 -0
- avrotize/structuretojava/choice_core.jinja +34 -0
- avrotize/structuretojava/class_core.jinja +23 -0
- avrotize/structuretojava/enum_core.jinja +18 -0
- avrotize/structuretojava/equals_hashcode.jinja +30 -0
- avrotize/structuretojava/pom.xml.jinja +26 -0
- avrotize/structuretojava/tuple_core.jinja +49 -0
- avrotize/structuretojava.py +938 -0
- avrotize/structuretojs/class_core.js.jinja +33 -0
- avrotize/structuretojs/enum_core.js.jinja +10 -0
- avrotize/structuretojs/package.json.jinja +12 -0
- avrotize/structuretojs/test_class.js.jinja +84 -0
- avrotize/structuretojs/test_enum.js.jinja +58 -0
- avrotize/structuretojs/test_runner.js.jinja +45 -0
- avrotize/structuretojs.py +657 -0
- avrotize/structuretojsons.py +498 -0
- avrotize/structuretokusto.py +639 -0
- avrotize/structuretomd/README.md.jinja +204 -0
- avrotize/structuretomd.py +322 -0
- avrotize/structuretoproto.py +764 -0
- avrotize/structuretopython/dataclass_core.jinja +363 -0
- avrotize/structuretopython/enum_core.jinja +45 -0
- avrotize/structuretopython/map_alias.jinja +21 -0
- avrotize/structuretopython/pyproject_toml.jinja +23 -0
- avrotize/structuretopython/test_class.jinja +103 -0
- avrotize/structuretopython/test_enum.jinja +34 -0
- avrotize/structuretopython.py +799 -0
- avrotize/structuretorust/dataclass_enum.rs.jinja +63 -0
- avrotize/structuretorust/dataclass_struct.rs.jinja +121 -0
- avrotize/structuretorust/dataclass_union.rs.jinja +81 -0
- avrotize/structuretorust.py +714 -0
- avrotize/structuretots/class_core.ts.jinja +78 -0
- avrotize/structuretots/enum_core.ts.jinja +6 -0
- avrotize/structuretots/gitignore.jinja +8 -0
- avrotize/structuretots/index.ts.jinja +1 -0
- avrotize/structuretots/package.json.jinja +39 -0
- avrotize/structuretots/test_class.ts.jinja +35 -0
- avrotize/structuretots/tsconfig.json.jinja +21 -0
- avrotize/structuretots.py +740 -0
- avrotize/structuretoxsd.py +679 -0
- avrotize/xsdtoavro.py +413 -0
- avrotize-2.21.1.dist-info/METADATA +1319 -0
- avrotize-2.21.1.dist-info/RECORD +171 -0
- avrotize-2.21.1.dist-info/WHEEL +4 -0
- avrotize-2.21.1.dist-info/entry_points.txt +3 -0
- avrotize-2.21.1.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
# pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-statements, line-too-long
|
|
2
|
+
|
|
3
|
+
"""Generates C++ code from JSON Structure schema"""
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from typing import Dict, List, Union, Set, Optional, Any, cast
|
|
7
|
+
|
|
8
|
+
from avrotize.common import pascal, process_template
|
|
9
|
+
|
|
10
|
+
INDENT = ' '
|
|
11
|
+
|
|
12
|
+
JsonNode = Dict[str, 'JsonNode'] | List['JsonNode'] | str | None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StructureToCpp:
|
|
16
|
+
"""Converts JSON Structure schema to C++ code, including JSON serialization 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_namespace: Dict[str, str] = {}
|
|
22
|
+
self.generated_types_cpp_namespace: Dict[str, str] = {}
|
|
23
|
+
self.json_annotation = True # JSON Structure always has JSON serialization
|
|
24
|
+
self.generated_files: List[str] = []
|
|
25
|
+
self.test_files: List[str] = []
|
|
26
|
+
self.schema_doc: JsonNode = None
|
|
27
|
+
self.schema_registry: Dict[str, Dict] = {}
|
|
28
|
+
self.generated_structure_types: Dict[str, Dict[str, Union[str, Dict, List]]] = {}
|
|
29
|
+
|
|
30
|
+
def safe_identifier(self, name: str) -> str:
|
|
31
|
+
"""Converts a name to a safe C++ identifier"""
|
|
32
|
+
reserved_words = [
|
|
33
|
+
'alignas', 'alignof', 'and', 'and_eq', 'asm', 'atomic_cancel', 'atomic_commit', 'atomic_noexcept', 'auto',
|
|
34
|
+
'bitand', 'bitor', 'bool', 'break', 'case', 'catch', 'char', 'char8_t', 'char16_t', 'char32_t', 'class',
|
|
35
|
+
'compl', 'concept', 'const', 'consteval', 'constexpr', 'constinit', 'const_cast', 'continue', 'co_await',
|
|
36
|
+
'co_return', 'co_yield', 'decltype', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum',
|
|
37
|
+
'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long',
|
|
38
|
+
'mutable', 'namespace', 'new', 'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq', 'private',
|
|
39
|
+
'protected', 'public', 'reflexpr', 'register', 'reinterpret_cast', 'requires', 'return', 'short', 'signed',
|
|
40
|
+
'sizeof', 'static', 'static_assert', 'static_cast', 'struct', 'switch', 'synchronized', 'template', 'this',
|
|
41
|
+
'thread_local', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using',
|
|
42
|
+
'virtual', 'void', 'volatile', 'wchar_t', 'while', 'xor', 'xor_eq'
|
|
43
|
+
]
|
|
44
|
+
if name in reserved_words:
|
|
45
|
+
return f"{name}_"
|
|
46
|
+
return name
|
|
47
|
+
|
|
48
|
+
def map_primitive_to_cpp(self, structure_type: str, is_optional: bool) -> str:
|
|
49
|
+
"""Maps JSON Structure primitive types to C++ types"""
|
|
50
|
+
optional_mapping = {
|
|
51
|
+
'null': 'std::optional<std::monostate>',
|
|
52
|
+
'boolean': 'std::optional<bool>',
|
|
53
|
+
'string': 'std::optional<std::string>',
|
|
54
|
+
'integer': 'std::optional<int>',
|
|
55
|
+
'number': 'std::optional<double>',
|
|
56
|
+
'int8': 'std::optional<int8_t>',
|
|
57
|
+
'uint8': 'std::optional<uint8_t>',
|
|
58
|
+
'int16': 'std::optional<int16_t>',
|
|
59
|
+
'uint16': 'std::optional<uint16_t>',
|
|
60
|
+
'int32': 'std::optional<int32_t>',
|
|
61
|
+
'uint32': 'std::optional<uint32_t>',
|
|
62
|
+
'int64': 'std::optional<int64_t>',
|
|
63
|
+
'uint64': 'std::optional<uint64_t>',
|
|
64
|
+
'float': 'std::optional<float>',
|
|
65
|
+
'double': 'std::optional<double>',
|
|
66
|
+
'binary': 'std::optional<std::vector<uint8_t>>',
|
|
67
|
+
'date': 'std::optional<std::chrono::system_clock::time_point>',
|
|
68
|
+
'time': 'std::optional<std::chrono::milliseconds>',
|
|
69
|
+
'datetime': 'std::optional<std::chrono::system_clock::time_point>',
|
|
70
|
+
'timestamp': 'std::optional<std::chrono::system_clock::time_point>',
|
|
71
|
+
'duration': 'std::optional<std::chrono::milliseconds>',
|
|
72
|
+
'uuid': 'std::optional<boost::uuids::uuid>',
|
|
73
|
+
'uri': 'std::optional<std::string>',
|
|
74
|
+
'jsonpointer': 'std::optional<std::string>',
|
|
75
|
+
'decimal': 'std::optional<std::string>',
|
|
76
|
+
'any': 'nlohmann::json'
|
|
77
|
+
}
|
|
78
|
+
required_mapping = {
|
|
79
|
+
'null': 'std::monostate',
|
|
80
|
+
'boolean': 'bool',
|
|
81
|
+
'string': 'std::string',
|
|
82
|
+
'integer': 'int',
|
|
83
|
+
'number': 'double',
|
|
84
|
+
'int8': 'int8_t',
|
|
85
|
+
'uint8': 'uint8_t',
|
|
86
|
+
'int16': 'int16_t',
|
|
87
|
+
'uint16': 'uint16_t',
|
|
88
|
+
'int32': 'int32_t',
|
|
89
|
+
'uint32': 'uint32_t',
|
|
90
|
+
'int64': 'int64_t',
|
|
91
|
+
'uint64': 'uint64_t',
|
|
92
|
+
'float': 'float',
|
|
93
|
+
'double': 'double',
|
|
94
|
+
'binary': 'std::vector<uint8_t>',
|
|
95
|
+
'date': 'std::chrono::system_clock::time_point',
|
|
96
|
+
'time': 'std::chrono::milliseconds',
|
|
97
|
+
'datetime': 'std::chrono::system_clock::time_point',
|
|
98
|
+
'timestamp': 'std::chrono::system_clock::time_point',
|
|
99
|
+
'duration': 'std::chrono::milliseconds',
|
|
100
|
+
'uuid': 'boost::uuids::uuid',
|
|
101
|
+
'uri': 'std::string',
|
|
102
|
+
'jsonpointer': 'std::string',
|
|
103
|
+
'decimal': 'std::string',
|
|
104
|
+
'any': 'nlohmann::json'
|
|
105
|
+
}
|
|
106
|
+
if '.' in structure_type:
|
|
107
|
+
type_name = structure_type.split('.')[-1]
|
|
108
|
+
package_name = '::'.join(structure_type.split('.')[:-1]).lower()
|
|
109
|
+
structure_type = self.get_qualified_name(package_name, type_name)
|
|
110
|
+
if structure_type in self.generated_types_namespace:
|
|
111
|
+
return self.get_qualified_name(self.base_namespace, structure_type)
|
|
112
|
+
else:
|
|
113
|
+
return required_mapping.get(structure_type, structure_type) if not is_optional else optional_mapping.get(structure_type, f'std::optional<{required_mapping.get(structure_type, structure_type)}>')
|
|
114
|
+
|
|
115
|
+
def get_qualified_name(self, namespace: str, name: str) -> str:
|
|
116
|
+
"""Concatenates namespace and name using a double colon separator"""
|
|
117
|
+
return f"{namespace}::{name}" if namespace else name
|
|
118
|
+
|
|
119
|
+
def concat_namespace(self, namespace: str, name: str) -> str:
|
|
120
|
+
"""Concatenates namespace and name using a double colon separator"""
|
|
121
|
+
if namespace and name:
|
|
122
|
+
return f"{namespace}::{name}"
|
|
123
|
+
elif namespace:
|
|
124
|
+
return namespace
|
|
125
|
+
return name
|
|
126
|
+
|
|
127
|
+
def resolve_ref(self, ref: str, context_schema: Optional[Dict] = None) -> Optional[Dict]:
|
|
128
|
+
""" Resolves a $ref to the actual schema definition """
|
|
129
|
+
# Check if it's an absolute URI reference (schema with $id)
|
|
130
|
+
if not ref.startswith('#/'):
|
|
131
|
+
# Try to resolve from schema registry
|
|
132
|
+
if ref in self.schema_registry:
|
|
133
|
+
return self.schema_registry[ref]
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
# Handle fragment-only references (internal to document)
|
|
137
|
+
path = ref[2:].split('/')
|
|
138
|
+
schema = context_schema if context_schema else self.schema_doc
|
|
139
|
+
|
|
140
|
+
for part in path:
|
|
141
|
+
if not isinstance(schema, dict) or part not in schema:
|
|
142
|
+
return None
|
|
143
|
+
schema = schema[part]
|
|
144
|
+
|
|
145
|
+
return schema
|
|
146
|
+
|
|
147
|
+
def register_schema_ids(self, schema: Dict, base_uri: str = '') -> None:
|
|
148
|
+
""" Recursively registers schemas with $id keywords """
|
|
149
|
+
if not isinstance(schema, dict):
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
# Register this schema if it has an $id
|
|
153
|
+
if '$id' in schema:
|
|
154
|
+
schema_id = schema['$id']
|
|
155
|
+
# Handle relative URIs
|
|
156
|
+
if base_uri and not schema_id.startswith(('http://', 'https://', 'urn:')):
|
|
157
|
+
from urllib.parse import urljoin
|
|
158
|
+
schema_id = urljoin(base_uri, schema_id)
|
|
159
|
+
self.schema_registry[schema_id] = schema
|
|
160
|
+
base_uri = schema_id # Update base URI for nested schemas
|
|
161
|
+
|
|
162
|
+
# Recursively process definitions
|
|
163
|
+
if 'definitions' in schema:
|
|
164
|
+
for def_name, def_schema in schema['definitions'].items():
|
|
165
|
+
if isinstance(def_schema, dict):
|
|
166
|
+
self.register_schema_ids(def_schema, base_uri)
|
|
167
|
+
|
|
168
|
+
# Recursively process properties
|
|
169
|
+
if 'properties' in schema:
|
|
170
|
+
for prop_name, prop_schema in schema['properties'].items():
|
|
171
|
+
if isinstance(prop_schema, dict):
|
|
172
|
+
self.register_schema_ids(prop_schema, base_uri)
|
|
173
|
+
|
|
174
|
+
# Recursively process items, values, etc.
|
|
175
|
+
for key in ['items', 'values', 'additionalProperties']:
|
|
176
|
+
if key in schema and isinstance(schema[key], dict):
|
|
177
|
+
self.register_schema_ids(schema[key], base_uri)
|
|
178
|
+
|
|
179
|
+
def convert_structure_type_to_cpp(self, class_name: str, field_name: str, structure_type: JsonNode, parent_namespace: str, nullable: bool = False) -> str:
|
|
180
|
+
"""Converts JSON Structure type to C++ type"""
|
|
181
|
+
if isinstance(structure_type, str):
|
|
182
|
+
return self.map_primitive_to_cpp(structure_type, nullable)
|
|
183
|
+
elif isinstance(structure_type, list):
|
|
184
|
+
# Handle type unions
|
|
185
|
+
non_null_types = [t for t in structure_type if t != 'null']
|
|
186
|
+
is_nullable = 'null' in structure_type
|
|
187
|
+
if len(non_null_types) == 1:
|
|
188
|
+
if isinstance(non_null_types[0], str):
|
|
189
|
+
return self.map_primitive_to_cpp(non_null_types[0], is_nullable)
|
|
190
|
+
else:
|
|
191
|
+
cpp_type = self.convert_structure_type_to_cpp(class_name, field_name, non_null_types[0], parent_namespace, nullable=False)
|
|
192
|
+
if is_nullable:
|
|
193
|
+
return f'std::optional<{cpp_type}>'
|
|
194
|
+
return cpp_type
|
|
195
|
+
else:
|
|
196
|
+
types: List[str] = [self.convert_structure_type_to_cpp(class_name, field_name, t, parent_namespace, nullable=False) for t in non_null_types]
|
|
197
|
+
return 'std::variant<' + ', '.join(types) + '>'
|
|
198
|
+
elif isinstance(structure_type, dict):
|
|
199
|
+
# Handle $ref
|
|
200
|
+
if '$ref' in structure_type:
|
|
201
|
+
ref_schema = self.resolve_ref(structure_type['$ref'], self.schema_doc if isinstance(self.schema_doc, dict) else None)
|
|
202
|
+
if ref_schema:
|
|
203
|
+
ref_path = structure_type['$ref'].split('/')
|
|
204
|
+
type_name = ref_path[-1]
|
|
205
|
+
ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
|
|
206
|
+
return self.generate_class_or_choice(ref_schema, ref_namespace, write_file=True, explicit_name=type_name)
|
|
207
|
+
return 'nlohmann::json'
|
|
208
|
+
|
|
209
|
+
# Handle enum keyword
|
|
210
|
+
if 'enum' in structure_type:
|
|
211
|
+
return self.generate_enum(structure_type, field_name, parent_namespace, write_file=True)
|
|
212
|
+
|
|
213
|
+
# Handle type keyword
|
|
214
|
+
if 'type' not in structure_type:
|
|
215
|
+
return 'nlohmann::json'
|
|
216
|
+
|
|
217
|
+
struct_type = structure_type['type']
|
|
218
|
+
|
|
219
|
+
if struct_type == 'object':
|
|
220
|
+
return self.generate_class(structure_type, parent_namespace, write_file=True)
|
|
221
|
+
elif struct_type == 'array':
|
|
222
|
+
items_type = self.convert_structure_type_to_cpp(class_name, field_name+'Item', structure_type.get('items', {'type': 'any'}), parent_namespace, nullable=False)
|
|
223
|
+
return f"std::vector<{items_type}>"
|
|
224
|
+
elif struct_type == 'set':
|
|
225
|
+
items_type = self.convert_structure_type_to_cpp(class_name, field_name+'Item', structure_type.get('items', {'type': 'any'}), parent_namespace, nullable=False)
|
|
226
|
+
return f"std::set<{items_type}>"
|
|
227
|
+
elif struct_type == 'map':
|
|
228
|
+
values_type = self.convert_structure_type_to_cpp(class_name, field_name+'Value', structure_type.get('values', {'type': 'any'}), parent_namespace, nullable=False)
|
|
229
|
+
return f"std::map<std::string, {values_type}>"
|
|
230
|
+
elif struct_type == 'choice':
|
|
231
|
+
return self.generate_choice(structure_type, parent_namespace, write_file=True)
|
|
232
|
+
elif struct_type == 'tuple':
|
|
233
|
+
return self.generate_tuple(structure_type, parent_namespace, write_file=True)
|
|
234
|
+
else:
|
|
235
|
+
return self.convert_structure_type_to_cpp(class_name, field_name, struct_type, parent_namespace, nullable)
|
|
236
|
+
return 'nlohmann::json'
|
|
237
|
+
|
|
238
|
+
def generate_class_or_choice(self, structure_schema: Dict, parent_namespace: str, write_file: bool = True, explicit_name: str = '') -> str:
|
|
239
|
+
"""Generates a Class or Choice"""
|
|
240
|
+
struct_type = structure_schema.get('type', 'object')
|
|
241
|
+
if struct_type == 'object':
|
|
242
|
+
return self.generate_class(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
|
|
243
|
+
elif struct_type == 'choice':
|
|
244
|
+
return self.generate_choice(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
|
|
245
|
+
elif struct_type == 'tuple':
|
|
246
|
+
return self.generate_tuple(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
|
|
247
|
+
elif struct_type in ('map', 'array', 'set'):
|
|
248
|
+
# For root-level container types, generate a type alias
|
|
249
|
+
return self.generate_container_alias(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
|
|
250
|
+
return 'nlohmann::json'
|
|
251
|
+
|
|
252
|
+
def generate_class(self, structure_schema: Dict, parent_namespace: str, write_file: bool, explicit_name: str = '') -> str:
|
|
253
|
+
"""Generates a C++ class from a JSON Structure object type"""
|
|
254
|
+
class_definition = ''
|
|
255
|
+
|
|
256
|
+
# Get name and namespace
|
|
257
|
+
class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'UnnamedClass'))
|
|
258
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
259
|
+
namespace = self.concat_namespace(self.base_namespace, schema_namespace.replace('.', '::'))
|
|
260
|
+
|
|
261
|
+
qualified_class_name = self.get_qualified_name(namespace, class_name)
|
|
262
|
+
if qualified_class_name in self.generated_types_namespace:
|
|
263
|
+
return qualified_class_name
|
|
264
|
+
|
|
265
|
+
self.generated_types_namespace[qualified_class_name] = schema_namespace
|
|
266
|
+
self.generated_types_cpp_namespace[qualified_class_name] = "class"
|
|
267
|
+
self.generated_structure_types[qualified_class_name] = structure_schema
|
|
268
|
+
|
|
269
|
+
# Track the includes for member types
|
|
270
|
+
member_includes = set()
|
|
271
|
+
|
|
272
|
+
# Generate class documentation
|
|
273
|
+
if 'description' in structure_schema or 'doc' in structure_schema:
|
|
274
|
+
doc = structure_schema.get('description', structure_schema.get('doc', ''))
|
|
275
|
+
class_definition += f"// {doc}\n"
|
|
276
|
+
|
|
277
|
+
# Check if this is an abstract type
|
|
278
|
+
is_abstract = structure_schema.get('abstract', False)
|
|
279
|
+
|
|
280
|
+
class_definition += f"class {class_name} {{\n"
|
|
281
|
+
class_definition += "public:\n"
|
|
282
|
+
|
|
283
|
+
# Generate properties
|
|
284
|
+
properties = structure_schema.get('properties', {})
|
|
285
|
+
required_props = structure_schema.get('required', [])
|
|
286
|
+
|
|
287
|
+
for prop_name, prop_schema in properties.items():
|
|
288
|
+
field_name = self.safe_identifier(prop_name)
|
|
289
|
+
is_required = prop_name in required_props if not isinstance(required_props, list) or len(required_props) == 0 or not isinstance(required_props[0], list) else any(prop_name in req_set for req_set in required_props)
|
|
290
|
+
|
|
291
|
+
# Convert to C++ type
|
|
292
|
+
field_type = self.convert_structure_type_to_cpp(class_name, field_name, prop_schema, schema_namespace, nullable=not is_required)
|
|
293
|
+
|
|
294
|
+
# Add documentation
|
|
295
|
+
if 'description' in prop_schema or 'doc' in prop_schema:
|
|
296
|
+
field_doc = prop_schema.get('description', prop_schema.get('doc', ''))
|
|
297
|
+
class_definition += f"{INDENT}// {field_doc}\n"
|
|
298
|
+
|
|
299
|
+
class_definition += f"{INDENT}{field_type} {field_name};\n"
|
|
300
|
+
|
|
301
|
+
# Add default constructor
|
|
302
|
+
class_definition += f"\npublic:\n"
|
|
303
|
+
class_definition += f"{INDENT}{class_name}() = default;\n"
|
|
304
|
+
|
|
305
|
+
# Add JSON serialization methods
|
|
306
|
+
class_definition += process_template("structuretocpp/dataclass_body.jinja",
|
|
307
|
+
class_name=class_name,
|
|
308
|
+
json_annotation=self.json_annotation)
|
|
309
|
+
|
|
310
|
+
if self.json_annotation:
|
|
311
|
+
class_definition += self.generate_to_json_method(class_name)
|
|
312
|
+
|
|
313
|
+
class_definition += "};\n\n"
|
|
314
|
+
|
|
315
|
+
# Create includes
|
|
316
|
+
includes = self.generate_includes(member_includes)
|
|
317
|
+
|
|
318
|
+
if write_file:
|
|
319
|
+
self.write_to_file(namespace, class_name, includes, class_definition)
|
|
320
|
+
self.generate_unit_test(class_name, properties, namespace, required_props)
|
|
321
|
+
|
|
322
|
+
return qualified_class_name
|
|
323
|
+
|
|
324
|
+
def generate_enum(self, structure_schema: Dict, field_name: str, parent_namespace: str, write_file: bool) -> str:
|
|
325
|
+
"""Generates a C++ enum from a JSON Structure enum"""
|
|
326
|
+
enum_definition = ''
|
|
327
|
+
|
|
328
|
+
# Generate enum name
|
|
329
|
+
enum_name = pascal(structure_schema.get('name', field_name + 'Enum'))
|
|
330
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
331
|
+
namespace = self.concat_namespace(self.base_namespace, schema_namespace.replace('.', '::'))
|
|
332
|
+
|
|
333
|
+
qualified_enum_name = self.get_qualified_name(namespace, enum_name)
|
|
334
|
+
if qualified_enum_name in self.generated_types_namespace:
|
|
335
|
+
return qualified_enum_name
|
|
336
|
+
|
|
337
|
+
self.generated_types_namespace[qualified_enum_name] = schema_namespace
|
|
338
|
+
self.generated_types_cpp_namespace[qualified_enum_name] = "enum"
|
|
339
|
+
|
|
340
|
+
if 'description' in structure_schema or 'doc' in structure_schema:
|
|
341
|
+
doc = structure_schema.get('description', structure_schema.get('doc', ''))
|
|
342
|
+
enum_definition += f"// {doc}\n"
|
|
343
|
+
|
|
344
|
+
symbols = structure_schema.get('enum', [])
|
|
345
|
+
enum_definition += f"enum class {enum_name} {{\n"
|
|
346
|
+
for symbol in symbols:
|
|
347
|
+
enum_definition += f"{INDENT}{self.safe_identifier(str(symbol))},\n"
|
|
348
|
+
enum_definition += "};\n\n"
|
|
349
|
+
|
|
350
|
+
if write_file:
|
|
351
|
+
self.write_to_file(namespace, enum_name, "", enum_definition)
|
|
352
|
+
|
|
353
|
+
return qualified_enum_name
|
|
354
|
+
|
|
355
|
+
def generate_choice(self, structure_schema: Dict, parent_namespace: str, write_file: bool, explicit_name: str = '') -> str:
|
|
356
|
+
"""Generates a choice (discriminated union) type"""
|
|
357
|
+
# For now, return variant of the choice types
|
|
358
|
+
choices = structure_schema.get('choices', {})
|
|
359
|
+
choice_types = []
|
|
360
|
+
|
|
361
|
+
for choice_name, choice_schema in choices.items():
|
|
362
|
+
if isinstance(choice_schema, dict):
|
|
363
|
+
if '$ref' in choice_schema:
|
|
364
|
+
ref_schema = self.resolve_ref(choice_schema['$ref'], self.schema_doc if isinstance(self.schema_doc, dict) else None)
|
|
365
|
+
if ref_schema:
|
|
366
|
+
ref_path = choice_schema['$ref'].split('/')
|
|
367
|
+
type_name = ref_path[-1]
|
|
368
|
+
ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
|
|
369
|
+
qualified_name = self.generate_class(ref_schema, ref_namespace, write_file=True, explicit_name=type_name)
|
|
370
|
+
choice_types.append(qualified_name)
|
|
371
|
+
elif 'type' in choice_schema:
|
|
372
|
+
choice_type = self.convert_structure_type_to_cpp('Choice', choice_name, choice_schema, parent_namespace, nullable=False)
|
|
373
|
+
choice_types.append(choice_type)
|
|
374
|
+
|
|
375
|
+
if len(choice_types) == 0:
|
|
376
|
+
return 'nlohmann::json'
|
|
377
|
+
elif len(choice_types) == 1:
|
|
378
|
+
return choice_types[0]
|
|
379
|
+
else:
|
|
380
|
+
return f"std::variant<{', '.join(choice_types)}>"
|
|
381
|
+
|
|
382
|
+
def generate_tuple(self, structure_schema: Dict, parent_namespace: str, write_file: bool, explicit_name: str = '') -> str:
|
|
383
|
+
"""Generates a tuple type"""
|
|
384
|
+
# Tuples serialize as JSON arrays
|
|
385
|
+
# For now, use std::tuple
|
|
386
|
+
properties = structure_schema.get('properties', {})
|
|
387
|
+
tuple_order = structure_schema.get('tuple', [])
|
|
388
|
+
|
|
389
|
+
tuple_types = []
|
|
390
|
+
for prop_name in tuple_order:
|
|
391
|
+
if prop_name in properties:
|
|
392
|
+
prop_schema = properties[prop_name]
|
|
393
|
+
prop_type = self.convert_structure_type_to_cpp('Tuple', prop_name, prop_schema, parent_namespace, nullable=False)
|
|
394
|
+
tuple_types.append(prop_type)
|
|
395
|
+
|
|
396
|
+
if len(tuple_types) == 0:
|
|
397
|
+
return 'nlohmann::json'
|
|
398
|
+
|
|
399
|
+
return f"std::tuple<{', '.join(tuple_types)}>"
|
|
400
|
+
|
|
401
|
+
def generate_container_alias(self, structure_schema: Dict, parent_namespace: str, write_file: bool, explicit_name: str = '') -> str:
|
|
402
|
+
"""Generates a type alias for root-level container types (map, array, set)"""
|
|
403
|
+
struct_type = structure_schema.get('type', 'map')
|
|
404
|
+
|
|
405
|
+
# Get name and namespace
|
|
406
|
+
class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', f'Root{struct_type.capitalize()}'))
|
|
407
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
408
|
+
namespace = self.concat_namespace(self.base_namespace, schema_namespace.replace('.', '::'))
|
|
409
|
+
|
|
410
|
+
qualified_name = self.get_qualified_name(namespace, class_name)
|
|
411
|
+
if qualified_name in self.generated_types_namespace:
|
|
412
|
+
return qualified_name
|
|
413
|
+
|
|
414
|
+
self.generated_types_namespace[qualified_name] = schema_namespace
|
|
415
|
+
self.generated_types_cpp_namespace[qualified_name] = "alias"
|
|
416
|
+
self.generated_structure_types[qualified_name] = structure_schema
|
|
417
|
+
|
|
418
|
+
# Determine the underlying type
|
|
419
|
+
if struct_type == 'map':
|
|
420
|
+
values_type = self.convert_structure_type_to_cpp(class_name, 'Value', structure_schema.get('values', {'type': 'any'}), schema_namespace, nullable=False)
|
|
421
|
+
underlying_type = f"std::map<std::string, {values_type}>"
|
|
422
|
+
elif struct_type == 'array':
|
|
423
|
+
items_type = self.convert_structure_type_to_cpp(class_name, 'Item', structure_schema.get('items', {'type': 'any'}), schema_namespace, nullable=False)
|
|
424
|
+
underlying_type = f"std::vector<{items_type}>"
|
|
425
|
+
elif struct_type == 'set':
|
|
426
|
+
items_type = self.convert_structure_type_to_cpp(class_name, 'Item', structure_schema.get('items', {'type': 'any'}), schema_namespace, nullable=False)
|
|
427
|
+
underlying_type = f"std::set<{items_type}>"
|
|
428
|
+
else:
|
|
429
|
+
underlying_type = 'nlohmann::json'
|
|
430
|
+
|
|
431
|
+
# Generate type alias
|
|
432
|
+
alias_definition = ''
|
|
433
|
+
if 'description' in structure_schema or 'doc' in structure_schema:
|
|
434
|
+
doc = structure_schema.get('description', structure_schema.get('doc', ''))
|
|
435
|
+
alias_definition += f"// {doc}\n"
|
|
436
|
+
|
|
437
|
+
alias_definition += f"using {class_name} = {underlying_type};\n\n"
|
|
438
|
+
|
|
439
|
+
if write_file:
|
|
440
|
+
self.write_to_file(namespace, class_name, "", alias_definition)
|
|
441
|
+
|
|
442
|
+
return qualified_name
|
|
443
|
+
|
|
444
|
+
def generate_to_json_method(self, class_name: str) -> str:
|
|
445
|
+
"""Generates the to_json method for the class"""
|
|
446
|
+
method_definition = f"\nnlohmann::json to_json() const {{\n"
|
|
447
|
+
method_definition += f"{INDENT}return nlohmann::json(*this);\n"
|
|
448
|
+
method_definition += f"}}\n"
|
|
449
|
+
return method_definition
|
|
450
|
+
|
|
451
|
+
def generate_includes(self, member_includes: set) -> str:
|
|
452
|
+
"""Generates the include statements for the member types"""
|
|
453
|
+
includes = '\n'.join([f'#include "{include.replace("::", "/")}.hpp"' for include in member_includes])
|
|
454
|
+
return includes
|
|
455
|
+
|
|
456
|
+
def generate_unit_test(self, class_name: str, properties: Dict, namespace: str, required_props: List) -> None:
|
|
457
|
+
"""Generates a unit test for a given class"""
|
|
458
|
+
test_definition = f'#include <gtest/gtest.h>\n'
|
|
459
|
+
test_definition += f'#include "{namespace.replace("::", "/")}/{class_name}.hpp"\n\n'
|
|
460
|
+
test_definition += f'TEST({class_name}Test, PropertiesTest) {{\n'
|
|
461
|
+
test_definition += f'{INDENT}{namespace}::{class_name} instance;\n'
|
|
462
|
+
|
|
463
|
+
for prop_name, prop_schema in properties.items():
|
|
464
|
+
field_name = self.safe_identifier(prop_name)
|
|
465
|
+
is_required = prop_name in required_props if not isinstance(required_props, list) or len(required_props) == 0 or not isinstance(required_props[0], list) else any(prop_name in req_set for req_set in required_props)
|
|
466
|
+
test_value = self.get_test_value(prop_schema, is_required)
|
|
467
|
+
test_definition += f'{INDENT}instance.{field_name} = {test_value};\n'
|
|
468
|
+
test_definition += f'{INDENT}EXPECT_EQ(instance.{field_name}, {test_value});\n'
|
|
469
|
+
|
|
470
|
+
test_definition += '}\n'
|
|
471
|
+
|
|
472
|
+
test_dir = os.path.join(self.output_dir, "tests")
|
|
473
|
+
if not os.path.exists(test_dir):
|
|
474
|
+
os.makedirs(test_dir, exist_ok=True)
|
|
475
|
+
|
|
476
|
+
test_file_path = os.path.join(test_dir, f"{class_name}_test.cpp")
|
|
477
|
+
with open(test_file_path, 'w', encoding='utf-8') as file:
|
|
478
|
+
file.write(test_definition)
|
|
479
|
+
|
|
480
|
+
self.test_files.append(test_file_path.replace(os.sep, '/'))
|
|
481
|
+
|
|
482
|
+
def get_test_value(self, structure_schema: JsonNode, is_required: bool) -> str:
|
|
483
|
+
"""Returns a default test value based on the Structure type"""
|
|
484
|
+
if isinstance(structure_schema, str):
|
|
485
|
+
test_values = {
|
|
486
|
+
'string': '"test_string"',
|
|
487
|
+
'boolean': 'true',
|
|
488
|
+
'integer': '42',
|
|
489
|
+
'number': '3.14',
|
|
490
|
+
'int8': '42',
|
|
491
|
+
'uint8': '42',
|
|
492
|
+
'int16': '42',
|
|
493
|
+
'uint16': '42',
|
|
494
|
+
'int32': '42',
|
|
495
|
+
'uint32': '42',
|
|
496
|
+
'int64': '42LL',
|
|
497
|
+
'uint64': '42ULL',
|
|
498
|
+
'float': '3.14f',
|
|
499
|
+
'double': '3.14',
|
|
500
|
+
'binary': 'std::vector<uint8_t>{0x01, 0x02, 0x03}',
|
|
501
|
+
'date': 'std::chrono::system_clock::now()',
|
|
502
|
+
'time': 'std::chrono::milliseconds(123456)',
|
|
503
|
+
'datetime': 'std::chrono::system_clock::now()',
|
|
504
|
+
'timestamp': 'std::chrono::system_clock::now()',
|
|
505
|
+
'duration': 'std::chrono::milliseconds(1000)',
|
|
506
|
+
'uuid': 'boost::uuids::random_generator()()',
|
|
507
|
+
'uri': '"https://example.com"',
|
|
508
|
+
'jsonpointer': '"/path/to/field"',
|
|
509
|
+
'decimal': '"123.45"',
|
|
510
|
+
'any': 'nlohmann::json::object()',
|
|
511
|
+
'null': 'std::monostate()',
|
|
512
|
+
}
|
|
513
|
+
return test_values.get(structure_schema, '"test"')
|
|
514
|
+
|
|
515
|
+
elif isinstance(structure_schema, list):
|
|
516
|
+
# For unions, use the first non-null type
|
|
517
|
+
non_null_types = [t for t in structure_schema if t != 'null']
|
|
518
|
+
if non_null_types:
|
|
519
|
+
return self.get_test_value(non_null_types[0], True)
|
|
520
|
+
return 'std::monostate()'
|
|
521
|
+
|
|
522
|
+
elif isinstance(structure_schema, dict):
|
|
523
|
+
struct_type = structure_schema.get('type', 'any')
|
|
524
|
+
|
|
525
|
+
if 'enum' in structure_schema:
|
|
526
|
+
symbols = structure_schema.get('enum', [])
|
|
527
|
+
if symbols:
|
|
528
|
+
return f"{pascal(structure_schema.get('name', 'Enum'))}_::{self.safe_identifier(str(symbols[0]))}"
|
|
529
|
+
return '"test"'
|
|
530
|
+
|
|
531
|
+
if struct_type == 'object':
|
|
532
|
+
return f"{pascal(structure_schema.get('name', 'Object'))}()"
|
|
533
|
+
elif struct_type == 'array':
|
|
534
|
+
item_type = self.convert_structure_type_to_cpp('Test', 'Item', structure_schema.get('items', {'type': 'any'}), '', False)
|
|
535
|
+
return f"std::vector<{item_type}>{{}}"
|
|
536
|
+
elif struct_type == 'set':
|
|
537
|
+
item_type = self.convert_structure_type_to_cpp('Test', 'Item', structure_schema.get('items', {'type': 'any'}), '', False)
|
|
538
|
+
return f"std::set<{item_type}>{{}}"
|
|
539
|
+
elif struct_type == 'map':
|
|
540
|
+
value_type = self.convert_structure_type_to_cpp('Test', 'Value', structure_schema.get('values', {'type': 'any'}), '', False)
|
|
541
|
+
return f"std::map<std::string, {value_type}>{{}}"
|
|
542
|
+
elif struct_type == 'tuple':
|
|
543
|
+
return '"tuple_test"'
|
|
544
|
+
else:
|
|
545
|
+
return self.get_test_value(struct_type, is_required)
|
|
546
|
+
|
|
547
|
+
return '"test"'
|
|
548
|
+
|
|
549
|
+
def write_to_file(self, namespace: str, name: str, includes: str, definition: str) -> None:
|
|
550
|
+
"""Writes a C++ class or enum to a file"""
|
|
551
|
+
directory_path = os.path.join(
|
|
552
|
+
self.output_dir, "include", namespace.replace('::', os.sep))
|
|
553
|
+
if not os.path.exists(directory_path):
|
|
554
|
+
os.makedirs(directory_path, exist_ok=True)
|
|
555
|
+
file_path = os.path.join(directory_path, f"{name}.hpp")
|
|
556
|
+
|
|
557
|
+
with open(file_path, 'w', encoding='utf-8') as file:
|
|
558
|
+
file.write("#pragma once\n")
|
|
559
|
+
if self.json_annotation:
|
|
560
|
+
file.write("#include <nlohmann/json.hpp>\n")
|
|
561
|
+
if "std::vector" in definition:
|
|
562
|
+
file.write("#include <vector>\n")
|
|
563
|
+
if "std::set" in definition:
|
|
564
|
+
file.write("#include <set>\n")
|
|
565
|
+
if "std::map" in definition:
|
|
566
|
+
file.write("#include <map>\n")
|
|
567
|
+
if "std::tuple" in definition:
|
|
568
|
+
file.write("#include <tuple>\n")
|
|
569
|
+
if "std::variant" in definition:
|
|
570
|
+
file.write("#include <variant>\n")
|
|
571
|
+
if "std::optional" in definition:
|
|
572
|
+
file.write("#include <optional>\n")
|
|
573
|
+
file.write("#include <stdexcept>\n")
|
|
574
|
+
if "std::chrono" in definition:
|
|
575
|
+
file.write("#include <chrono>\n")
|
|
576
|
+
if "boost::uuid" in definition:
|
|
577
|
+
file.write("#include <boost/uuid/uuid.hpp>\n")
|
|
578
|
+
file.write("#include <boost/uuid/uuid_io.hpp>\n")
|
|
579
|
+
file.write("#include <boost/uuid/uuid_generators.hpp>\n")
|
|
580
|
+
if includes:
|
|
581
|
+
file.write(includes + '\n')
|
|
582
|
+
if namespace:
|
|
583
|
+
file.write(f"\nnamespace {namespace} {{\n\n")
|
|
584
|
+
file.write(definition)
|
|
585
|
+
if namespace:
|
|
586
|
+
file.write(f"}} // namespace {namespace}\n")
|
|
587
|
+
|
|
588
|
+
# Collect the generated file names
|
|
589
|
+
self.generated_files.append(file_path.replace(os.sep, '/'))
|
|
590
|
+
|
|
591
|
+
def generate_cmake_lists(self, project_name: str) -> None:
|
|
592
|
+
"""Generates a CMakeLists.txt file"""
|
|
593
|
+
cmake_content = process_template("structuretocpp/CMakeLists.txt.jinja",
|
|
594
|
+
project_name=project_name,
|
|
595
|
+
json_annotation=self.json_annotation)
|
|
596
|
+
cmake_path = os.path.join(self.output_dir, 'CMakeLists.txt')
|
|
597
|
+
with open(cmake_path, 'w', encoding='utf-8') as file:
|
|
598
|
+
file.write(cmake_content)
|
|
599
|
+
|
|
600
|
+
def generate_vcpkg_json(self) -> None:
|
|
601
|
+
"""Generates a vcpkg.json file"""
|
|
602
|
+
vcpkg_json = process_template("structuretocpp/vcpkg.json.jinja",
|
|
603
|
+
project_name=self.base_namespace,
|
|
604
|
+
json_annotation=self.json_annotation)
|
|
605
|
+
vcpkg_json_path = os.path.join(self.output_dir, 'vcpkg.json')
|
|
606
|
+
with open(vcpkg_json_path, 'w', encoding='utf-8') as file:
|
|
607
|
+
file.write(vcpkg_json)
|
|
608
|
+
|
|
609
|
+
def generate_build_scripts(self) -> None:
|
|
610
|
+
"""Generates build scripts for Windows and Linux"""
|
|
611
|
+
build_script_linux = process_template("structuretocpp/build.sh.jinja")
|
|
612
|
+
build_script_windows = process_template("structuretocpp/build.bat.jinja")
|
|
613
|
+
script_path_linux = os.path.join(self.output_dir, 'build.sh')
|
|
614
|
+
script_path_windows = os.path.join(self.output_dir, 'build.bat')
|
|
615
|
+
|
|
616
|
+
with open(script_path_linux, 'w', encoding='utf-8') as file:
|
|
617
|
+
file.write(build_script_linux)
|
|
618
|
+
|
|
619
|
+
with open(script_path_windows, 'w', encoding='utf-8') as file:
|
|
620
|
+
file.write(build_script_windows)
|
|
621
|
+
|
|
622
|
+
def convert_schema(self, schema: Union[Dict, List], output_dir: str) -> None:
|
|
623
|
+
"""Converts JSON Structure schema to C++"""
|
|
624
|
+
if not isinstance(schema, list):
|
|
625
|
+
schema = [schema]
|
|
626
|
+
if not os.path.exists(output_dir):
|
|
627
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
628
|
+
self.output_dir = output_dir
|
|
629
|
+
|
|
630
|
+
# Register all schema IDs first
|
|
631
|
+
for structure_schema in (s for s in schema if isinstance(s, dict)):
|
|
632
|
+
self.register_schema_ids(structure_schema)
|
|
633
|
+
|
|
634
|
+
# Process each schema
|
|
635
|
+
for structure_schema in (s for s in schema if isinstance(s, dict)):
|
|
636
|
+
self.schema_doc = structure_schema
|
|
637
|
+
|
|
638
|
+
# Process root type
|
|
639
|
+
if 'type' in structure_schema:
|
|
640
|
+
self.generate_class_or_choice(structure_schema, '', write_file=True)
|
|
641
|
+
elif '$root' in structure_schema:
|
|
642
|
+
root_ref = structure_schema['$root']
|
|
643
|
+
root_schema = self.resolve_ref(root_ref, structure_schema)
|
|
644
|
+
if root_schema:
|
|
645
|
+
ref_path = root_ref.split('/')
|
|
646
|
+
type_name = ref_path[-1]
|
|
647
|
+
ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else ''
|
|
648
|
+
self.generate_class_or_choice(root_schema, ref_namespace, write_file=True, explicit_name=type_name)
|
|
649
|
+
|
|
650
|
+
# Process definitions
|
|
651
|
+
if 'definitions' in structure_schema:
|
|
652
|
+
self.process_definitions(structure_schema['definitions'], '')
|
|
653
|
+
|
|
654
|
+
self.generate_cmake_lists(self.base_namespace)
|
|
655
|
+
self.generate_build_scripts()
|
|
656
|
+
self.generate_vcpkg_json()
|
|
657
|
+
|
|
658
|
+
def process_definitions(self, definitions: Dict, namespace_path: str) -> None:
|
|
659
|
+
""" Processes the definitions section recursively """
|
|
660
|
+
for name, definition in definitions.items():
|
|
661
|
+
if isinstance(definition, dict):
|
|
662
|
+
if 'type' in definition or 'enum' in definition:
|
|
663
|
+
# This is a type definition
|
|
664
|
+
current_namespace = self.concat_namespace(namespace_path, '')
|
|
665
|
+
# Check if this type was already generated
|
|
666
|
+
check_namespace = self.concat_namespace(self.base_namespace, current_namespace).replace('.', '::')
|
|
667
|
+
check_name = pascal(name)
|
|
668
|
+
check_ref = self.get_qualified_name(check_namespace, check_name)
|
|
669
|
+
if check_ref not in self.generated_types_namespace:
|
|
670
|
+
self.generate_class_or_choice(definition, current_namespace, write_file=True, explicit_name=name)
|
|
671
|
+
else:
|
|
672
|
+
# This is a namespace
|
|
673
|
+
new_namespace = self.concat_namespace(namespace_path, name)
|
|
674
|
+
self.process_definitions(definition, new_namespace)
|
|
675
|
+
|
|
676
|
+
def convert(self, structure_schema_path: str, output_dir: str) -> None:
|
|
677
|
+
"""Converts JSON Structure schema to C++"""
|
|
678
|
+
with open(structure_schema_path, 'r', encoding='utf-8') as file:
|
|
679
|
+
schema = json.load(file)
|
|
680
|
+
self.convert_schema(schema, output_dir)
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def convert_structure_to_cpp(structure_schema_path: str, output_dir: str, namespace: str = '', json_annotation: bool = True) -> None:
|
|
684
|
+
"""Converts JSON Structure schema to C++ classes"""
|
|
685
|
+
|
|
686
|
+
if not namespace:
|
|
687
|
+
namespace = os.path.splitext(os.path.basename(structure_schema_path))[0].replace('-', '_')
|
|
688
|
+
|
|
689
|
+
structure_to_cpp = StructureToCpp(namespace)
|
|
690
|
+
structure_to_cpp.json_annotation = json_annotation
|
|
691
|
+
structure_to_cpp.convert(structure_schema_path, output_dir)
|
|
692
|
+
|
|
693
|
+
def convert_structure_schema_to_cpp(structure_schema: Dict, output_dir: str, namespace: str = '', json_annotation: bool = True) -> None:
|
|
694
|
+
"""Converts JSON Structure schema to C++ classes"""
|
|
695
|
+
structure_to_cpp = StructureToCpp(namespace)
|
|
696
|
+
structure_to_cpp.json_annotation = json_annotation
|
|
697
|
+
structure_to_cpp.convert_schema(structure_schema, output_dir)
|