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
vspec/model/vsstree.py
ADDED
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2021 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 copy
|
|
12
|
+
import logging
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
from typing import Any, List, Optional, Set
|
|
16
|
+
|
|
17
|
+
from anytree import ( # type: ignore[import]
|
|
18
|
+
ChildResolverError,
|
|
19
|
+
Node,
|
|
20
|
+
RenderTree,
|
|
21
|
+
Resolver,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from .constants import VSSDataType, VSSType, VSSUnit, VSSUnitCollection
|
|
25
|
+
from .exceptions import (
|
|
26
|
+
ImpossibleMergeException,
|
|
27
|
+
IncompleteElementException,
|
|
28
|
+
NameStyleValidationException,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
DEFAULT_SEPARATOR = "."
|
|
32
|
+
ARRAY_SUBSCRIPT_OP = "[]"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class VSSNode(Node):
|
|
36
|
+
"""Representation of an VSS element according to the vehicle signal specification."""
|
|
37
|
+
|
|
38
|
+
type: VSSType
|
|
39
|
+
description = None
|
|
40
|
+
comment: str = ""
|
|
41
|
+
uuid: str = ""
|
|
42
|
+
# data type - string representation. For struct names, this is the fully
|
|
43
|
+
# qualified struct name.
|
|
44
|
+
data_type_str: str = ""
|
|
45
|
+
# data type - enum representation if available
|
|
46
|
+
datatype: Optional[VSSDataType]
|
|
47
|
+
|
|
48
|
+
# The node types that the nodes can take
|
|
49
|
+
available_types: Set[str] = set()
|
|
50
|
+
|
|
51
|
+
core_attributes = [
|
|
52
|
+
"type",
|
|
53
|
+
"children",
|
|
54
|
+
"datatype",
|
|
55
|
+
"description",
|
|
56
|
+
"unit",
|
|
57
|
+
"uuid",
|
|
58
|
+
"min",
|
|
59
|
+
"max",
|
|
60
|
+
"allowed",
|
|
61
|
+
"instantiate",
|
|
62
|
+
"aggregate",
|
|
63
|
+
"default",
|
|
64
|
+
"instances",
|
|
65
|
+
"deprecation",
|
|
66
|
+
"arraysize",
|
|
67
|
+
"comment",
|
|
68
|
+
"$file_name$",
|
|
69
|
+
"fka",
|
|
70
|
+
"delete",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# List of accepted extended attributes. In strict terminate if an attribute is
|
|
74
|
+
# neither in core or extended,
|
|
75
|
+
whitelisted_extended_attributes: List[str] = []
|
|
76
|
+
|
|
77
|
+
unit: Optional[VSSUnit] = None
|
|
78
|
+
|
|
79
|
+
min = ""
|
|
80
|
+
max = ""
|
|
81
|
+
allowed = ""
|
|
82
|
+
instantiate = True
|
|
83
|
+
|
|
84
|
+
ttl_name = ""
|
|
85
|
+
|
|
86
|
+
default = ""
|
|
87
|
+
|
|
88
|
+
instances = None
|
|
89
|
+
expanded = False
|
|
90
|
+
deprecation = ""
|
|
91
|
+
fka = ""
|
|
92
|
+
delete: bool = False
|
|
93
|
+
|
|
94
|
+
def __deepcopy__(self, memo):
|
|
95
|
+
# Deep copy of source_dict and children needed as overlay or programmatic changes
|
|
96
|
+
# in exporters otherwise risk changing values not only for current instances but also for others
|
|
97
|
+
return VSSNode(
|
|
98
|
+
self.name,
|
|
99
|
+
copy.deepcopy(self.source_dict),
|
|
100
|
+
self.available_types.copy(),
|
|
101
|
+
parent=None,
|
|
102
|
+
children=copy.deepcopy(self.children, memo),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
name,
|
|
108
|
+
source_dict: dict,
|
|
109
|
+
available_types: Set[str],
|
|
110
|
+
parent=None,
|
|
111
|
+
children=None,
|
|
112
|
+
break_on_unknown_attribute=False,
|
|
113
|
+
break_on_name_style_violation=False,
|
|
114
|
+
):
|
|
115
|
+
"""Creates an VSS Node object from parsed yaml instance represented as a dict.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
name: Name of this VSS instance.
|
|
119
|
+
source_dict: VSS instance represented as dict from yaml parsing.
|
|
120
|
+
available_types: Available node types asa string list
|
|
121
|
+
parent: Optional parent of this node instance.
|
|
122
|
+
children: Optional children instances of this node.
|
|
123
|
+
break_on_unknown_attribute: Throw if the node contains attributes not in core VSS specification
|
|
124
|
+
break_on_name_style_vioation: Throw if this node's name is not follwing th VSS recommended style
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
VSSNode object according to the Vehicle Signal Specification.
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
super().__init__(name, parent, children)
|
|
132
|
+
self.available_types = available_types
|
|
133
|
+
|
|
134
|
+
if source_dict["type"] not in available_types:
|
|
135
|
+
logging.error(
|
|
136
|
+
f'Invalid type provided for VSSNode: {source_dict["type"]}. Allowed types are {self.available_types}'
|
|
137
|
+
)
|
|
138
|
+
sys.exit(-1)
|
|
139
|
+
|
|
140
|
+
self.source_dict = source_dict
|
|
141
|
+
self.unpack_source_dict()
|
|
142
|
+
|
|
143
|
+
if self.is_property() and not self.parent.is_struct():
|
|
144
|
+
logging.error(
|
|
145
|
+
f"Orphan property detected. {self.name} is not defined under a struct"
|
|
146
|
+
)
|
|
147
|
+
sys.exit(-1)
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
self.validate_name_style(self.source_dict["$file_name$"])
|
|
151
|
+
except NameStyleValidationException as e:
|
|
152
|
+
info_string = str(e)
|
|
153
|
+
if break_on_name_style_violation:
|
|
154
|
+
logging.warning(info_string)
|
|
155
|
+
logging.error("You asked for strict checking. Terminating.")
|
|
156
|
+
sys.exit(-1)
|
|
157
|
+
else:
|
|
158
|
+
logging.info(info_string)
|
|
159
|
+
|
|
160
|
+
def unpack_source_dict(self):
|
|
161
|
+
self.extended_attributes = self.source_dict.copy()
|
|
162
|
+
|
|
163
|
+
# Clean special cases
|
|
164
|
+
if "children" in self.extended_attributes:
|
|
165
|
+
del self.extended_attributes["children"]
|
|
166
|
+
if "type" in self.extended_attributes:
|
|
167
|
+
del self.extended_attributes["type"]
|
|
168
|
+
|
|
169
|
+
def extractCoreAttribute(name: str):
|
|
170
|
+
if (
|
|
171
|
+
name != "children"
|
|
172
|
+
and name != "type"
|
|
173
|
+
and name in self.source_dict.keys()
|
|
174
|
+
):
|
|
175
|
+
setattr(self, name, self.source_dict[name])
|
|
176
|
+
del self.extended_attributes[name]
|
|
177
|
+
|
|
178
|
+
self.type = VSSType.from_str(self.source_dict["type"])
|
|
179
|
+
|
|
180
|
+
for attribute in VSSNode.core_attributes:
|
|
181
|
+
extractCoreAttribute(attribute)
|
|
182
|
+
|
|
183
|
+
# Datatype and unit need special handling, so we do some further analysis
|
|
184
|
+
|
|
185
|
+
# Units are applicable only for primitives. Not user defined types.
|
|
186
|
+
if self.has_unit():
|
|
187
|
+
|
|
188
|
+
if not (self.is_signal() or self.is_property()):
|
|
189
|
+
logging.error(
|
|
190
|
+
"Item %s cannot have unit, only allowed for signal and property!",
|
|
191
|
+
self.name,
|
|
192
|
+
)
|
|
193
|
+
sys.exit(-1)
|
|
194
|
+
|
|
195
|
+
unit = self.source_dict["unit"]
|
|
196
|
+
self.unit = VSSUnitCollection.get_unit(unit)
|
|
197
|
+
if self.unit is None:
|
|
198
|
+
logging.error(
|
|
199
|
+
f"Unknown unit {unit} for signal {self.qualified_name()}. Terminating."
|
|
200
|
+
)
|
|
201
|
+
sys.exit(-1)
|
|
202
|
+
|
|
203
|
+
# self.data_type shall only be set if base type is a primitive (VSSDataType)
|
|
204
|
+
if self.has_datatype():
|
|
205
|
+
if self.is_signal() or self.is_property():
|
|
206
|
+
self.data_type_str = self.source_dict["datatype"]
|
|
207
|
+
self.validate_and_set_datatype()
|
|
208
|
+
else:
|
|
209
|
+
logging.error(
|
|
210
|
+
"Item %s cannot have datatype, only allowed for signal and property!",
|
|
211
|
+
self.name,
|
|
212
|
+
)
|
|
213
|
+
sys.exit(-1)
|
|
214
|
+
elif self.is_signal() or self.is_property():
|
|
215
|
+
raise IncompleteElementException(
|
|
216
|
+
f"Incomplete element {self.name} from {self.source_dict['$file_name$']}: "
|
|
217
|
+
f"Elements of type {self.type.value} need to have a datatype declared."
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Datatype check for unit performed first when we have set the right datatype
|
|
221
|
+
if self.has_unit():
|
|
222
|
+
|
|
223
|
+
if not self.has_datatype():
|
|
224
|
+
logging.error(
|
|
225
|
+
"Unit specified for item not using standard datatype: %s", self.name
|
|
226
|
+
)
|
|
227
|
+
sys.exit(-1)
|
|
228
|
+
|
|
229
|
+
if self.has_instances() and not self.is_branch():
|
|
230
|
+
logging.error(
|
|
231
|
+
f"Only branches can be instantiated. {self.qualified_name()} is of type {self.type}"
|
|
232
|
+
)
|
|
233
|
+
sys.exit(-1)
|
|
234
|
+
|
|
235
|
+
def validate_name_style(self, sourcefile):
|
|
236
|
+
"""Checks wether this node is adhering to VSS style conventions.
|
|
237
|
+
|
|
238
|
+
Throws NameStyleValidationException when deviations are detected. A VSS model violating
|
|
239
|
+
this conventions can still be a valid model.
|
|
240
|
+
|
|
241
|
+
"""
|
|
242
|
+
camel_regexp = re.compile("[A-Z][A-Za-z0-9]*$")
|
|
243
|
+
if (
|
|
244
|
+
self.is_signal()
|
|
245
|
+
and self.datatype == VSSDataType.BOOLEAN
|
|
246
|
+
and not self.name.startswith("Is")
|
|
247
|
+
):
|
|
248
|
+
raise NameStyleValidationException(
|
|
249
|
+
(
|
|
250
|
+
f'Boolean node "{self.name}" found in file "{sourcefile}" is not following naming conventions. ',
|
|
251
|
+
'It is recommended that boolean nodes start with "Is".',
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
camel_regexp = re.compile('[A-Z][A-Za-z0-9]*$')
|
|
256
|
+
# relax camel case requirement for struct properties
|
|
257
|
+
if not self.is_property() and not camel_regexp.match(self.name):
|
|
258
|
+
raise NameStyleValidationException(
|
|
259
|
+
(
|
|
260
|
+
f'Node "{self.name}" found in file "{sourcefile}" is not following naming conventions. ',
|
|
261
|
+
"It is recommended that node names use camel case, starting with a capital letter, ",
|
|
262
|
+
"only using letters A-z and numbers 0-9.",
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def base_data_type_str(self) -> str:
|
|
267
|
+
"""
|
|
268
|
+
This gives the base type of the type, i.e. without array suffix if present
|
|
269
|
+
"""
|
|
270
|
+
suffix = "[]"
|
|
271
|
+
if self.data_type_str.endswith(suffix):
|
|
272
|
+
return self.data_type_str[: -len(suffix)]
|
|
273
|
+
return self.data_type_str
|
|
274
|
+
|
|
275
|
+
def qualified_name(self, separator=DEFAULT_SEPARATOR) -> str:
|
|
276
|
+
"""Returns fully qualified name of a VSS object (including path) using the defined separator (or default ='.')
|
|
277
|
+
Args:
|
|
278
|
+
separator: Optional parameter as custom separator between path elements of this instance
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Fully Qualified VSS Node string representation including complete path.
|
|
282
|
+
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
name = self.name
|
|
286
|
+
|
|
287
|
+
path = self
|
|
288
|
+
while not path.is_root:
|
|
289
|
+
path = path.parent
|
|
290
|
+
node_name = path.name
|
|
291
|
+
|
|
292
|
+
name = f"{node_name}{separator}{name}"
|
|
293
|
+
return name
|
|
294
|
+
|
|
295
|
+
def is_branch(self):
|
|
296
|
+
return self.type == VSSType.BRANCH
|
|
297
|
+
|
|
298
|
+
def is_sensor(self):
|
|
299
|
+
return self.type == VSSType.SENSOR
|
|
300
|
+
|
|
301
|
+
def is_actuator(self):
|
|
302
|
+
return self.type == VSSType.ACTUATOR
|
|
303
|
+
|
|
304
|
+
def is_attribute(self):
|
|
305
|
+
return self.type == VSSType.ATTRIBUTE
|
|
306
|
+
|
|
307
|
+
def is_struct(self):
|
|
308
|
+
return self.type == VSSType.STRUCT
|
|
309
|
+
|
|
310
|
+
def is_property(self):
|
|
311
|
+
return self.type == VSSType.PROPERTY
|
|
312
|
+
|
|
313
|
+
def is_signal(self):
|
|
314
|
+
return self.is_sensor() or self.is_actuator() or self.is_attribute()
|
|
315
|
+
|
|
316
|
+
def is_orphan(self) -> bool:
|
|
317
|
+
"""Checks if this instance is a branch without any child nodes
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
True if this instance is a branch and has no children.
|
|
321
|
+
"""
|
|
322
|
+
if self.is_branch() or self.is_struct():
|
|
323
|
+
return self.is_leaf
|
|
324
|
+
return False
|
|
325
|
+
|
|
326
|
+
def get_struct_qualified_name(self, struct_name) -> Optional[str]:
|
|
327
|
+
"""
|
|
328
|
+
Returns whether a struct node with the given relative name is defined under the branch of this node.
|
|
329
|
+
A relative name is the fully qualified name of the struct without the branch prefix under which it is defined.
|
|
330
|
+
|
|
331
|
+
Example 1:
|
|
332
|
+
struct: VehicleTypes.Branch1.StructA
|
|
333
|
+
this: VehicleTypes.Branch1.StructB.Property1 can use data type "StructA"
|
|
334
|
+
|
|
335
|
+
Example 2:
|
|
336
|
+
struct: VehicleTypes.Branch1.StructA
|
|
337
|
+
this: VehicleTypes.Branch1.Branch2.StructB.Property1 CANNOT use data type "StructA" since they are not defined
|
|
338
|
+
under the same branch
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
Keyword arguments:
|
|
342
|
+
struct_name - The struct name to search for.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
Fully qualified name of the struct if one exists. Otherwise None.
|
|
346
|
+
"""
|
|
347
|
+
path = self
|
|
348
|
+
|
|
349
|
+
# find the ancestor branch
|
|
350
|
+
root = None
|
|
351
|
+
while not path.is_branch():
|
|
352
|
+
path = path.parent
|
|
353
|
+
root = path
|
|
354
|
+
|
|
355
|
+
# find the struct node under the branch
|
|
356
|
+
if root is None:
|
|
357
|
+
return None
|
|
358
|
+
|
|
359
|
+
for child in root.children:
|
|
360
|
+
if child.is_struct() and child.name == struct_name:
|
|
361
|
+
return child.qualified_name()
|
|
362
|
+
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
def is_instantiated(self) -> bool:
|
|
366
|
+
"""Checks if node shall be instantiated through its parent
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
True if it shall be instantiated
|
|
370
|
+
"""
|
|
371
|
+
return self.instantiate
|
|
372
|
+
|
|
373
|
+
def has_unit(self) -> bool:
|
|
374
|
+
"""Checks if this instance has a unit
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
True if this instance has a unit, False otherwise
|
|
378
|
+
"""
|
|
379
|
+
return hasattr(self, "unit") and self.unit is not None
|
|
380
|
+
|
|
381
|
+
def get_unit(self) -> str:
|
|
382
|
+
"""Returns:
|
|
383
|
+
The name of the unit or empty string if no unit
|
|
384
|
+
"""
|
|
385
|
+
if hasattr(self, "unit") and self.unit is not None:
|
|
386
|
+
return self.unit.value
|
|
387
|
+
else:
|
|
388
|
+
return ""
|
|
389
|
+
|
|
390
|
+
def has_datatype(self) -> bool:
|
|
391
|
+
"""Check if this instance has a datatype
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
True if this instance has a data type, False otherwise
|
|
395
|
+
"""
|
|
396
|
+
return hasattr(self, "datatype") and self.datatype is not None
|
|
397
|
+
|
|
398
|
+
def get_datatype(self) -> str:
|
|
399
|
+
"""Returns:
|
|
400
|
+
The name of the datatype or empty string if no datatype
|
|
401
|
+
"""
|
|
402
|
+
return self.data_type_str
|
|
403
|
+
|
|
404
|
+
def has_instances(self) -> bool:
|
|
405
|
+
"""Check if this instance has a VSS instances
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
True if this instance declares instances, False otherwise
|
|
409
|
+
"""
|
|
410
|
+
return hasattr(self, "instances") and self.instances is not None
|
|
411
|
+
|
|
412
|
+
def merge(self, other: "VSSNode"):
|
|
413
|
+
"""Merges two VSSNode, other parameter overwrites the caller object,
|
|
414
|
+
if it is not None
|
|
415
|
+
Args:
|
|
416
|
+
other: other node to merge into the caller object
|
|
417
|
+
"""
|
|
418
|
+
if self.is_branch() and not other.is_branch():
|
|
419
|
+
raise ImpossibleMergeException(
|
|
420
|
+
(
|
|
421
|
+
f"Impossible merging {self.name} from {self.source_dict['$file_name$']} with {other.name} ",
|
|
422
|
+
f"from {other.source_dict['$file_name$']}, can not change branch to {other.type.value}.",
|
|
423
|
+
)
|
|
424
|
+
)
|
|
425
|
+
elif not self.is_branch() and other.is_branch():
|
|
426
|
+
raise ImpossibleMergeException(
|
|
427
|
+
f"Impossible merging {self.name} from {self.source_dict['$file_name$']} with {other.name} "
|
|
428
|
+
f"from {other.source_dict['$file_name$']}, can not change {self.type.value} to branch."
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
self.source_dict.update(other.source_dict)
|
|
432
|
+
self.unpack_source_dict()
|
|
433
|
+
|
|
434
|
+
def validate_and_set_datatype(self):
|
|
435
|
+
"""
|
|
436
|
+
For signals:
|
|
437
|
+
Validate that the data type string represents the corresponding VSSDataType enumeration
|
|
438
|
+
|
|
439
|
+
For properties:
|
|
440
|
+
Validate that
|
|
441
|
+
1. the data type string represents the corresponding VSSDataType enumeration, OR
|
|
442
|
+
2. the data type string refers to a struct name that is defined under the same branch.
|
|
443
|
+
Note that only struct names relative to the branch under which they are defined are checked.
|
|
444
|
+
Data types provided as fully qualified struct names are skipped from validation as they
|
|
445
|
+
require the entire tree to be rendered first. These are validated after the entire tree is rendered.
|
|
446
|
+
"""
|
|
447
|
+
is_array = ARRAY_SUBSCRIPT_OP in self.data_type_str
|
|
448
|
+
# get the base name without subscript decoration
|
|
449
|
+
undecorated_datatype_str = self.data_type_str.split(DEFAULT_SEPARATOR)[
|
|
450
|
+
-1
|
|
451
|
+
].replace(ARRAY_SUBSCRIPT_OP, "")
|
|
452
|
+
|
|
453
|
+
try:
|
|
454
|
+
self.datatype = VSSDataType.from_str(self.data_type_str)
|
|
455
|
+
|
|
456
|
+
if self.unit and self.unit.allowed_datatypes:
|
|
457
|
+
if not (
|
|
458
|
+
(undecorated_datatype_str in self.unit.allowed_datatypes)
|
|
459
|
+
or (
|
|
460
|
+
VSSDataType.is_numeric(self.datatype)
|
|
461
|
+
and "numeric" in self.unit.allowed_datatypes
|
|
462
|
+
)
|
|
463
|
+
):
|
|
464
|
+
logging.error(
|
|
465
|
+
"Datatype %s not allowed for unit %s",
|
|
466
|
+
self.data_type_str,
|
|
467
|
+
self.unit.id,
|
|
468
|
+
)
|
|
469
|
+
sys.exit(-1)
|
|
470
|
+
except KeyError as e:
|
|
471
|
+
if self.type == VSSType.PROPERTY:
|
|
472
|
+
# Fully Qualified name as data type name
|
|
473
|
+
if DEFAULT_SEPARATOR in self.data_type_str:
|
|
474
|
+
logging.info(
|
|
475
|
+
(
|
|
476
|
+
f"Qualified datatype name {self.data_type_str} provided in node {self.qualified_name()}. ",
|
|
477
|
+
"Semantic checks will be performed after the entire tree is rendered. SKIPPING NOW...",
|
|
478
|
+
)
|
|
479
|
+
)
|
|
480
|
+
else:
|
|
481
|
+
# Custom data types can contain names defined under the
|
|
482
|
+
# same branch
|
|
483
|
+
struct_fqn = self.get_struct_qualified_name(
|
|
484
|
+
undecorated_datatype_str
|
|
485
|
+
)
|
|
486
|
+
if struct_fqn is None:
|
|
487
|
+
logging.error(
|
|
488
|
+
f"Data type not found. Data Type: {undecorated_datatype_str}"
|
|
489
|
+
)
|
|
490
|
+
sys.exit(-1)
|
|
491
|
+
|
|
492
|
+
# replace data type with qualified name
|
|
493
|
+
if is_array:
|
|
494
|
+
self.data_type_str = struct_fqn + ARRAY_SUBSCRIPT_OP
|
|
495
|
+
else:
|
|
496
|
+
self.data_type_str = struct_fqn
|
|
497
|
+
elif self.is_signal():
|
|
498
|
+
# This is a signal possibly referencing a user-defined type.
|
|
499
|
+
# Just assign the string value for now. Validation will be
|
|
500
|
+
# performed after the entire tree is rendered.
|
|
501
|
+
logging.debug(
|
|
502
|
+
f"Possible struct-type encountered - {self.data_type_str} in node {self.name}. "
|
|
503
|
+
)
|
|
504
|
+
else:
|
|
505
|
+
raise e
|
|
506
|
+
self.datatype = None # reset the enum
|
|
507
|
+
|
|
508
|
+
def does_attribute_exist(
|
|
509
|
+
self, other: "VSSNode", attr_fn, other_attr_fn, other_filter_fn
|
|
510
|
+
):
|
|
511
|
+
"""
|
|
512
|
+
Returns whether the an attribute of this node exists as another attribute in the specified tree
|
|
513
|
+
|
|
514
|
+
Keyword arguments:
|
|
515
|
+
other: Tree root of the search tree
|
|
516
|
+
attr_fn: Attribute projection function for this node that takes in a VSSNode as argument.
|
|
517
|
+
other_attr_fn: Attribute projection function for nodes in the search tree. The argument is of type VSSNode.
|
|
518
|
+
other_filter_fn: A filter function for node search in the "other" tree. The argument is of type VSSNode.
|
|
519
|
+
"""
|
|
520
|
+
key = attr_fn(self)
|
|
521
|
+
return key in set(VSSNode.get_tree_attrs(other, other_attr_fn, other_filter_fn))
|
|
522
|
+
|
|
523
|
+
@staticmethod
|
|
524
|
+
def node_exists(root, node_name) -> bool:
|
|
525
|
+
"""Checks if a node with the name provided to this method exists
|
|
526
|
+
Args:
|
|
527
|
+
root: root node of tree or root of search if search is applied to subtree
|
|
528
|
+
node_name: name of the node that is searched for. Full path (excluding root) is required.
|
|
529
|
+
"""
|
|
530
|
+
try:
|
|
531
|
+
r = Resolver()
|
|
532
|
+
r.get(root, node_name)
|
|
533
|
+
return True
|
|
534
|
+
except ChildResolverError:
|
|
535
|
+
return False
|
|
536
|
+
|
|
537
|
+
def verify_attributes(self, abort_on_unknown_attribute: bool):
|
|
538
|
+
"""
|
|
539
|
+
Validates a VSS object. Checks if it has the minimum parameters (description, type, uuid) and if the optional
|
|
540
|
+
parameters are supported within the specification.
|
|
541
|
+
Should not be used directly on overlays as the requirement to have all present is not relevant for
|
|
542
|
+
overlays, but rather for the final result after emrging.
|
|
543
|
+
"""
|
|
544
|
+
|
|
545
|
+
# Type presence should have been tested earlier, but is tested here again for completeness
|
|
546
|
+
if "type" not in self.source_dict.keys():
|
|
547
|
+
logging.error("Invalid VSS element %s, must have type", self.name)
|
|
548
|
+
sys.exit(-1)
|
|
549
|
+
|
|
550
|
+
if "description" not in self.source_dict.keys():
|
|
551
|
+
logging.error("Invalid VSS element %s, must have description", self.name)
|
|
552
|
+
sys.exit(-1)
|
|
553
|
+
|
|
554
|
+
unknown = []
|
|
555
|
+
for aKey in self.source_dict.keys():
|
|
556
|
+
if (
|
|
557
|
+
aKey not in VSSNode.core_attributes
|
|
558
|
+
and aKey not in VSSNode.whitelisted_extended_attributes
|
|
559
|
+
):
|
|
560
|
+
unknown.append(aKey)
|
|
561
|
+
|
|
562
|
+
unknown_found = False
|
|
563
|
+
if len(unknown) > 0:
|
|
564
|
+
logging.warning(
|
|
565
|
+
f"Attribute(s) {', '.join(map(str, unknown))} in element {self.name} not a core "
|
|
566
|
+
"or known extended attribute."
|
|
567
|
+
)
|
|
568
|
+
unknown_found = True
|
|
569
|
+
|
|
570
|
+
if "default" in self.source_dict.keys():
|
|
571
|
+
if self.source_dict["type"] not in {
|
|
572
|
+
"attribute",
|
|
573
|
+
"property",
|
|
574
|
+
"sensor",
|
|
575
|
+
"actuator",
|
|
576
|
+
}:
|
|
577
|
+
logging.warning(
|
|
578
|
+
"Invalid VSS element %s, %s cannot use default",
|
|
579
|
+
self.name,
|
|
580
|
+
self.source_dict["type"],
|
|
581
|
+
)
|
|
582
|
+
unknown_found = True
|
|
583
|
+
|
|
584
|
+
if unknown_found and abort_on_unknown_attribute:
|
|
585
|
+
logging.error("You asked for strict checking. Terminating.")
|
|
586
|
+
sys.exit(-1)
|
|
587
|
+
|
|
588
|
+
@staticmethod
|
|
589
|
+
def get_tree_attrs(node: "VSSNode", proj_fn, filter_fn) -> List[Any]:
|
|
590
|
+
"""
|
|
591
|
+
Collect all attributes of tree nodes rooted at `node` by applying the specified projection and filter function.
|
|
592
|
+
|
|
593
|
+
Keyword arguments:
|
|
594
|
+
node: The tree root node
|
|
595
|
+
proj_fn: A function that takes in the tree node as input and returns a node attribute (projection)
|
|
596
|
+
filter_fn: A function that takes in the tree node as input and
|
|
597
|
+
returns True if the node should be processed in the result set/False otherwise.
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
Projection of nodes that meet the filter criterion specified.
|
|
601
|
+
"""
|
|
602
|
+
return [proj_fn(n) for _, _, n in RenderTree(node) if filter_fn(n)]
|
vspec/py.typed
ADDED
|
File without changes
|
vspec/utils/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
# Copyright (c) 2023 Contributors to COVESA
|
|
3
|
+
#
|
|
4
|
+
# This program and the accompanying materials are made available under the
|
|
5
|
+
# terms of the Mozilla Public License 2.0 which is available at
|
|
6
|
+
# https://www.mozilla.org/en-US/MPL/2.0/
|
|
7
|
+
#
|
|
8
|
+
# SPDX-License-Identifier: MPL-2.0
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def remove_options_argparse(parser: argparse.ArgumentParser, options) -> None:
|
|
14
|
+
# ToDo: this allows removing arguments from argparse but involves accessing protected members
|
|
15
|
+
# also when removing '--format' it removes the positional argument vspec_file
|
|
16
|
+
for option in options:
|
|
17
|
+
for action in parser._actions:
|
|
18
|
+
if (
|
|
19
|
+
vars(action)["option_strings"]
|
|
20
|
+
and vars(action)["option_strings"][0] == option
|
|
21
|
+
):
|
|
22
|
+
parser._handle_conflict_resolve(action, [(option, action)])
|
|
23
|
+
break
|