vss-tools 0.5.0.dev0__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.
- vspec/__init__.py +996 -0
- vspec/loggingconfig.py +22 -0
- vspec/model/__init__.py +7 -0
- vspec/model/constants.py +307 -0
- vspec/model/exceptions.py +29 -0
- vspec/model/vsstree.py +602 -0
- vspec/py.typed +0 -0
- vspec/utils/__init__.py +23 -0
- vspec/utils/idgen_utils.py +97 -0
- vspec/utils/stringstyle.py +25 -0
- vspec/utils/vss2id_val.py +165 -0
- vspec/vspec2vss_config.py +53 -0
- vspec/vspec2x.py +191 -0
- vspec/vss2x.py +38 -0
- vspec/vssexporters/__init__.py +7 -0
- vspec/vssexporters/vss2binary.py +147 -0
- vspec/vssexporters/vss2csv.py +82 -0
- vspec/vssexporters/vss2ddsidl.py +295 -0
- vspec/vssexporters/vss2franca.py +98 -0
- vspec/vssexporters/vss2graphql.py +143 -0
- vspec/vssexporters/vss2id.py +172 -0
- vspec/vssexporters/vss2json.py +116 -0
- vspec/vssexporters/vss2jsonschema.py +159 -0
- vspec/vssexporters/vss2protobuf.py +171 -0
- vspec/vssexporters/vss2yaml.py +122 -0
- vss_tools-0.5.0.dev0.data/scripts/vspec2csv.py +29 -0
- vss_tools-0.5.0.dev0.data/scripts/vspec2ddsidl.py +29 -0
- vss_tools-0.5.0.dev0.data/scripts/vspec2franca.py +29 -0
- vss_tools-0.5.0.dev0.data/scripts/vspec2graphql.py +29 -0
- vss_tools-0.5.0.dev0.data/scripts/vspec2id.py +28 -0
- vss_tools-0.5.0.dev0.data/scripts/vspec2json.py +29 -0
- vss_tools-0.5.0.dev0.data/scripts/vspec2jsonschema.py +29 -0
- vss_tools-0.5.0.dev0.data/scripts/vspec2protobuf.py +29 -0
- vss_tools-0.5.0.dev0.data/scripts/vspec2yaml.py +29 -0
- vss_tools-0.5.0.dev0.dist-info/LICENSE +373 -0
- vss_tools-0.5.0.dev0.dist-info/METADATA +65 -0
- vss_tools-0.5.0.dev0.dist-info/RECORD +39 -0
- vss_tools-0.5.0.dev0.dist-info/WHEEL +5 -0
- vss_tools-0.5.0.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2022 Contributors to COVESA
|
|
4
|
+
#
|
|
5
|
+
# This program and the accompanying materials are made available under the
|
|
6
|
+
# terms of the Mozilla Public License 2.0 which is available at
|
|
7
|
+
# https://www.mozilla.org/en-US/MPL/2.0/
|
|
8
|
+
#
|
|
9
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
from vspec.model.vsstree import VSSNode
|
|
13
|
+
from vspec.utils.stringstyle import camel_back
|
|
14
|
+
from vspec.model.constants import VSSDataType
|
|
15
|
+
from vspec import VSpecError
|
|
16
|
+
from typing import Dict
|
|
17
|
+
from typing import Optional
|
|
18
|
+
from vspec.vss2x import Vss2X
|
|
19
|
+
from vspec.vspec2vss_config import Vspec2VssConfig
|
|
20
|
+
|
|
21
|
+
from graphql import (
|
|
22
|
+
GraphQLSchema,
|
|
23
|
+
GraphQLObjectType,
|
|
24
|
+
GraphQLField,
|
|
25
|
+
GraphQLArgument,
|
|
26
|
+
GraphQLNonNull,
|
|
27
|
+
GraphQLString,
|
|
28
|
+
GraphQLList,
|
|
29
|
+
print_schema,
|
|
30
|
+
GraphQLInt,
|
|
31
|
+
GraphQLFloat,
|
|
32
|
+
GraphQLBoolean,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
GRAPHQL_TYPE_MAPPING = {
|
|
36
|
+
VSSDataType.INT8: GraphQLInt,
|
|
37
|
+
VSSDataType.INT8_ARRAY: GraphQLList(GraphQLInt),
|
|
38
|
+
VSSDataType.UINT8: GraphQLInt,
|
|
39
|
+
VSSDataType.UINT8_ARRAY: GraphQLList(GraphQLInt),
|
|
40
|
+
VSSDataType.INT16: GraphQLInt,
|
|
41
|
+
VSSDataType.INT16_ARRAY: GraphQLList(GraphQLInt),
|
|
42
|
+
VSSDataType.UINT16: GraphQLInt,
|
|
43
|
+
VSSDataType.UINT16_ARRAY: GraphQLList(GraphQLInt),
|
|
44
|
+
VSSDataType.INT32: GraphQLInt,
|
|
45
|
+
VSSDataType.INT32_ARRAY: GraphQLList(GraphQLInt),
|
|
46
|
+
VSSDataType.UINT32: GraphQLFloat,
|
|
47
|
+
VSSDataType.UINT32_ARRAY: GraphQLList(GraphQLFloat),
|
|
48
|
+
VSSDataType.INT64: GraphQLFloat,
|
|
49
|
+
VSSDataType.INT64_ARRAY: GraphQLList(GraphQLFloat),
|
|
50
|
+
VSSDataType.UINT64: GraphQLFloat,
|
|
51
|
+
VSSDataType.UINT64_ARRAY: GraphQLList(GraphQLFloat),
|
|
52
|
+
VSSDataType.FLOAT: GraphQLFloat,
|
|
53
|
+
VSSDataType.FLOAT_ARRAY: GraphQLList(GraphQLFloat),
|
|
54
|
+
VSSDataType.DOUBLE: GraphQLFloat,
|
|
55
|
+
VSSDataType.DOUBLE_ARRAY: GraphQLList(GraphQLFloat),
|
|
56
|
+
VSSDataType.BOOLEAN: GraphQLBoolean,
|
|
57
|
+
VSSDataType.BOOLEAN_ARRAY: GraphQLList(GraphQLBoolean),
|
|
58
|
+
VSSDataType.STRING: GraphQLString,
|
|
59
|
+
VSSDataType.STRING_ARRAY: GraphQLList(GraphQLString),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_schema_from_tree(root_node: VSSNode, additional_leaf_fields: list) -> str:
|
|
64
|
+
"""Takes a VSSNode and additional fields for the leafs. Returns a graphql schema as string."""
|
|
65
|
+
args = dict(
|
|
66
|
+
id=GraphQLArgument(
|
|
67
|
+
GraphQLNonNull(GraphQLString),
|
|
68
|
+
description="VIN of the vehicle that you want to request data for.",
|
|
69
|
+
),
|
|
70
|
+
after=GraphQLArgument(
|
|
71
|
+
GraphQLString,
|
|
72
|
+
description=(
|
|
73
|
+
"Filter data to only provide information that was sent "
|
|
74
|
+
"from the vehicle after that timestamp."
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
root_query = GraphQLObjectType(
|
|
80
|
+
"Query",
|
|
81
|
+
lambda: {"vehicle": GraphQLField(to_gql_type(root_node, additional_leaf_fields), args)},
|
|
82
|
+
)
|
|
83
|
+
return print_schema(GraphQLSchema(root_query))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def to_gql_type(node: VSSNode, additional_leaf_fields: list) -> GraphQLObjectType:
|
|
87
|
+
return GraphQLObjectType(
|
|
88
|
+
name=node.qualified_name("_"),
|
|
89
|
+
fields=leaf_fields(node, additional_leaf_fields) if hasattr(
|
|
90
|
+
node, "datatype") else branch_fields(node, additional_leaf_fields),
|
|
91
|
+
description=node.description,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def leaf_fields(node: VSSNode, additional_leaf_fields: list) -> Dict[str, GraphQLField]:
|
|
96
|
+
field_dict = {}
|
|
97
|
+
if node.datatype is not None:
|
|
98
|
+
field_dict["value"] = field(node, "Value: ", GRAPHQL_TYPE_MAPPING[node.datatype])
|
|
99
|
+
field_dict["timestamp"] = field(node, "Timestamp: ")
|
|
100
|
+
if additional_leaf_fields:
|
|
101
|
+
for additional_field in additional_leaf_fields:
|
|
102
|
+
if len(additional_field) == 2:
|
|
103
|
+
field_dict[additional_field[0]] = field(node, f" {str(additional_field[1])}: ")
|
|
104
|
+
else:
|
|
105
|
+
raise VSpecError("", "", "Incorrect format of graphql field specification")
|
|
106
|
+
if node.has_unit():
|
|
107
|
+
field_dict["unit"] = field(node, "Unit of ")
|
|
108
|
+
return field_dict
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def branch_fields(node: VSSNode, additional_leaf_fields: list) -> Dict[str, GraphQLField]:
|
|
112
|
+
# we only consider nodes that either have children or are leave with a datatype
|
|
113
|
+
valid = (c for c in node.children if len(c.children) or hasattr(c, "datatype"))
|
|
114
|
+
return {camel_back(c.name): field(c, type=to_gql_type(c, additional_leaf_fields)) for c in valid}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def field(node: VSSNode, description_prefix="", type=GraphQLString) -> GraphQLField:
|
|
118
|
+
return GraphQLField(
|
|
119
|
+
type,
|
|
120
|
+
deprecation_reason=node.deprecation or None,
|
|
121
|
+
description=f"{description_prefix}{node.description}",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Vss2Graphql(Vss2X):
|
|
126
|
+
|
|
127
|
+
def __init__(self, vspec2vss_config: Vspec2VssConfig):
|
|
128
|
+
vspec2vss_config.no_expand_option_supported = False
|
|
129
|
+
vspec2vss_config.type_tree_supported = False
|
|
130
|
+
vspec2vss_config.uuid_supported = False
|
|
131
|
+
|
|
132
|
+
def add_arguments(self, parser: argparse.ArgumentParser):
|
|
133
|
+
parser.add_argument('--gqlfield', action='append', nargs=2,
|
|
134
|
+
help=" Add additional fields to the nodes in the graphql schema. "
|
|
135
|
+
"use: <field_name> <description>")
|
|
136
|
+
|
|
137
|
+
def generate(self, config: argparse.Namespace, signal_root: VSSNode, vspec2vss_config: Vspec2VssConfig,
|
|
138
|
+
data_type_root: Optional[VSSNode] = None) -> None:
|
|
139
|
+
print("Generating graphql output...")
|
|
140
|
+
outfile = open(config.output_file, 'w')
|
|
141
|
+
outfile.write(get_schema_from_tree(signal_root, config.gqlfield))
|
|
142
|
+
outfile.write("\n")
|
|
143
|
+
outfile.close()
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2023 Contributors to COVESA
|
|
4
|
+
#
|
|
5
|
+
# This program and the accompanying materials are made available under the
|
|
6
|
+
# terms of the Mozilla Public License 2.0 which is available at
|
|
7
|
+
# https://www.mozilla.org/en-US/MPL/2.0/
|
|
8
|
+
#
|
|
9
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
10
|
+
#
|
|
11
|
+
# Generate IDs of 4bytes size, 3 bytes incremental value + 1 byte for layer id.
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
from typing import Dict, Tuple, Optional
|
|
18
|
+
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
from vspec import load_tree
|
|
22
|
+
from vspec.model.constants import VSSTreeType
|
|
23
|
+
from vspec.model.vsstree import VSSNode
|
|
24
|
+
from vspec.utils import vss2id_val
|
|
25
|
+
from vspec.utils.idgen_utils import (fnv1_32_hash, get_all_keys_values,
|
|
26
|
+
get_node_identifier_bytes)
|
|
27
|
+
from vspec.vss2x import Vss2X
|
|
28
|
+
from vspec.vspec2vss_config import Vspec2VssConfig
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def generate_split_id(
|
|
32
|
+
node: VSSNode, id_counter: int, strict_mode: bool
|
|
33
|
+
) -> Tuple[str, int]:
|
|
34
|
+
|
|
35
|
+
"""Generates static UIDs using 4-byte FNV-1 hash.
|
|
36
|
+
|
|
37
|
+
@param node: VSSNode that we want to generate a static UID for
|
|
38
|
+
@param id_counter: consecutive numbers counter for amount of nodes
|
|
39
|
+
@param strict_mode: strict mode means case sensitivity for static UID generation
|
|
40
|
+
@return: tuple of hashed string and id counter
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
if hasattr(node, "fka") and node.fka:
|
|
44
|
+
name = node.fka[0] if isinstance(node.fka, list) else node.fka
|
|
45
|
+
else:
|
|
46
|
+
name = node.qualified_name()
|
|
47
|
+
identifier = get_node_identifier_bytes(
|
|
48
|
+
name,
|
|
49
|
+
node.data_type_str,
|
|
50
|
+
node.type.value,
|
|
51
|
+
node.get_unit(),
|
|
52
|
+
node.allowed,
|
|
53
|
+
node.min,
|
|
54
|
+
node.max,
|
|
55
|
+
strict_mode,
|
|
56
|
+
)
|
|
57
|
+
hashed_str = format(fnv1_32_hash(identifier), "08X")
|
|
58
|
+
|
|
59
|
+
return hashed_str, id_counter + 1
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def export_node(yaml_dict, node, id_counter, strict_mode: bool) -> Tuple[int, int]:
|
|
63
|
+
"""Recursive function to export the full tree to a dict
|
|
64
|
+
|
|
65
|
+
@param yaml_dict: the to be exported dict
|
|
66
|
+
@param node: parent node of the tree
|
|
67
|
+
@param id_counter: counter for amount of ids
|
|
68
|
+
@param strict_mode: strict mode means case sensitivity for static UID generation
|
|
69
|
+
@return: id_counter, id_counter
|
|
70
|
+
"""
|
|
71
|
+
node_id, id_counter = generate_split_id(node, id_counter, strict_mode)
|
|
72
|
+
|
|
73
|
+
# check for hash duplicates
|
|
74
|
+
for key, value in get_all_keys_values(yaml_dict):
|
|
75
|
+
if not isinstance(value, dict) and key == "staticUID":
|
|
76
|
+
if node_id == value[2:]:
|
|
77
|
+
logging.fatal(
|
|
78
|
+
f"There is a small chance that the result of FNV-1 "
|
|
79
|
+
f"hashes are the same in this case the hash of node "
|
|
80
|
+
f"'{node.qualified_name()}' is the same as another hash."
|
|
81
|
+
f"Can you please update it."
|
|
82
|
+
)
|
|
83
|
+
# We could add handling of duplicates here
|
|
84
|
+
sys.exit(-1)
|
|
85
|
+
|
|
86
|
+
node_path = node.qualified_name()
|
|
87
|
+
|
|
88
|
+
yaml_dict[node_path] = {"staticUID": f"0x{node_id}"}
|
|
89
|
+
yaml_dict[node_path]["description"] = node.description
|
|
90
|
+
yaml_dict[node_path]["type"] = str(node.type.value)
|
|
91
|
+
if node.unit:
|
|
92
|
+
yaml_dict[node_path]["unit"] = str(node.unit.value)
|
|
93
|
+
if node.is_signal() or node.is_property():
|
|
94
|
+
yaml_dict[node_path]["datatype"] = node.data_type_str
|
|
95
|
+
if node.allowed:
|
|
96
|
+
yaml_dict[node_path]["allowed"] = node.allowed
|
|
97
|
+
if node.min:
|
|
98
|
+
yaml_dict[node_path]["min"] = node.min
|
|
99
|
+
if node.max:
|
|
100
|
+
yaml_dict[node_path]["max"] = node.max
|
|
101
|
+
|
|
102
|
+
if node.fka:
|
|
103
|
+
yaml_dict[node_path]["fka"] = node.fka
|
|
104
|
+
elif "fka" in node.extended_attributes.keys():
|
|
105
|
+
yaml_dict[node_path]["fka"] = node.extended_attributes["fka"]
|
|
106
|
+
|
|
107
|
+
if node.deprecation:
|
|
108
|
+
yaml_dict[node_path]["deprecation"] = node.deprecation
|
|
109
|
+
|
|
110
|
+
for child in node.children:
|
|
111
|
+
id_counter, id_counter = export_node(yaml_dict, child, id_counter, strict_mode)
|
|
112
|
+
|
|
113
|
+
return id_counter, id_counter
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Vss2Id(Vss2X):
|
|
117
|
+
|
|
118
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
119
|
+
"""Adds command line arguments to a pre-existing argument parser
|
|
120
|
+
|
|
121
|
+
@param parser: the pre-existing argument parser
|
|
122
|
+
"""
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"--validate-static-uid", type=str, default="", help="Path to validation file."
|
|
125
|
+
)
|
|
126
|
+
parser.add_argument(
|
|
127
|
+
"--only-validate-no-export",
|
|
128
|
+
action="store_true",
|
|
129
|
+
default=False,
|
|
130
|
+
help="For pytests and pipelines you can skip the export of the vspec file.",
|
|
131
|
+
)
|
|
132
|
+
parser.add_argument(
|
|
133
|
+
"--strict-mode",
|
|
134
|
+
action="store_true",
|
|
135
|
+
help="Strict mode means that the generation of static UIDs is case-sensitive.",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def generate(self, config: argparse.Namespace, signal_root: VSSNode, vspec2vss_config: Vspec2VssConfig,
|
|
139
|
+
data_type_root: Optional[VSSNode] = None) -> None:
|
|
140
|
+
|
|
141
|
+
"""Main export function used to generate the output id vspec.
|
|
142
|
+
|
|
143
|
+
@param config: Command line arguments it was run with
|
|
144
|
+
@param signal_root: root of the signal tree
|
|
145
|
+
@param print_uuid: Not used here but needed by main script
|
|
146
|
+
"""
|
|
147
|
+
logging.info("Generating YAML output...")
|
|
148
|
+
|
|
149
|
+
id_counter: int = 0
|
|
150
|
+
signals_yaml_dict: Dict[str, str] = {} # Use str for ID values
|
|
151
|
+
id_counter, _ = export_node(
|
|
152
|
+
signals_yaml_dict, signal_root, id_counter, config.strict_mode
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if config.validate_static_uid:
|
|
156
|
+
logging.info(
|
|
157
|
+
f"Now validating nodes, static UIDs, types, units and description with "
|
|
158
|
+
f"file '{config.validate_static_uid}'"
|
|
159
|
+
)
|
|
160
|
+
if os.path.isabs(config.validate_static_uid):
|
|
161
|
+
other_path = config.validate_static_uid
|
|
162
|
+
else:
|
|
163
|
+
other_path = os.path.join(os.getcwd(), config.validate_static_uid)
|
|
164
|
+
|
|
165
|
+
validation_tree = load_tree(
|
|
166
|
+
other_path, ["."], tree_type=VSSTreeType.SIGNAL_TREE
|
|
167
|
+
)
|
|
168
|
+
vss2id_val.validate_static_uids(signals_yaml_dict, validation_tree, config)
|
|
169
|
+
|
|
170
|
+
if not config.only_validate_no_export:
|
|
171
|
+
with open(config.output_file, "w") as f:
|
|
172
|
+
yaml.dump(signals_yaml_dict, f)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2022 Contributors to COVESA
|
|
4
|
+
#
|
|
5
|
+
# This program and the accompanying materials are made available under the
|
|
6
|
+
# terms of the Mozilla Public License 2.0 which is available at
|
|
7
|
+
# https://www.mozilla.org/en-US/MPL/2.0/
|
|
8
|
+
#
|
|
9
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
10
|
+
|
|
11
|
+
# Convert vspec tree to JSON
|
|
12
|
+
|
|
13
|
+
from vspec.model.vsstree import VSSNode
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Dict, Any
|
|
18
|
+
from typing import Optional
|
|
19
|
+
from vspec.vss2x import Vss2X
|
|
20
|
+
from vspec.vspec2vss_config import Vspec2VssConfig
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def export_node(json_dict, node, config, print_uuid):
|
|
24
|
+
|
|
25
|
+
json_dict[node.name] = {}
|
|
26
|
+
|
|
27
|
+
if node.is_signal() or node.is_property():
|
|
28
|
+
json_dict[node.name]["datatype"] = node.data_type_str
|
|
29
|
+
|
|
30
|
+
json_dict[node.name]["type"] = str(node.type.value)
|
|
31
|
+
|
|
32
|
+
# many optional attributes are initilized to "" in vsstree.py
|
|
33
|
+
if node.min != "":
|
|
34
|
+
json_dict[node.name]["min"] = node.min
|
|
35
|
+
if node.max != "":
|
|
36
|
+
json_dict[node.name]["max"] = node.max
|
|
37
|
+
if node.allowed != "":
|
|
38
|
+
json_dict[node.name]["allowed"] = node.allowed
|
|
39
|
+
if node.default != "":
|
|
40
|
+
json_dict[node.name]["default"] = node.default
|
|
41
|
+
if node.deprecation != "":
|
|
42
|
+
json_dict[node.name]["deprecation"] = node.deprecation
|
|
43
|
+
|
|
44
|
+
# in case of unit or aggregate, the attribute will be missing
|
|
45
|
+
try:
|
|
46
|
+
json_dict[node.name]["unit"] = str(node.unit.value)
|
|
47
|
+
except AttributeError:
|
|
48
|
+
pass
|
|
49
|
+
try:
|
|
50
|
+
json_dict[node.name]["aggregate"] = node.aggregate
|
|
51
|
+
except AttributeError:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
json_dict[node.name]["description"] = node.description
|
|
55
|
+
if node.comment != "":
|
|
56
|
+
json_dict[node.name]["comment"] = node.comment
|
|
57
|
+
|
|
58
|
+
if print_uuid:
|
|
59
|
+
json_dict[node.name]["uuid"] = node.uuid
|
|
60
|
+
|
|
61
|
+
for k, v in node.extended_attributes.items():
|
|
62
|
+
if not config.json_all_extended_attributes and k not in VSSNode.whitelisted_extended_attributes:
|
|
63
|
+
continue
|
|
64
|
+
json_dict[node.name][k] = v
|
|
65
|
+
|
|
66
|
+
# Include instance information if we run tool in "no-expand" mode
|
|
67
|
+
if config.no_expand and node.instances is not None:
|
|
68
|
+
json_dict[node.name]["instances"] = node.instances
|
|
69
|
+
|
|
70
|
+
# Might be better to not generate child dict, if there are no children
|
|
71
|
+
# if node.type == VSSType.BRANCH and len(node.children) != 0:
|
|
72
|
+
# json_dict[node.name]["children"]={}
|
|
73
|
+
|
|
74
|
+
# But old JSON code always generates children, so lets do so to
|
|
75
|
+
if node.is_branch() or node.is_struct():
|
|
76
|
+
json_dict[node.name]["children"] = {}
|
|
77
|
+
|
|
78
|
+
for child in node.children:
|
|
79
|
+
export_node(json_dict[node.name]["children"],
|
|
80
|
+
child, config, print_uuid)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Vss2Json(Vss2X):
|
|
84
|
+
|
|
85
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
86
|
+
parser.add_argument('--json-all-extended-attributes', action='store_true',
|
|
87
|
+
help="Generate all extended attributes found in the model "
|
|
88
|
+
"(default is generating only those given by the -e/--extended-attributes parameter).")
|
|
89
|
+
parser.add_argument('--json-pretty', action='store_true',
|
|
90
|
+
help=" Pretty print JSON output.")
|
|
91
|
+
|
|
92
|
+
def generate(self, config: argparse.Namespace, signal_root: VSSNode, vspec2vss_config: Vspec2VssConfig,
|
|
93
|
+
data_type_root: Optional[VSSNode] = None) -> None:
|
|
94
|
+
logging.info("Generating JSON output...")
|
|
95
|
+
indent = None
|
|
96
|
+
if config.json_pretty:
|
|
97
|
+
logging.info("Serializing pretty JSON...")
|
|
98
|
+
indent = 2
|
|
99
|
+
else:
|
|
100
|
+
logging.info("Serializing compact JSON...")
|
|
101
|
+
|
|
102
|
+
signals_json_dict: Dict[str, Any] = {}
|
|
103
|
+
export_node(signals_json_dict, signal_root, config, vspec2vss_config.generate_uuid)
|
|
104
|
+
|
|
105
|
+
if data_type_root is not None:
|
|
106
|
+
data_types_json_dict: Dict[str, Any] = {}
|
|
107
|
+
export_node(data_types_json_dict, data_type_root, config, vspec2vss_config.generate_uuid)
|
|
108
|
+
if config.types_output_file is None:
|
|
109
|
+
logging.info("Adding custom data types to signal dictionary")
|
|
110
|
+
signals_json_dict["ComplexDataTypes"] = data_types_json_dict
|
|
111
|
+
else:
|
|
112
|
+
with open(config.types_output_file, 'w') as f:
|
|
113
|
+
json.dump(data_types_json_dict, f, indent=indent, sort_keys=True)
|
|
114
|
+
|
|
115
|
+
with open(config.output_file, 'w') as f:
|
|
116
|
+
json.dump(signals_json_dict, f, indent=indent, sort_keys=True)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2023 Contributors to COVESA
|
|
4
|
+
#
|
|
5
|
+
# This program and the accompanying materials are made available under the
|
|
6
|
+
# terms of the Mozilla Public License 2.0 which is available at
|
|
7
|
+
# https://www.mozilla.org/en-US/MPL/2.0/
|
|
8
|
+
#
|
|
9
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
10
|
+
#
|
|
11
|
+
# Convert vspec tree compatible JSON schema
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
from typing import Dict, Any
|
|
17
|
+
from typing import Optional
|
|
18
|
+
from vspec.model.vsstree import VSSNode
|
|
19
|
+
from vspec.vss2x import Vss2X
|
|
20
|
+
from vspec.vspec2vss_config import Vspec2VssConfig
|
|
21
|
+
|
|
22
|
+
type_map = {
|
|
23
|
+
"int8": "integer",
|
|
24
|
+
"uint8": "integer",
|
|
25
|
+
"int16": "integer",
|
|
26
|
+
"uint16": "integer",
|
|
27
|
+
"int32": "integer",
|
|
28
|
+
"uint32": "integer",
|
|
29
|
+
"int64": "integer",
|
|
30
|
+
"uint64": "integer",
|
|
31
|
+
"boolean": "boolean",
|
|
32
|
+
"float": "number",
|
|
33
|
+
"double": "number",
|
|
34
|
+
"string": "string",
|
|
35
|
+
"int8[]": "array",
|
|
36
|
+
"uint8[]": "array",
|
|
37
|
+
"int16[]": "array",
|
|
38
|
+
"uint16[]": "array",
|
|
39
|
+
"int32[]": "array",
|
|
40
|
+
"uint32[]": "array",
|
|
41
|
+
"int64[]": "array",
|
|
42
|
+
"uint64[]": "array",
|
|
43
|
+
"boolean[]": "array",
|
|
44
|
+
"float[]": "array",
|
|
45
|
+
"double[]": "array",
|
|
46
|
+
"string[]": "array"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def export_node(json_dict, node, config, print_uuid):
|
|
51
|
+
"""Preparing nodes for JSON schema output."""
|
|
52
|
+
# keyword with X- sign are left for extensions and they are not part of official JSON schema
|
|
53
|
+
json_dict[node.name] = {
|
|
54
|
+
"description": node.description,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if node.is_signal() or node.is_property():
|
|
58
|
+
json_dict[node.name]["type"] = type_map[node.data_type_str]
|
|
59
|
+
|
|
60
|
+
# many optional attributes are initialized to "" in vsstree.py
|
|
61
|
+
if node.min != "":
|
|
62
|
+
json_dict[node.name]["minimum"] = node.min
|
|
63
|
+
if node.max != "":
|
|
64
|
+
json_dict[node.name]["maximum"] = node.max
|
|
65
|
+
if node.allowed != "":
|
|
66
|
+
json_dict[node.name]["enum"] = node.allowed
|
|
67
|
+
if node.default != "":
|
|
68
|
+
json_dict[node.name]["default"] = node.default
|
|
69
|
+
if node.is_struct():
|
|
70
|
+
# change type to object
|
|
71
|
+
json_dict[node.type]["type"] = "object"
|
|
72
|
+
|
|
73
|
+
if config.jsonschema_all_extended_attributes:
|
|
74
|
+
if node.type != "":
|
|
75
|
+
json_dict[node.name]["x-VSStype"] = str(node.type.value)
|
|
76
|
+
if node.data_type_str != "":
|
|
77
|
+
json_dict[node.name]["x-datatype"] = node.data_type_str
|
|
78
|
+
if node.deprecation != "":
|
|
79
|
+
json_dict[node.name]["x-deprecation"] = node.deprecation
|
|
80
|
+
|
|
81
|
+
# in case of unit or aggregate, the attribute will be missing
|
|
82
|
+
try:
|
|
83
|
+
json_dict[node.name]["x-unit"] = str(node.unit.value)
|
|
84
|
+
except AttributeError:
|
|
85
|
+
pass
|
|
86
|
+
try:
|
|
87
|
+
json_dict[node.name]["x-aggregate"] = node.aggregate
|
|
88
|
+
if node.aggregate:
|
|
89
|
+
# change type to object
|
|
90
|
+
json_dict[node.type]["type"] = "object"
|
|
91
|
+
except AttributeError:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
if node.comment != "":
|
|
95
|
+
json_dict[node.name]["x-comment"] = node.comment
|
|
96
|
+
|
|
97
|
+
if print_uuid:
|
|
98
|
+
json_dict[node.name]["x-uuid"] = node.uuid
|
|
99
|
+
|
|
100
|
+
for k, v in node.extended_attributes.items():
|
|
101
|
+
json_dict[node.name][k] = v
|
|
102
|
+
|
|
103
|
+
# Generate child nodes
|
|
104
|
+
if node.is_branch() or node.is_struct():
|
|
105
|
+
if config.jsonschema_disallow_additional_properties:
|
|
106
|
+
json_dict[node.name]["additionalProperties"] = False
|
|
107
|
+
json_dict[node.name]["properties"] = {}
|
|
108
|
+
if config.jsonschema_require_all_properties:
|
|
109
|
+
json_dict[node.name]["required"] = [child.name for child in node.children]
|
|
110
|
+
for child in node.children:
|
|
111
|
+
export_node(json_dict[node.name]["properties"], child, config, print_uuid)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Vss2JsonSchema(Vss2X):
|
|
115
|
+
|
|
116
|
+
def add_arguments(self, parser: argparse.ArgumentParser):
|
|
117
|
+
"""Check for input arguments."""
|
|
118
|
+
parser.add_argument('--jsonschema-all-extended-attributes', action='store_true',
|
|
119
|
+
help="Generate all extended attributes found in the model."
|
|
120
|
+
"Should not be used with strict mode JSON Schema validators.")
|
|
121
|
+
parser.add_argument("--jsonschema-disallow-additional-properties", action='store_true',
|
|
122
|
+
help="Do not allow properties not defined in VSS tree")
|
|
123
|
+
parser.add_argument("--jsonschema-require-all-properties", action='store_true',
|
|
124
|
+
help="Require all elements defined in VSS tree for a valid object")
|
|
125
|
+
parser.add_argument('--jsonschema-pretty', action='store_true',
|
|
126
|
+
help=" Pretty print JSON Schema output.")
|
|
127
|
+
|
|
128
|
+
def generate(self, config: argparse.Namespace, signal_root: VSSNode, vspec2vss_config: Vspec2VssConfig,
|
|
129
|
+
data_type_root: Optional[VSSNode] = None) -> None:
|
|
130
|
+
"""Export function for generating JSON schema file."""
|
|
131
|
+
logging.info("Generating JSON schema...")
|
|
132
|
+
indent = None
|
|
133
|
+
if config.jsonschema_pretty:
|
|
134
|
+
logging.info("Serializing pretty JSON schema...")
|
|
135
|
+
indent = 2
|
|
136
|
+
|
|
137
|
+
signals_json_schema: Dict[str, Any] = {}
|
|
138
|
+
export_node(signals_json_schema, signal_root, config, vspec2vss_config.generate_uuid)
|
|
139
|
+
|
|
140
|
+
# Add data types to the schema
|
|
141
|
+
if data_type_root is not None:
|
|
142
|
+
data_types_json_schema: Dict[str, Any] = {}
|
|
143
|
+
export_node(data_types_json_schema, data_type_root, config, vspec2vss_config.generate_uuid)
|
|
144
|
+
if config.jsonschema_all_extended_attributes:
|
|
145
|
+
signals_json_schema["x-ComplexDataTypes"] = data_types_json_schema
|
|
146
|
+
|
|
147
|
+
# VSS models only have one root, so there should only be one
|
|
148
|
+
# key in the dict
|
|
149
|
+
assert (len(signals_json_schema.keys()) == 1)
|
|
150
|
+
top_node_name = list(signals_json_schema.keys())[0]
|
|
151
|
+
signals_json_schema = signals_json_schema.pop(top_node_name)
|
|
152
|
+
|
|
153
|
+
json_schema = {
|
|
154
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
155
|
+
"title": top_node_name,
|
|
156
|
+
"type": "object",
|
|
157
|
+
**signals_json_schema}
|
|
158
|
+
with open(config.output_file, 'w', encoding="utf-8") as output_file:
|
|
159
|
+
json.dump(json_schema, output_file, indent=indent, sort_keys=False)
|