structurize 3.7.3__tar.gz → 3.8.0__tar.gz
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.
- {structurize-3.7.3/structurize.egg-info → structurize-3.8.0}/PKG-INFO +1 -1
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/__init__.py +2 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/_version.py +3 -3
- structurize-3.8.0/avrotize/avrotoasn1.py +330 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotocsharp.py +11 -2
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotogo.py +15 -5
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/commands.json +107 -2
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/common.py +41 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/dependencies/typescript/node22/package.json +1 -1
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/jsonstostructure.py +169 -12
- structurize-3.8.0/avrotize/structuretoasn1.py +467 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretocsharp.py +11 -2
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretogo.py +15 -5
- {structurize-3.7.3 → structurize-3.8.0/structurize.egg-info}/PKG-INFO +1 -1
- {structurize-3.7.3 → structurize-3.8.0}/structurize.egg-info/SOURCES.txt +4 -0
- {structurize-3.7.3 → structurize-3.8.0}/.gitignore +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/LICENSE +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/MANIFEST.in +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/README.md +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/__main__.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/asn1toavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotize.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotocapnproto.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotocpp.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotocsv.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotocue.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotodatapackage.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotodb.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotoflatbuffers.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotographql.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotoiceberg.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotojava.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotojs.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotojsons.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotojstruct.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotojtd.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotokusto.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotomd.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotools.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotoparquet.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotoproto.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotopython.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotoraml.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotorust.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotosmithy.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotosurreal.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotothrift.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotots.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrototsml.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrotoxsd.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/avrovalidator.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/capnprototoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/cddltostructure.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/choice_inference.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/constants.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/csvtoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/cuetoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/cuetostructure.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/datapackagetoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/dependencies/cpp/vcpkg/vcpkg.json +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/dependency_resolver.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/dependency_version.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/flatbufferstoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/flatbufferstojstruct.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/jsonstoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/jsontoschema.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/jstructtoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/jstructtoflatbuffers.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/jstructtoraml.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/jstructtosmithy.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/jtdtoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/jtdtostructure.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/kstructtoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/kustotoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/kustotojstruct.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/mcp_server.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/openapitostructure.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/parquettoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/proto2parser.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/proto3parser.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/prototoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/ramltoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/ramltojstruct.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/schema_inference.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/smithytoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/smithytojstruct.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/sqltoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretocddl.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretocpp.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretocsv.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretocue.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretodatapackage.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretodb.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretographql.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretoiceberg.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretojava.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretojs.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretojsons.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretojtd.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretokusto.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretomd.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretoparquet.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretoproto.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretopython.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretorust.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretots.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretotsml.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/structuretoxsd.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/surrealtoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/thriftstructure.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/thrifttoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/tmslvalidate.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/validate.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/xmltoschema.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/avrotize/xsdtoavro.py +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/build.ps1 +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/build.sh +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/pyproject.toml +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/setup.cfg +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/structurize.egg-info/dependency_links.txt +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/structurize.egg-info/entry_points.txt +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/structurize.egg-info/requires.txt +0 -0
- {structurize-3.7.3 → structurize-3.8.0}/structurize.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structurize
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.0
|
|
4
4
|
Summary: Tools to convert from and to JSON Structure from various other schema languages.
|
|
5
5
|
Author-email: Clemens Vasters <clemensv@microsoft.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -51,6 +51,8 @@ _mappings = {
|
|
|
51
51
|
"convert_kafka_struct_to_avro_schema": (f"{mod}.kstructtoavro", "convert_kafka_struct_to_avro_schema"),
|
|
52
52
|
"convert_kusto_to_avro": (f"{mod}.kustotoavro", "convert_kusto_to_avro"),
|
|
53
53
|
"convert_asn1_to_avro": (f"{mod}.asn1toavro", "convert_asn1_to_avro"),
|
|
54
|
+
"convert_avro_to_asn1": (f"{mod}.avrotoasn1", "convert_avro_to_asn1"),
|
|
55
|
+
"convert_structure_to_asn1": (f"{mod}.structuretoasn1", "convert_structure_to_asn1"),
|
|
54
56
|
"convert_xsd_to_avro": (f"{mod}.xsdtoavro", "convert_xsd_to_avro"),
|
|
55
57
|
"convert_avro_to_json_schema": (f"{mod}.avrotojsons", "convert_avro_to_json_schema"),
|
|
56
58
|
"convert_avro_to_kusto_file": (f"{mod}.avrotokusto", "convert_avro_to_kusto_file"),
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '3.
|
|
22
|
-
__version_tuple__ = version_tuple = (3,
|
|
21
|
+
__version__ = version = '3.8.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (3, 8, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'geb0e0af04'
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# pylint: disable=line-too-long
|
|
2
|
+
|
|
3
|
+
""" AvroToASN1 class for converting Avrotize (Avro) schema to an ASN.1 module. """
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from typing import Dict, List, Union
|
|
9
|
+
|
|
10
|
+
from avrotize.common import is_generic_avro_type
|
|
11
|
+
|
|
12
|
+
JsonNode = Union[Dict, List, str, int, float, bool, None]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AvroToASN1:
|
|
16
|
+
""" Converts an Avrotize (Avro) schema to an ASN.1 (X.680) module. """
|
|
17
|
+
|
|
18
|
+
def __init__(self, module_name: str = 'AvrotizeSchema'):
|
|
19
|
+
self.module_name = self._to_type_reference(module_name)
|
|
20
|
+
# Ordered list of top-level "Name ::= ..." assignment strings.
|
|
21
|
+
self.assignments: List[str] = []
|
|
22
|
+
# ASN.1 type names already emitted (global uniqueness for typereferences).
|
|
23
|
+
self.known_types: Dict[str, str] = {}
|
|
24
|
+
# Avro full name -> ASN.1 type name.
|
|
25
|
+
self.type_name_map: Dict[str, str] = {}
|
|
26
|
+
self.used_type_names: set = set()
|
|
27
|
+
|
|
28
|
+
# ------------------------------------------------------------------ names
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def _sanitize(name: str) -> str:
|
|
32
|
+
"""Turn an arbitrary name into an ASN.1-legal token body (letters, digits, hyphens)."""
|
|
33
|
+
if not name:
|
|
34
|
+
return 'x'
|
|
35
|
+
# Keep only the last dotted segment for namespaced names.
|
|
36
|
+
name = name.split('.')[-1]
|
|
37
|
+
# Replace any run of illegal characters with a single hyphen.
|
|
38
|
+
name = re.sub(r'[^A-Za-z0-9]+', '-', name)
|
|
39
|
+
# Collapse consecutive hyphens and strip leading/trailing hyphens.
|
|
40
|
+
name = re.sub(r'-+', '-', name).strip('-')
|
|
41
|
+
if not name:
|
|
42
|
+
return 'x'
|
|
43
|
+
# ASN.1 tokens must start with a letter.
|
|
44
|
+
if not name[0].isalpha():
|
|
45
|
+
name = 'x-' + name
|
|
46
|
+
return name
|
|
47
|
+
|
|
48
|
+
def _to_type_reference(self, name: str) -> str:
|
|
49
|
+
"""ASN.1 typereference: must start with an upper-case letter."""
|
|
50
|
+
token = self._sanitize(name)
|
|
51
|
+
return token[0].upper() + token[1:]
|
|
52
|
+
|
|
53
|
+
def _to_identifier(self, name: str) -> str:
|
|
54
|
+
"""ASN.1 identifier (member / enum value): must start with a lower-case letter."""
|
|
55
|
+
token = self._sanitize(name)
|
|
56
|
+
letters = [c for c in token if c.isalpha()]
|
|
57
|
+
if letters and all(c.isupper() for c in letters):
|
|
58
|
+
# ALL-CAPS tokens (e.g. Avro enum symbols) read best fully lower-cased.
|
|
59
|
+
token = token.lower()
|
|
60
|
+
return token[0].lower() + token[1:]
|
|
61
|
+
|
|
62
|
+
def _unique_type_name(self, name: str) -> str:
|
|
63
|
+
"""Return a module-unique ASN.1 typereference for the given name."""
|
|
64
|
+
base = self._to_type_reference(name)
|
|
65
|
+
candidate = base
|
|
66
|
+
counter = 1
|
|
67
|
+
while candidate in self.used_type_names:
|
|
68
|
+
counter += 1
|
|
69
|
+
candidate = f"{base}{counter}"
|
|
70
|
+
self.used_type_names.add(candidate)
|
|
71
|
+
return candidate
|
|
72
|
+
|
|
73
|
+
# ------------------------------------------------------------- primitives
|
|
74
|
+
|
|
75
|
+
def _map_primitive(self, avro_type: Union[str, dict]) -> str:
|
|
76
|
+
"""Map an Avro primitive (or logical) type to an ASN.1 built-in type."""
|
|
77
|
+
if isinstance(avro_type, dict):
|
|
78
|
+
logical_type = avro_type.get('logicalType')
|
|
79
|
+
base = avro_type.get('type')
|
|
80
|
+
if logical_type == 'date':
|
|
81
|
+
return 'DATE'
|
|
82
|
+
if logical_type in ('timestamp-millis', 'timestamp-micros',
|
|
83
|
+
'local-timestamp-millis', 'local-timestamp-micros'):
|
|
84
|
+
return 'DATE-TIME'
|
|
85
|
+
if logical_type in ('time-millis', 'time-micros'):
|
|
86
|
+
return 'TIME-OF-DAY'
|
|
87
|
+
if logical_type == 'decimal':
|
|
88
|
+
return 'REAL'
|
|
89
|
+
if logical_type == 'uuid':
|
|
90
|
+
return 'UTF8String'
|
|
91
|
+
if logical_type == 'duration':
|
|
92
|
+
return 'UTF8String'
|
|
93
|
+
return self._map_primitive(base)
|
|
94
|
+
|
|
95
|
+
mapping = {
|
|
96
|
+
'null': 'NULL',
|
|
97
|
+
'boolean': 'BOOLEAN',
|
|
98
|
+
'int': 'INTEGER',
|
|
99
|
+
'long': 'INTEGER',
|
|
100
|
+
'float': 'REAL',
|
|
101
|
+
'double': 'REAL',
|
|
102
|
+
'bytes': 'OCTET STRING',
|
|
103
|
+
'string': 'UTF8String',
|
|
104
|
+
}
|
|
105
|
+
return mapping.get(avro_type, '')
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _is_primitive(avro_type: Union[str, dict]) -> bool:
|
|
109
|
+
if isinstance(avro_type, dict):
|
|
110
|
+
return 'logicalType' in avro_type or avro_type.get('type') in {
|
|
111
|
+
'null', 'boolean', 'int', 'long', 'float', 'double', 'bytes', 'string'}
|
|
112
|
+
return avro_type in {'null', 'boolean', 'int', 'long', 'float', 'double', 'bytes', 'string'}
|
|
113
|
+
|
|
114
|
+
# ------------------------------------------------------------- expressions
|
|
115
|
+
|
|
116
|
+
def type_expression(self, avro_type: JsonNode) -> str:
|
|
117
|
+
"""Return an ASN.1 type expression for an Avro type (defining named types as a side effect)."""
|
|
118
|
+
if isinstance(avro_type, str):
|
|
119
|
+
if self._is_primitive(avro_type):
|
|
120
|
+
return self._map_primitive(avro_type)
|
|
121
|
+
# Reference to a previously defined named type.
|
|
122
|
+
return self.type_name_map.get(avro_type, self._to_type_reference(avro_type))
|
|
123
|
+
|
|
124
|
+
if isinstance(avro_type, list):
|
|
125
|
+
return self._union_expression(avro_type)
|
|
126
|
+
|
|
127
|
+
if isinstance(avro_type, dict):
|
|
128
|
+
if 'logicalType' in avro_type:
|
|
129
|
+
return self._map_primitive(avro_type)
|
|
130
|
+
kind = avro_type.get('type')
|
|
131
|
+
if kind == 'record' or kind == 'error':
|
|
132
|
+
return self._define_record(avro_type)
|
|
133
|
+
if kind == 'enum':
|
|
134
|
+
return self._define_enum(avro_type)
|
|
135
|
+
if kind == 'fixed':
|
|
136
|
+
return self._define_fixed(avro_type)
|
|
137
|
+
if kind == 'array':
|
|
138
|
+
items = avro_type.get('items', 'string')
|
|
139
|
+
return f"SEQUENCE OF {self.type_expression(items)}"
|
|
140
|
+
if kind == 'map':
|
|
141
|
+
values = avro_type.get('values', 'string')
|
|
142
|
+
return f"SEQUENCE OF SEQUENCE {{ key UTF8String, value {self.type_expression(values)} }}"
|
|
143
|
+
if self._is_primitive(avro_type):
|
|
144
|
+
return self._map_primitive(avro_type)
|
|
145
|
+
if isinstance(kind, (dict, list)):
|
|
146
|
+
return self.type_expression(kind)
|
|
147
|
+
if isinstance(kind, str):
|
|
148
|
+
return self.type_name_map.get(kind, self._to_type_reference(kind))
|
|
149
|
+
return 'UTF8String'
|
|
150
|
+
|
|
151
|
+
def _union_expression(self, union: List[JsonNode]) -> str:
|
|
152
|
+
"""Map an Avro union to an ASN.1 type expression (nullability is handled by the caller)."""
|
|
153
|
+
if is_generic_avro_type(union):
|
|
154
|
+
return 'ANY'
|
|
155
|
+
non_null = [t for t in union if t != 'null']
|
|
156
|
+
if len(non_null) == 0:
|
|
157
|
+
return 'NULL'
|
|
158
|
+
if len(non_null) == 1:
|
|
159
|
+
return self.type_expression(non_null[0])
|
|
160
|
+
members = []
|
|
161
|
+
used: set = set()
|
|
162
|
+
for i, branch in enumerate(non_null):
|
|
163
|
+
alt = self._alternative_name(branch, i, used)
|
|
164
|
+
members.append(f"{alt} {self.type_expression(branch)}")
|
|
165
|
+
return "CHOICE { " + ", ".join(members) + " }"
|
|
166
|
+
|
|
167
|
+
def _alternative_name(self, branch: JsonNode, index: int, used: set) -> str:
|
|
168
|
+
"""Derive a unique ASN.1 CHOICE alternative identifier from a union branch."""
|
|
169
|
+
if isinstance(branch, str):
|
|
170
|
+
base = self._to_identifier(branch)
|
|
171
|
+
elif isinstance(branch, dict):
|
|
172
|
+
if 'name' in branch:
|
|
173
|
+
base = self._to_identifier(branch['name'])
|
|
174
|
+
else:
|
|
175
|
+
base = self._to_identifier(str(branch.get('type', f'choice{index}')))
|
|
176
|
+
else:
|
|
177
|
+
base = f'choice{index}'
|
|
178
|
+
candidate = base
|
|
179
|
+
counter = 1
|
|
180
|
+
while candidate in used:
|
|
181
|
+
counter += 1
|
|
182
|
+
candidate = f"{base}{counter}"
|
|
183
|
+
used.add(candidate)
|
|
184
|
+
return candidate
|
|
185
|
+
|
|
186
|
+
# --------------------------------------------------------------- defining
|
|
187
|
+
|
|
188
|
+
def _define_record(self, record: dict) -> str:
|
|
189
|
+
"""Emit an ASN.1 SEQUENCE assignment for an Avro record, returning its type name."""
|
|
190
|
+
full_name = self._full_name(record)
|
|
191
|
+
if full_name in self.type_name_map:
|
|
192
|
+
return self.type_name_map[full_name]
|
|
193
|
+
|
|
194
|
+
type_name = self._unique_type_name(record.get('name', 'Record'))
|
|
195
|
+
# Register before recursing so self-references resolve.
|
|
196
|
+
self.type_name_map[full_name] = type_name
|
|
197
|
+
|
|
198
|
+
fields = record.get('fields', [])
|
|
199
|
+
used_members: set = set()
|
|
200
|
+
members = [(self._member_definition(field, used_members), field.get('doc'))
|
|
201
|
+
for field in fields]
|
|
202
|
+
|
|
203
|
+
if members:
|
|
204
|
+
lines: List[str] = []
|
|
205
|
+
for i, (core, doc) in enumerate(members):
|
|
206
|
+
comma = "," if i < len(members) - 1 else ""
|
|
207
|
+
if doc:
|
|
208
|
+
lines.append(f" -- {self._comment(doc)}")
|
|
209
|
+
lines.append(f" {core}{comma}")
|
|
210
|
+
body = "SEQUENCE {\n" + "\n".join(lines) + "\n}"
|
|
211
|
+
else:
|
|
212
|
+
body = "SEQUENCE {}"
|
|
213
|
+
self._append_assignment(type_name, body, record.get('doc'))
|
|
214
|
+
return type_name
|
|
215
|
+
|
|
216
|
+
def _member_definition(self, field: dict, used_members: set) -> str:
|
|
217
|
+
"""Build a single ASN.1 SEQUENCE member (without comment) from an Avro field."""
|
|
218
|
+
name = self._unique_identifier(field['name'], used_members)
|
|
219
|
+
field_type = field['type']
|
|
220
|
+
optional = isinstance(field_type, list) and 'null' in field_type
|
|
221
|
+
expr = self.type_expression(field_type)
|
|
222
|
+
member = f"{name} {expr}"
|
|
223
|
+
if optional:
|
|
224
|
+
member += " OPTIONAL"
|
|
225
|
+
return member
|
|
226
|
+
|
|
227
|
+
def _define_enum(self, enum: dict) -> str:
|
|
228
|
+
"""Emit an ASN.1 ENUMERATED assignment for an Avro enum, returning its type name."""
|
|
229
|
+
full_name = self._full_name(enum)
|
|
230
|
+
if full_name in self.type_name_map:
|
|
231
|
+
return self.type_name_map[full_name]
|
|
232
|
+
|
|
233
|
+
type_name = self._unique_type_name(enum.get('name', 'Enum'))
|
|
234
|
+
self.type_name_map[full_name] = type_name
|
|
235
|
+
|
|
236
|
+
values: List[str] = []
|
|
237
|
+
used_symbols: set = set()
|
|
238
|
+
for i, symbol in enumerate(enum.get('symbols', [])):
|
|
239
|
+
sym = self._unique_identifier(symbol, used_symbols)
|
|
240
|
+
values.append(f"{sym}({i})")
|
|
241
|
+
body = "ENUMERATED { " + ", ".join(values) + " }" if values else "ENUMERATED { unspecified(0) }"
|
|
242
|
+
self._append_assignment(type_name, body, enum.get('doc'))
|
|
243
|
+
return type_name
|
|
244
|
+
|
|
245
|
+
def _define_fixed(self, fixed: dict) -> str:
|
|
246
|
+
"""Emit an ASN.1 OCTET STRING (SIZE(n)) assignment for an Avro fixed type."""
|
|
247
|
+
full_name = self._full_name(fixed)
|
|
248
|
+
if full_name in self.type_name_map:
|
|
249
|
+
return self.type_name_map[full_name]
|
|
250
|
+
|
|
251
|
+
type_name = self._unique_type_name(fixed.get('name', 'Fixed'))
|
|
252
|
+
self.type_name_map[full_name] = type_name
|
|
253
|
+
size = fixed.get('size', 0)
|
|
254
|
+
self._append_assignment(type_name, f"OCTET STRING (SIZE({size}))", fixed.get('doc'))
|
|
255
|
+
return type_name
|
|
256
|
+
|
|
257
|
+
# ------------------------------------------------------------- utilities
|
|
258
|
+
|
|
259
|
+
def _unique_identifier(self, name: str, used: set) -> str:
|
|
260
|
+
base = self._to_identifier(name)
|
|
261
|
+
candidate = base
|
|
262
|
+
counter = 1
|
|
263
|
+
while candidate in used:
|
|
264
|
+
counter += 1
|
|
265
|
+
candidate = f"{base}-{counter}"
|
|
266
|
+
used.add(candidate)
|
|
267
|
+
return candidate
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
def _full_name(schema: dict) -> str:
|
|
271
|
+
name = schema.get('name', '')
|
|
272
|
+
namespace = schema.get('namespace', '')
|
|
273
|
+
return f"{namespace}.{name}" if namespace else name
|
|
274
|
+
|
|
275
|
+
@staticmethod
|
|
276
|
+
def _comment(text: str) -> str:
|
|
277
|
+
"""ASN.1 line comment body: no line breaks, no double-hyphen terminator."""
|
|
278
|
+
return re.sub(r'\s+', ' ', str(text)).replace('--', '- -').strip()
|
|
279
|
+
|
|
280
|
+
def _append_assignment(self, type_name: str, body: str, doc: str = None) -> None:
|
|
281
|
+
assignment = ''
|
|
282
|
+
if doc:
|
|
283
|
+
assignment += f"-- {self._comment(doc)}\n"
|
|
284
|
+
assignment += f"{type_name} ::= {body}"
|
|
285
|
+
self.assignments.append(assignment)
|
|
286
|
+
self.known_types[type_name] = body
|
|
287
|
+
|
|
288
|
+
# ----------------------------------------------------------------- render
|
|
289
|
+
|
|
290
|
+
def convert(self, avro_schema: JsonNode) -> str:
|
|
291
|
+
"""Convert an Avro schema (dict or list of named types) to an ASN.1 module string."""
|
|
292
|
+
schemas = avro_schema if isinstance(avro_schema, list) else [avro_schema]
|
|
293
|
+
for schema in schemas:
|
|
294
|
+
if not isinstance(schema, dict):
|
|
295
|
+
continue
|
|
296
|
+
kind = schema.get('type')
|
|
297
|
+
if kind in ('record', 'error'):
|
|
298
|
+
self._define_record(schema)
|
|
299
|
+
elif kind == 'enum':
|
|
300
|
+
self._define_enum(schema)
|
|
301
|
+
elif kind == 'fixed':
|
|
302
|
+
self._define_fixed(schema)
|
|
303
|
+
return self.render()
|
|
304
|
+
|
|
305
|
+
def render(self) -> str:
|
|
306
|
+
"""Render the accumulated assignments as a complete ASN.1 module."""
|
|
307
|
+
lines = [f"{self.module_name} DEFINITIONS AUTOMATIC TAGS ::= BEGIN", ""]
|
|
308
|
+
for assignment in self.assignments:
|
|
309
|
+
lines.append(assignment)
|
|
310
|
+
lines.append("")
|
|
311
|
+
lines.append("END")
|
|
312
|
+
return "\n".join(lines) + "\n"
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def convert_avro_to_asn1(avro_schema_path: str, asn1_file_path: str, module_name: str = '') -> None:
|
|
316
|
+
"""Convert an Avrotize schema file to an ASN.1 module file."""
|
|
317
|
+
with open(avro_schema_path, 'r', encoding='utf-8') as avro_file:
|
|
318
|
+
avro_schema = json.load(avro_file)
|
|
319
|
+
|
|
320
|
+
if not module_name:
|
|
321
|
+
module_name = os.path.splitext(os.path.basename(asn1_file_path))[0] or 'AvrotizeSchema'
|
|
322
|
+
|
|
323
|
+
converter = AvroToASN1(module_name)
|
|
324
|
+
asn1_text = converter.convert(avro_schema)
|
|
325
|
+
|
|
326
|
+
out_dir = os.path.dirname(asn1_file_path)
|
|
327
|
+
if out_dir and not os.path.exists(out_dir):
|
|
328
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
329
|
+
with open(asn1_file_path, 'w', encoding='utf-8') as asn1_file:
|
|
330
|
+
asn1_file.write(asn1_text)
|
|
@@ -58,6 +58,7 @@ class AvroToCSharp:
|
|
|
58
58
|
self.avro_annotation = False
|
|
59
59
|
self.protobuf_net_annotation = False
|
|
60
60
|
self.openapi_generator_compat = False
|
|
61
|
+
self.target_framework = ''
|
|
61
62
|
self.generated_types: Dict[str,str] = {}
|
|
62
63
|
self.generated_avro_types: Dict[str, Dict[str, Union[str, Dict, List]]] = {}
|
|
63
64
|
self.type_dict: Dict[str, Dict] = {}
|
|
@@ -1189,6 +1190,7 @@ class AvroToCSharp:
|
|
|
1189
1190
|
file.write(process_template(
|
|
1190
1191
|
"avrotocsharp/project.csproj.jinja",
|
|
1191
1192
|
project_name=project_name,
|
|
1193
|
+
target_framework=self.target_framework,
|
|
1192
1194
|
avro_annotation=self.avro_annotation,
|
|
1193
1195
|
system_xml_annotation=self.system_xml_annotation,
|
|
1194
1196
|
system_text_json_annotation=self.system_text_json_annotation,
|
|
@@ -1216,6 +1218,7 @@ class AvroToCSharp:
|
|
|
1216
1218
|
file.write(process_template(
|
|
1217
1219
|
"avrotocsharp/testproject.csproj.jinja",
|
|
1218
1220
|
project_name=project_name,
|
|
1221
|
+
target_framework=self.target_framework,
|
|
1219
1222
|
avro_annotation=self.avro_annotation,
|
|
1220
1223
|
system_xml_annotation=self.system_xml_annotation,
|
|
1221
1224
|
system_text_json_annotation=self.system_text_json_annotation,
|
|
@@ -1279,7 +1282,8 @@ def convert_avro_to_csharp(
|
|
|
1279
1282
|
cbor_annotation=False,
|
|
1280
1283
|
avro_annotation=False,
|
|
1281
1284
|
protobuf_net_annotation=False,
|
|
1282
|
-
openapi_generator_compat=False
|
|
1285
|
+
openapi_generator_compat=False,
|
|
1286
|
+
target_framework=''
|
|
1283
1287
|
):
|
|
1284
1288
|
"""Converts Avro schema to C# classes
|
|
1285
1289
|
|
|
@@ -1297,6 +1301,7 @@ def convert_avro_to_csharp(
|
|
|
1297
1301
|
avro_annotation (bool, optional): Use Avro annotations. Defaults to False.
|
|
1298
1302
|
protobuf_net_annotation (bool, optional): Use protobuf-net annotations. Defaults to False.
|
|
1299
1303
|
openapi_generator_compat (bool, optional): Generate OpenAPI Generator-compatible C# models. Defaults to False.
|
|
1304
|
+
target_framework (str, optional): Target framework moniker written into the generated .csproj (e.g. 'net8.0'). A ';'-separated value emits <TargetFrameworks> for multi-targeting. Defaults to '' (net10.0).
|
|
1300
1305
|
"""
|
|
1301
1306
|
|
|
1302
1307
|
if not base_namespace:
|
|
@@ -1312,6 +1317,7 @@ def convert_avro_to_csharp(
|
|
|
1312
1317
|
avrotocs.avro_annotation = avro_annotation
|
|
1313
1318
|
avrotocs.protobuf_net_annotation = protobuf_net_annotation
|
|
1314
1319
|
avrotocs.openapi_generator_compat = openapi_generator_compat
|
|
1320
|
+
avrotocs.target_framework = target_framework
|
|
1315
1321
|
avrotocs.convert(avro_schema_path, cs_file_path)
|
|
1316
1322
|
|
|
1317
1323
|
|
|
@@ -1328,7 +1334,8 @@ def convert_avro_schema_to_csharp(
|
|
|
1328
1334
|
cbor_annotation: bool = False,
|
|
1329
1335
|
avro_annotation: bool = False,
|
|
1330
1336
|
protobuf_net_annotation: bool = False,
|
|
1331
|
-
openapi_generator_compat: bool = False
|
|
1337
|
+
openapi_generator_compat: bool = False,
|
|
1338
|
+
target_framework: str = ''
|
|
1332
1339
|
):
|
|
1333
1340
|
"""Converts Avro schema to C# classes
|
|
1334
1341
|
|
|
@@ -1345,6 +1352,7 @@ def convert_avro_schema_to_csharp(
|
|
|
1345
1352
|
cbor_annotation (bool, optional): Use Dahomey.Cbor annotations. Defaults to False.
|
|
1346
1353
|
avro_annotation (bool, optional): Use Avro annotations. Defaults to False.
|
|
1347
1354
|
protobuf_net_annotation (bool, optional): Use protobuf-net annotations. Defaults to False.
|
|
1355
|
+
target_framework (str, optional): Target framework moniker written into the generated .csproj (e.g. 'net8.0'). A ';'-separated value emits <TargetFrameworks> for multi-targeting. Defaults to '' (net10.0).
|
|
1348
1356
|
"""
|
|
1349
1357
|
avrotocs = AvroToCSharp(base_namespace)
|
|
1350
1358
|
avrotocs.project_name = project_name
|
|
@@ -1357,4 +1365,5 @@ def convert_avro_schema_to_csharp(
|
|
|
1357
1365
|
avrotocs.avro_annotation = avro_annotation
|
|
1358
1366
|
avrotocs.protobuf_net_annotation = protobuf_net_annotation
|
|
1359
1367
|
avrotocs.openapi_generator_compat = openapi_generator_compat
|
|
1368
|
+
avrotocs.target_framework = target_framework
|
|
1360
1369
|
avrotocs.convert_schema(avro_schema, output_dir)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
from typing import Dict, List, Union, Set
|
|
4
|
-
from avrotize.common import get_longest_namespace_prefix, is_generic_avro_type, is_any_value_type, pascal, render_template
|
|
4
|
+
from avrotize.common import get_longest_namespace_prefix, is_generic_avro_type, is_any_value_type, pascal, render_template, format_go_files
|
|
5
5
|
|
|
6
6
|
INDENT = ' '
|
|
7
7
|
|
|
@@ -33,10 +33,19 @@ class AvroToGo:
|
|
|
33
33
|
self.enums = []
|
|
34
34
|
|
|
35
35
|
def _safe_package_name(self, name: str) -> str:
|
|
36
|
-
"""Converts a name to a safe Go package name
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
"""Converts a name to a safe Go package name (a valid lowercase identifier).
|
|
37
|
+
|
|
38
|
+
Sanitizes any character that is not valid in a Go identifier (dots,
|
|
39
|
+
hyphens from file names, etc.) to underscores so the emitted ``package``
|
|
40
|
+
clause is compilable and gofmt-parseable.
|
|
41
|
+
"""
|
|
42
|
+
import re
|
|
43
|
+
safe = re.sub(r'[^a-zA-Z0-9_]+', '_', name).strip('_').lower()
|
|
44
|
+
if not safe or not re.match(r'^[a-z]', safe):
|
|
45
|
+
safe = f"pkg_{safe}" if safe else "pkg"
|
|
46
|
+
if safe in self.GO_RESERVED_WORDS:
|
|
47
|
+
return f"{safe}_"
|
|
48
|
+
return safe
|
|
40
49
|
|
|
41
50
|
def safe_identifier(self, name: str) -> str:
|
|
42
51
|
"""Converts a name to a safe Go identifier"""
|
|
@@ -418,6 +427,7 @@ class AvroToGo:
|
|
|
418
427
|
self.write_go_mod_file()
|
|
419
428
|
self.write_modname_go_file()
|
|
420
429
|
self.generate_helpers()
|
|
430
|
+
format_go_files(self.output_dir)
|
|
421
431
|
|
|
422
432
|
def write_go_mod_file(self):
|
|
423
433
|
"""Writes the go.mod file for the Go project"""
|
|
@@ -1,4 +1,95 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"command": "a2asn",
|
|
4
|
+
"description": "Convert Avrotize schema to ASN.1 schema",
|
|
5
|
+
"group": "1_Schemas",
|
|
6
|
+
"function": {
|
|
7
|
+
"name": "avrotize.avrotoasn1.convert_avro_to_asn1",
|
|
8
|
+
"args": {
|
|
9
|
+
"avro_schema_path": "input_file_path",
|
|
10
|
+
"asn1_file_path": "output_file_path",
|
|
11
|
+
"module_name": "args.module"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"extensions": [
|
|
15
|
+
".avsc"
|
|
16
|
+
],
|
|
17
|
+
"args": [
|
|
18
|
+
{
|
|
19
|
+
"name": "input",
|
|
20
|
+
"type": "str",
|
|
21
|
+
"nargs": "?",
|
|
22
|
+
"help": "Path to the Avrotize schema file (or read from stdin if omitted)",
|
|
23
|
+
"required": false
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "--out",
|
|
27
|
+
"type": "str",
|
|
28
|
+
"help": "Path to the ASN.1 schema file",
|
|
29
|
+
"required": false
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "--avsc",
|
|
33
|
+
"type": "str",
|
|
34
|
+
"help": "Deprecated: Path to the Avrotize schema file (for backcompat)",
|
|
35
|
+
"required": false
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "--module",
|
|
39
|
+
"type": "str",
|
|
40
|
+
"help": "ASN.1 module name (defaults to the output file name)",
|
|
41
|
+
"required": false
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"suggested_output_file_path": "{input_file_name}.asn1",
|
|
45
|
+
"prompts": []
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"command": "s2asn",
|
|
49
|
+
"description": "Convert JSON Structure schema to ASN.1 schema",
|
|
50
|
+
"group": "1_Schemas",
|
|
51
|
+
"function": {
|
|
52
|
+
"name": "avrotize.structuretoasn1.convert_structure_to_asn1",
|
|
53
|
+
"args": {
|
|
54
|
+
"structure_schema_path": "input_file_path",
|
|
55
|
+
"asn1_file_path": "output_file_path",
|
|
56
|
+
"module_name": "args.module"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"extensions": [
|
|
60
|
+
".struct.json",
|
|
61
|
+
".json"
|
|
62
|
+
],
|
|
63
|
+
"args": [
|
|
64
|
+
{
|
|
65
|
+
"name": "input",
|
|
66
|
+
"type": "str",
|
|
67
|
+
"nargs": "?",
|
|
68
|
+
"help": "Path to the JSON Structure schema file (or read from stdin if omitted)",
|
|
69
|
+
"required": false
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "--out",
|
|
73
|
+
"type": "str",
|
|
74
|
+
"help": "Path to the ASN.1 schema file",
|
|
75
|
+
"required": false
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "--struct",
|
|
79
|
+
"type": "str",
|
|
80
|
+
"help": "Deprecated: Path to the JSON Structure schema file (for backcompat)",
|
|
81
|
+
"required": false
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "--module",
|
|
85
|
+
"type": "str",
|
|
86
|
+
"help": "ASN.1 module name (defaults to the output file name)",
|
|
87
|
+
"required": false
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"suggested_output_file_path": "{input_file_name}.asn1",
|
|
91
|
+
"prompts": []
|
|
92
|
+
},
|
|
2
93
|
{
|
|
3
94
|
"command": "p2a",
|
|
4
95
|
"description": "Convert Proto schema to Avrotize schema",
|
|
@@ -2575,7 +2666,8 @@
|
|
|
2575
2666
|
"cbor_annotation": "args.cbor_annotation",
|
|
2576
2667
|
"pascal_properties": "args.pascal_properties",
|
|
2577
2668
|
"base_namespace": "args.namespace",
|
|
2578
|
-
"openapi_generator_compat": "args.openapi_generator_compat"
|
|
2669
|
+
"openapi_generator_compat": "args.openapi_generator_compat",
|
|
2670
|
+
"target_framework": "args.target_framework"
|
|
2579
2671
|
}
|
|
2580
2672
|
},
|
|
2581
2673
|
"extensions": [
|
|
@@ -2669,6 +2761,12 @@
|
|
|
2669
2761
|
"help": "Generate OpenAPI Generator-compatible C# models with Option<T> optional properties and System.Text.Json converters",
|
|
2670
2762
|
"default": false,
|
|
2671
2763
|
"required": false
|
|
2764
|
+
},
|
|
2765
|
+
{
|
|
2766
|
+
"name": "--target-framework",
|
|
2767
|
+
"type": "str",
|
|
2768
|
+
"help": "Target framework moniker written into the generated .csproj (e.g. net8.0). A ';'-separated value emits <TargetFrameworks> for multi-targeting. Defaults to net10.0.",
|
|
2769
|
+
"required": false
|
|
2672
2770
|
}
|
|
2673
2771
|
],
|
|
2674
2772
|
"suggested_output_file_path": "{input_file_name}-cs",
|
|
@@ -2719,7 +2817,8 @@
|
|
|
2719
2817
|
"pascal_properties": "args.pascal_properties",
|
|
2720
2818
|
"base_namespace": "args.namespace",
|
|
2721
2819
|
"project_name": "args.project_name",
|
|
2722
|
-
"openapi_generator_compat": "args.openapi_generator_compat"
|
|
2820
|
+
"openapi_generator_compat": "args.openapi_generator_compat",
|
|
2821
|
+
"target_framework": "args.target_framework"
|
|
2723
2822
|
}
|
|
2724
2823
|
},
|
|
2725
2824
|
"extensions": [
|
|
@@ -2799,6 +2898,12 @@
|
|
|
2799
2898
|
"help": "Generate OpenAPI Generator-compatible C# models with Option<T> optional properties and System.Text.Json converters",
|
|
2800
2899
|
"default": false,
|
|
2801
2900
|
"required": false
|
|
2901
|
+
},
|
|
2902
|
+
{
|
|
2903
|
+
"name": "--target-framework",
|
|
2904
|
+
"type": "str",
|
|
2905
|
+
"help": "Target framework moniker written into the generated .csproj (e.g. net8.0). A ';'-separated value emits <TargetFrameworks> for multi-targeting. Defaults to net10.0.",
|
|
2906
|
+
"required": false
|
|
2802
2907
|
}
|
|
2803
2908
|
],
|
|
2804
2909
|
"suggested_output_file_path": "{input_file_name}-cs",
|
|
@@ -940,6 +940,47 @@ def render_template(template: str, output: str, **kvargs):
|
|
|
940
940
|
f.write(out)
|
|
941
941
|
|
|
942
942
|
|
|
943
|
+
def format_go_files(directory: str) -> bool:
|
|
944
|
+
"""Run ``gofmt`` over every ``.go`` file under ``directory`` to produce
|
|
945
|
+
canonical, gofmt-clean output.
|
|
946
|
+
|
|
947
|
+
This normalizes indentation (tabs), sorts imports, aligns struct fields and
|
|
948
|
+
``const`` blocks, fixes blank-line placement, and guarantees a trailing
|
|
949
|
+
newline at EOF -- everything the Go emitters cannot easily express in Jinja
|
|
950
|
+
templates. Struct-tag spacing is fixed in the templates instead, because
|
|
951
|
+
``gofmt`` never rewrites the contents of raw string literals.
|
|
952
|
+
|
|
953
|
+
Best-effort: if the Go toolchain is not installed, the generated files are
|
|
954
|
+
left untouched and ``False`` is returned (the emitter still produced valid,
|
|
955
|
+
compilable Go).
|
|
956
|
+
|
|
957
|
+
Args:
|
|
958
|
+
directory (str): Directory containing generated ``.go`` files. ``gofmt``
|
|
959
|
+
processes it recursively.
|
|
960
|
+
|
|
961
|
+
Returns:
|
|
962
|
+
bool: ``True`` if ``gofmt`` (or ``go fmt``) ran, ``False`` otherwise.
|
|
963
|
+
"""
|
|
964
|
+
import shutil
|
|
965
|
+
import subprocess
|
|
966
|
+
gofmt = shutil.which('gofmt')
|
|
967
|
+
try:
|
|
968
|
+
if gofmt:
|
|
969
|
+
subprocess.run(
|
|
970
|
+
[gofmt, '-w', directory], check=False,
|
|
971
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
972
|
+
return True
|
|
973
|
+
go = shutil.which('go')
|
|
974
|
+
if go:
|
|
975
|
+
subprocess.run(
|
|
976
|
+
[go, 'fmt', './...'], cwd=directory, check=False,
|
|
977
|
+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
978
|
+
return True
|
|
979
|
+
except OSError:
|
|
980
|
+
return False
|
|
981
|
+
return False
|
|
982
|
+
|
|
983
|
+
|
|
943
984
|
def get_longest_namespace_prefix(schema):
|
|
944
985
|
""" Get the longest common prefix for the namespace of all types in the schema. """
|
|
945
986
|
namespaces = set(collect_namespaces(schema))
|