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.
Files changed (39) hide show
  1. vspec/__init__.py +996 -0
  2. vspec/loggingconfig.py +22 -0
  3. vspec/model/__init__.py +7 -0
  4. vspec/model/constants.py +307 -0
  5. vspec/model/exceptions.py +29 -0
  6. vspec/model/vsstree.py +602 -0
  7. vspec/py.typed +0 -0
  8. vspec/utils/__init__.py +23 -0
  9. vspec/utils/idgen_utils.py +97 -0
  10. vspec/utils/stringstyle.py +25 -0
  11. vspec/utils/vss2id_val.py +165 -0
  12. vspec/vspec2vss_config.py +53 -0
  13. vspec/vspec2x.py +191 -0
  14. vspec/vss2x.py +38 -0
  15. vspec/vssexporters/__init__.py +7 -0
  16. vspec/vssexporters/vss2binary.py +147 -0
  17. vspec/vssexporters/vss2csv.py +82 -0
  18. vspec/vssexporters/vss2ddsidl.py +295 -0
  19. vspec/vssexporters/vss2franca.py +98 -0
  20. vspec/vssexporters/vss2graphql.py +143 -0
  21. vspec/vssexporters/vss2id.py +172 -0
  22. vspec/vssexporters/vss2json.py +116 -0
  23. vspec/vssexporters/vss2jsonschema.py +159 -0
  24. vspec/vssexporters/vss2protobuf.py +171 -0
  25. vspec/vssexporters/vss2yaml.py +122 -0
  26. vss_tools-0.5.0.dev0.data/scripts/vspec2csv.py +29 -0
  27. vss_tools-0.5.0.dev0.data/scripts/vspec2ddsidl.py +29 -0
  28. vss_tools-0.5.0.dev0.data/scripts/vspec2franca.py +29 -0
  29. vss_tools-0.5.0.dev0.data/scripts/vspec2graphql.py +29 -0
  30. vss_tools-0.5.0.dev0.data/scripts/vspec2id.py +28 -0
  31. vss_tools-0.5.0.dev0.data/scripts/vspec2json.py +29 -0
  32. vss_tools-0.5.0.dev0.data/scripts/vspec2jsonschema.py +29 -0
  33. vss_tools-0.5.0.dev0.data/scripts/vspec2protobuf.py +29 -0
  34. vss_tools-0.5.0.dev0.data/scripts/vspec2yaml.py +29 -0
  35. vss_tools-0.5.0.dev0.dist-info/LICENSE +373 -0
  36. vss_tools-0.5.0.dev0.dist-info/METADATA +65 -0
  37. vss_tools-0.5.0.dev0.dist-info/RECORD +39 -0
  38. vss_tools-0.5.0.dev0.dist-info/WHEEL +5 -0
  39. 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
@@ -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