udsdoc 0.66.0__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 (160) hide show
  1. udsdoc/__init__.py +288 -0
  2. udsdoc/__main__.py +7 -0
  3. udsdoc/_compat.py +74 -0
  4. udsdoc/_d3_interactive.py +324 -0
  5. udsdoc/_reprogramming.py +1288 -0
  6. udsdoc/_rich_output.py +208 -0
  7. udsdoc/_serialization.py +331 -0
  8. udsdoc/_style_presets.py +328 -0
  9. udsdoc/ap_parser.py +410 -0
  10. udsdoc/autosar_compat.py +208 -0
  11. udsdoc/boot_overlay.py +506 -0
  12. udsdoc/c_scanner.py +393 -0
  13. udsdoc/cantp_parser.py +493 -0
  14. udsdoc/cli.py +2299 -0
  15. udsdoc/comm_arxml_parser.py +420 -0
  16. udsdoc/completion.py +260 -0
  17. udsdoc/compliance_matrix.py +1357 -0
  18. udsdoc/config.py +64 -0
  19. udsdoc/csv_to_arxml.py +745 -0
  20. udsdoc/dem_parser.py +978 -0
  21. udsdoc/diff.py +562 -0
  22. udsdoc/doip_parser.py +268 -0
  23. udsdoc/ecu_log_analyzer.py +379 -0
  24. udsdoc/ecu_simulator.py +432 -0
  25. udsdoc/ecu_test/__init__.py +84 -0
  26. udsdoc/ecu_test/coverage.py +232 -0
  27. udsdoc/ecu_test/executor.py +296 -0
  28. udsdoc/ecu_test/formal_generator.py +298 -0
  29. udsdoc/ecu_test/fsm.py +565 -0
  30. udsdoc/ecu_test/generator.py +697 -0
  31. udsdoc/ecu_test/logger.py +532 -0
  32. udsdoc/ecu_test/models.py +122 -0
  33. udsdoc/ecu_test/transport.py +256 -0
  34. udsdoc/generator.py +7041 -0
  35. udsdoc/hardware.py +431 -0
  36. udsdoc/i18n.py +690 -0
  37. udsdoc/interactive.py +544 -0
  38. udsdoc/iso_versions.py +281 -0
  39. udsdoc/llm_integration.py +1436 -0
  40. udsdoc/lsp_server.py +322 -0
  41. udsdoc/models.py +1957 -0
  42. udsdoc/multi_ecu/__init__.py +59 -0
  43. udsdoc/multi_ecu/generator.py +2012 -0
  44. udsdoc/multi_ecu/inference.py +104 -0
  45. udsdoc/multi_ecu/loader.py +315 -0
  46. udsdoc/multi_ecu/models.py +406 -0
  47. udsdoc/multi_ecu/validator.py +207 -0
  48. udsdoc/multi_ecu/visualizations.py +379 -0
  49. udsdoc/obd_parser.py +245 -0
  50. udsdoc/odx/__init__.py +228 -0
  51. udsdoc/odx/mapper.py +1468 -0
  52. udsdoc/odx/models_odx.py +996 -0
  53. udsdoc/odx/pdx_packager.py +347 -0
  54. udsdoc/odx/serializers/__init__.py +85 -0
  55. udsdoc/odx/serializers/_shared.py +664 -0
  56. udsdoc/odx/serializers/odx_c.py +188 -0
  57. udsdoc/odx/serializers/odx_cs.py +143 -0
  58. udsdoc/odx/serializers/odx_d.py +935 -0
  59. udsdoc/odx/serializers/odx_e.py +268 -0
  60. udsdoc/odx/serializers/odx_f.py +260 -0
  61. udsdoc/odx/serializers/odx_fd.py +168 -0
  62. udsdoc/odx/serializers/odx_m.py +187 -0
  63. udsdoc/odx/serializers/odx_v.py +429 -0
  64. udsdoc/odx/supplement.py +342 -0
  65. udsdoc/odx/xsd/LICENSE +48 -0
  66. udsdoc/odx/xsd/odx-c.xsd +26 -0
  67. udsdoc/odx/xsd/odx-cs.xsd +26 -0
  68. udsdoc/odx/xsd/odx-d.xsd +28 -0
  69. udsdoc/odx/xsd/odx-e.xsd +27 -0
  70. udsdoc/odx/xsd/odx-f.xsd +26 -0
  71. udsdoc/odx/xsd/odx-fd.xsd +26 -0
  72. udsdoc/odx/xsd/odx-m.xsd +26 -0
  73. udsdoc/odx/xsd/odx-v.xsd +26 -0
  74. udsdoc/odx/xsd_validator.py +180 -0
  75. udsdoc/ollama_setup.py +513 -0
  76. udsdoc/parser.py +3480 -0
  77. udsdoc/plugin.py +110 -0
  78. udsdoc/py.typed +0 -0
  79. udsdoc/pytest_plugin.py +353 -0
  80. udsdoc/repo_scanner.py +1137 -0
  81. udsdoc/samples/boot_overlay_minimal.yaml +31 -0
  82. udsdoc/samples/boot_overlay_sample.yaml +103 -0
  83. udsdoc/samples/compliance_matrix_oem_annex.csv +103 -0
  84. udsdoc/samples/compliance_matrix_oem_did.csv +102 -0
  85. udsdoc/samples/compliance_matrix_oem_docan_ecu.csv +101 -0
  86. udsdoc/samples/compliance_matrix_oem_docan_standard.csv +102 -0
  87. udsdoc/samples/compliance_matrix_oem_dtc.csv +118 -0
  88. udsdoc/samples/compliance_matrix_oem_reprogramming.csv +99 -0
  89. udsdoc/samples/compliance_matrix_oem_rid.csv +106 -0
  90. udsdoc/samples/compliance_matrix_oem_sample.csv +7 -0
  91. udsdoc/samples/compliance_matrix_sources_sample.yaml +55 -0
  92. udsdoc/samples/compliance_matrix_supplier_sample.csv +5 -0
  93. udsdoc/samples/header_config_sample.yaml +22 -0
  94. udsdoc/samples/llm_config_ollama.json +9 -0
  95. udsdoc/samples/llm_config_openrouter.json +7 -0
  96. udsdoc/samples/odx_supplement_sample.yaml +87 -0
  97. udsdoc/samples/sample1_cantp.arxml +243 -0
  98. udsdoc/samples/sample1_dcm.arxml +2384 -0
  99. udsdoc/samples/sample1_dem.arxml +493 -0
  100. udsdoc/samples/sample1_did.c +127 -0
  101. udsdoc/samples/sample1_rid.c +104 -0
  102. udsdoc/samples/sample2_cantp.arxml +243 -0
  103. udsdoc/samples/sample2_dcm.arxml +1966 -0
  104. udsdoc/samples/sample2_dem.arxml +493 -0
  105. udsdoc/samples/sample2_did.c +110 -0
  106. udsdoc/samples/sample2_rid.c +104 -0
  107. udsdoc/samples/style_preset_demo.yaml +36 -0
  108. udsdoc/samples/system_redundant.yaml +76 -0
  109. udsdoc/samples/udsspec.cls +113 -0
  110. udsdoc/schema.py +286 -0
  111. udsdoc/templates/assets/mermaid.min.js +2029 -0
  112. udsdoc/templates/assets/search.js +138 -0
  113. udsdoc/templates/assets/tikz-uml.sty +5376 -0
  114. udsdoc/templates/structured_tex/root.tex.j2 +197 -0
  115. udsdoc/templates/structured_tex/sections/0_intro/0_short_descript/short_descript.tex.j2 +113 -0
  116. udsdoc/templates/structured_tex/sections/0_intro/1_about_doc/about_doc.tex.j2 +79 -0
  117. udsdoc/templates/structured_tex/sections/0_intro/2_context/context.tex.j2 +75 -0
  118. udsdoc/templates/structured_tex/sections/0_intro/intro.tex.j2 +3 -0
  119. udsdoc/templates/structured_tex/sections/1_low_layer/2_datalink/datalink.tex.j2 +22 -0
  120. udsdoc/templates/structured_tex/sections/1_low_layer/3_network/network.tex.j2 +87 -0
  121. udsdoc/templates/structured_tex/sections/1_low_layer/4_transport/transport.tex.j2 +146 -0
  122. udsdoc/templates/structured_tex/sections/1_low_layer/5_session/session.tex.j2 +127 -0
  123. udsdoc/templates/structured_tex/sections/1_low_layer/low_layer.tex.j2 +7 -0
  124. udsdoc/templates/structured_tex/sections/2_high_layer/09_overview/overview.tex.j2 +66 -0
  125. udsdoc/templates/structured_tex/sections/2_high_layer/10-2_sid10/sid10.tex.j2 +148 -0
  126. udsdoc/templates/structured_tex/sections/2_high_layer/10-3_sid11/sid11.tex.j2 +130 -0
  127. udsdoc/templates/structured_tex/sections/2_high_layer/10-4_sid27/sid27.tex.j2 +312 -0
  128. udsdoc/templates/structured_tex/sections/2_high_layer/10-5_sid28/sid28.tex.j2 +136 -0
  129. udsdoc/templates/structured_tex/sections/2_high_layer/10-6_sid29/sid29.tex.j2 +144 -0
  130. udsdoc/templates/structured_tex/sections/2_high_layer/10-7_sid3E/sid3E.tex.j2 +127 -0
  131. udsdoc/templates/structured_tex/sections/2_high_layer/10-8_sid85/sid85.tex.j2 +136 -0
  132. udsdoc/templates/structured_tex/sections/2_high_layer/11-2_sid22/sid22.tex.j2 +137 -0
  133. udsdoc/templates/structured_tex/sections/2_high_layer/11-7_sid2E/sid2E.tex.j2 +140 -0
  134. udsdoc/templates/structured_tex/sections/2_high_layer/12-2_sid14/sid14.tex.j2 +122 -0
  135. udsdoc/templates/structured_tex/sections/2_high_layer/12-3_sid19/sid19.tex.j2 +126 -0
  136. udsdoc/templates/structured_tex/sections/2_high_layer/14-2_sid31/sid31.tex.j2 +184 -0
  137. udsdoc/templates/structured_tex/sections/2_high_layer/15-2_sid34/sid34.tex.j2 +127 -0
  138. udsdoc/templates/structured_tex/sections/2_high_layer/15-4_sid36/sid36.tex.j2 +131 -0
  139. udsdoc/templates/structured_tex/sections/2_high_layer/15-5_sid37/sid37.tex.j2 +118 -0
  140. udsdoc/templates/structured_tex/sections/2_high_layer/high_layer.tex.j2 +50 -0
  141. udsdoc/templates/structured_tex/sections/3_bootloader/bootloader.tex.j2 +8 -0
  142. udsdoc/templates/structured_tex/sections/A_annex/annex.tex.j2 +249 -0
  143. udsdoc/templates/structured_tex/sections/C_references/references.tex.j2 +31 -0
  144. udsdoc/templates/structured_tex/sections/_cm_trace.tex.j2 +36 -0
  145. udsdoc/templates/structured_tex/sections/section.tex.j2 +10 -0
  146. udsdoc/templates/tikz-uml.sty +5386 -0
  147. udsdoc/templates/uds_spec.html.j2 +737 -0
  148. udsdoc/templates/uds_spec.tex.j2 +2248 -0
  149. udsdoc/templates/udsspec.cls +130 -0
  150. udsdoc/templates/web_ui.html +4548 -0
  151. udsdoc/tikz_svg.py +213 -0
  152. udsdoc/validator.py +450 -0
  153. udsdoc/visualizations.py +441 -0
  154. udsdoc/webserver.py +4732 -0
  155. udsdoc-0.66.0.dist-info/METADATA +347 -0
  156. udsdoc-0.66.0.dist-info/RECORD +160 -0
  157. udsdoc-0.66.0.dist-info/WHEEL +5 -0
  158. udsdoc-0.66.0.dist-info/entry_points.txt +6 -0
  159. udsdoc-0.66.0.dist-info/licenses/LICENSE +21 -0
  160. udsdoc-0.66.0.dist-info/top_level.txt +1 -0
udsdoc/__init__.py ADDED
@@ -0,0 +1,288 @@
1
+ """udsdoc - Convert AUTOSAR DCM/CanTp/DEM arxml to UDS specification documents and ISO 22901-1 ODX/PDX."""
2
+
3
+ __version__ = "0.66.0"
4
+
5
+ # LLM features — submodule import fails fast if anthropic is missing only when
6
+ # its functions are actually called.
7
+ from udsdoc import (
8
+ ecu_test, # noqa: F401
9
+ llm_integration, # noqa: F401
10
+ )
11
+ from udsdoc.ap_parser import ApParser
12
+ from udsdoc.boot_overlay import (
13
+ BootloaderInfo,
14
+ BootOverlay,
15
+ DomainCanIds,
16
+ DomainMap,
17
+ FlashSegment,
18
+ empty_overlay,
19
+ load_overlay,
20
+ )
21
+ from udsdoc.boot_overlay import (
22
+ apply_to_spec as apply_boot_overlay,
23
+ )
24
+ from udsdoc.c_scanner import scan_did_variables, scan_rid_variables
25
+ from udsdoc.cantp_parser import CanTpParser
26
+ from udsdoc.comm_arxml_parser import CommArxmlParser
27
+ from udsdoc.compliance_matrix import (
28
+ ComplianceMatrixDocument,
29
+ ComplianceMatrixEntry,
30
+ ComplianceMatrixSource,
31
+ ComplianceMatrixTrace,
32
+ load_compliance_matrix_csv,
33
+ load_compliance_matrix_csvs,
34
+ load_compliance_matrix_document,
35
+ load_compliance_matrix_sources,
36
+ )
37
+ from udsdoc.compliance_matrix import (
38
+ match_to_spec as match_compliance_matrix_to_spec,
39
+ )
40
+ from udsdoc.config import GenerationConfig
41
+ from udsdoc.dem_parser import DemParser
42
+ from udsdoc.diff import DiffEntry, DiffKind, SpecDiff, SpecDiffer, diff_specs
43
+ from udsdoc.doip_parser import DoIpParser
44
+ from udsdoc.generator import TexGenerator
45
+ from udsdoc.i18n import SUPPORTED_LANGS, Translator
46
+ from udsdoc.i18n import t as translate
47
+ from udsdoc.interactive import InteractiveSession
48
+ from udsdoc.models import (
49
+ AccessTimingRow,
50
+ ApConfig,
51
+ ApDiagnosticService,
52
+ ApEvent,
53
+ ApField,
54
+ ApMethod,
55
+ ApServiceInterface,
56
+ AuthenticationConfig,
57
+ CanTpChannel,
58
+ CanTpConfig,
59
+ CommunicationConfig,
60
+ DataElement,
61
+ DemCombinedDtcEntry,
62
+ DemDebounceConfig,
63
+ DemDtcEntry,
64
+ DemEnableCondition,
65
+ DemEventMemoryConfig,
66
+ DemEventParameter,
67
+ DemExtendedDataClass,
68
+ DemFreezeFrameClass,
69
+ DemGeneralConfig,
70
+ DemIndicatorConfig,
71
+ DemOperationCycle,
72
+ DemStorageCondition,
73
+ DiagSession,
74
+ Did,
75
+ DoIpConfig,
76
+ DoIpLogicalAddress,
77
+ DoIpRoutingActivation,
78
+ DslConnection,
79
+ DtcGroup,
80
+ EcuInstance,
81
+ Frame,
82
+ FrameSignal,
83
+ ISignal,
84
+ LinkControlBaudrateEntry,
85
+ NetworkTopology,
86
+ NrcEntry,
87
+ ObdConfig,
88
+ ObdPid,
89
+ Pdu,
90
+ ProtocolRow,
91
+ Routine,
92
+ RoutineParameter,
93
+ SecurityLevel,
94
+ SubFunction,
95
+ UdsService,
96
+ UdsSpecification,
97
+ )
98
+ from udsdoc.obd_parser import ObdParser
99
+ from udsdoc.odx import (
100
+ OdxCategory,
101
+ OdxDocument,
102
+ empty_supplement,
103
+ load_supplement,
104
+ map_ecu_system_to_odx,
105
+ map_spec_to_odx,
106
+ )
107
+ from udsdoc.parser import ArxmlParser, ArxmlValidationError, ArxmlValidationWarning
108
+ from udsdoc.plugin import GeneratorPlugin, ParserPlugin, PluginRegistry, registry
109
+ from udsdoc.repo_scanner import (
110
+ EcuPartition,
111
+ RepoScanResult,
112
+ classify_arxml,
113
+ detect_ecu_mode,
114
+ load_from_repo,
115
+ load_system_from_repo,
116
+ partition_by_ecu,
117
+ scan_repo,
118
+ )
119
+ from udsdoc.schema import generate_json_schema, generate_pydantic_models, save_json_schema
120
+ from udsdoc.validator import UdsValidator, ValidationIssue, ValidationResult, validate
121
+ from udsdoc.visualizations import (
122
+ generate_all_visualizations,
123
+ generate_security_did_heatmap,
124
+ generate_session_service_heatmap,
125
+ generate_timing_diagram,
126
+ )
127
+
128
+ __all__ = [
129
+ "ecu_test",
130
+ "AccessTimingRow",
131
+ "ArxmlParser",
132
+ "ArxmlValidationError",
133
+ "ArxmlValidationWarning",
134
+ "AuthenticationConfig",
135
+ "CanTpParser",
136
+ "DemCombinedDtcEntry",
137
+ "DemDebounceConfig",
138
+ "DemDtcEntry",
139
+ "DemEnableCondition",
140
+ "DemEventMemoryConfig",
141
+ "DemEventParameter",
142
+ "DemExtendedDataClass",
143
+ "DemFreezeFrameClass",
144
+ "DemGeneralConfig",
145
+ "DemIndicatorConfig",
146
+ "DemOperationCycle",
147
+ "DemParser",
148
+ "DemStorageCondition",
149
+ "DslConnection",
150
+ "DtcGroup",
151
+ "GenerationConfig",
152
+ "InteractiveSession",
153
+ "LinkControlBaudrateEntry",
154
+ "ProtocolRow",
155
+ "TexGenerator",
156
+ "UdsSpecification",
157
+ "UdsService",
158
+ "DiagSession",
159
+ "SecurityLevel",
160
+ "Did",
161
+ "DataElement",
162
+ "Routine",
163
+ "RoutineParameter",
164
+ "SubFunction",
165
+ "NrcEntry",
166
+ "CanTpChannel",
167
+ "CanTpConfig",
168
+ "scan_did_variables",
169
+ "scan_rid_variables",
170
+ # Validation
171
+ "UdsValidator",
172
+ "ValidationIssue",
173
+ "ValidationResult",
174
+ "validate",
175
+ # Diff
176
+ "SpecDiffer",
177
+ "SpecDiff",
178
+ "DiffEntry",
179
+ "DiffKind",
180
+ "diff_specs",
181
+ # Plugin system
182
+ "ParserPlugin",
183
+ "GeneratorPlugin",
184
+ "PluginRegistry",
185
+ "registry",
186
+ # i18n
187
+ "Translator",
188
+ "SUPPORTED_LANGS",
189
+ "translate",
190
+ # DoIP (ISO 13400)
191
+ "DoIpConfig",
192
+ "DoIpLogicalAddress",
193
+ "DoIpRoutingActivation",
194
+ "DoIpParser",
195
+ # OBD (ISO 15031 / WWH-OBD)
196
+ "ObdConfig",
197
+ "ObdPid",
198
+ "ObdParser",
199
+ # Communication ARXML
200
+ "CommunicationConfig",
201
+ "Frame",
202
+ "FrameSignal",
203
+ "ISignal",
204
+ "Pdu",
205
+ "EcuInstance",
206
+ "NetworkTopology",
207
+ "CommArxmlParser",
208
+ # Adaptive Platform
209
+ "ApConfig",
210
+ "ApServiceInterface",
211
+ "ApMethod",
212
+ "ApEvent",
213
+ "ApField",
214
+ "ApDiagnosticService",
215
+ "ApParser",
216
+ # JSON Schema / Pydantic export
217
+ "generate_json_schema",
218
+ "save_json_schema",
219
+ "generate_pydantic_models",
220
+ # ODX / PDX (ISO 22901-1)
221
+ "OdxCategory",
222
+ "OdxDocument",
223
+ "empty_supplement",
224
+ "load_supplement",
225
+ "map_ecu_system_to_odx",
226
+ "map_spec_to_odx",
227
+ # Bootloader-domain overlay
228
+ "BootOverlay",
229
+ "BootloaderInfo",
230
+ "DomainCanIds",
231
+ "DomainMap",
232
+ "FlashSegment",
233
+ "apply_boot_overlay",
234
+ "empty_overlay",
235
+ "load_overlay",
236
+ # Repository auto-discovery
237
+ "EcuPartition",
238
+ "RepoScanResult",
239
+ "classify_arxml",
240
+ "detect_ecu_mode",
241
+ "load_from_repo",
242
+ "load_system_from_repo",
243
+ "partition_by_ecu",
244
+ "scan_repo",
245
+ # Compliance Matrix (ASPICE SYS.2.BP4 / ISO 26262-8 §6.4.5)
246
+ "ComplianceMatrixDocument",
247
+ "ComplianceMatrixEntry",
248
+ "ComplianceMatrixSource",
249
+ "ComplianceMatrixTrace",
250
+ "load_compliance_matrix_csv",
251
+ "load_compliance_matrix_csvs",
252
+ "load_compliance_matrix_document",
253
+ "load_compliance_matrix_sources",
254
+ "match_compliance_matrix_to_spec",
255
+ # Visualizations
256
+ "generate_all_visualizations",
257
+ "generate_security_did_heatmap",
258
+ "generate_session_service_heatmap",
259
+ "generate_timing_diagram",
260
+ ]
261
+
262
+ # ----------------------------------------------------------------------------
263
+ # Agent D — Style preset system (0.38)
264
+ # Generic-only presets (no OEM / vendor branding). Append below to keep
265
+ # merge conflicts with Agent E's reconciliation obvious.
266
+ # TODO (Agent E): fold these imports + names into the main import block
267
+ # and the literal ``__all__`` list once all agents have landed.
268
+ # ----------------------------------------------------------------------------
269
+ from udsdoc._style_presets import ( # noqa: F401, E402
270
+ HeaderConfig,
271
+ StylePreset,
272
+ empty_header_config,
273
+ get_preset,
274
+ list_presets,
275
+ load_header_config,
276
+ register_preset,
277
+ )
278
+
279
+ __all__ += [
280
+ # Style preset system
281
+ "HeaderConfig",
282
+ "StylePreset",
283
+ "empty_header_config",
284
+ "get_preset",
285
+ "list_presets",
286
+ "load_header_config",
287
+ "register_preset",
288
+ ]
udsdoc/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Allow running udsdoc as ``python -m udsdoc``."""
2
+
3
+ import sys
4
+
5
+ from udsdoc.cli import main
6
+
7
+ sys.exit(main())
udsdoc/_compat.py ADDED
@@ -0,0 +1,74 @@
1
+ """Backward-compatibility helpers for the udsxml2tex → udsdoc rename.
2
+
3
+ The project was published as ``udsxml2tex`` up to v0.65.x. Environment
4
+ variables, the user config directory and the console script kept that
5
+ prefix. This module lets the renamed package keep honouring the legacy
6
+ spellings for a deprecation period (planned removal: v1.0).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import warnings
13
+ from pathlib import Path
14
+ from typing import overload
15
+
16
+ _NEW_PREFIX = "UDSDOC_"
17
+ _LEGACY_PREFIX = "UDSXML2TEX_"
18
+
19
+ #: Legacy user config directory (``~/.udsxml2tex``), consulted as a
20
+ #: fallback when ``~/.udsdoc`` has no matching file.
21
+ LEGACY_CONFIG_DIR = ".udsxml2tex"
22
+
23
+
24
+ def _warn_legacy(old: str, new: str) -> None:
25
+ warnings.warn(
26
+ f"{old} is deprecated since the rename to udsdoc; "
27
+ f"use {new} instead (legacy support will be removed in v1.0)",
28
+ DeprecationWarning,
29
+ stacklevel=3,
30
+ )
31
+
32
+
33
+ @overload
34
+ def env_get(name: str) -> str | None: ...
35
+ @overload
36
+ def env_get(name: str, default: str) -> str: ...
37
+
38
+
39
+ def env_get(name: str, default: str | None = None) -> str | None:
40
+ """``os.environ.get`` honouring the legacy ``UDSXML2TEX_*`` prefix.
41
+
42
+ ``name`` must be the new ``UDSDOC_*`` spelling. When it is unset but
43
+ the legacy spelling is present, the legacy value is returned with a
44
+ :class:`DeprecationWarning`.
45
+ """
46
+ value = os.environ.get(name)
47
+ if value is not None:
48
+ return value
49
+ if name.startswith(_NEW_PREFIX):
50
+ legacy_name = _LEGACY_PREFIX + name[len(_NEW_PREFIX):]
51
+ legacy_value = os.environ.get(legacy_name)
52
+ if legacy_value is not None:
53
+ _warn_legacy(legacy_name, name)
54
+ return legacy_value
55
+ return default
56
+
57
+
58
+ def user_config_candidates(*relative: str) -> list[Path]:
59
+ """Paths for a user config file, new location first, legacy second.
60
+
61
+ ``user_config_candidates("llm.json")`` →
62
+ ``[~/.udsdoc/llm.json, ~/.udsxml2tex/llm.json]``.
63
+ """
64
+ home = Path.home()
65
+ return [
66
+ home.joinpath(".udsdoc", *relative),
67
+ home.joinpath(LEGACY_CONFIG_DIR, *relative),
68
+ ]
69
+
70
+
71
+ def warn_legacy_path(path: Path) -> None:
72
+ """Emit the deprecation warning for a legacy config path hit."""
73
+ if LEGACY_CONFIG_DIR in path.parts:
74
+ _warn_legacy(str(path), str(path).replace(LEGACY_CONFIG_DIR, ".udsdoc"))
@@ -0,0 +1,324 @@
1
+ """D3.js-based interactive visualizations for the HTML ZIP output.
2
+
3
+ Produces a self-contained HTML page (`interactive.html`) embedding a D3 v7
4
+ force-directed graph of the UDS specification's Sessions, Services, DIDs, and
5
+ Routines, with click-through filtering and a sidebar inspector panel.
6
+
7
+ The D3 library is loaded from a CDN (no bundling) — works offline once the
8
+ page has been visited once and the asset is cached, but does require initial
9
+ network access. To bundle D3 instead, drop `d3.v7.min.js` into the ZIP and
10
+ swap the <script src="..."> URL.
11
+
12
+ Public API:
13
+ render_interactive_page(spec, ecu_name) -> str # full HTML page
14
+ spec_to_d3_json(spec) -> dict # serialisable graph data
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ from typing import Any
21
+
22
+
23
+ def spec_to_d3_json(spec: Any) -> dict:
24
+ """Convert a UdsSpecification into a {nodes, links} dict for D3."""
25
+ nodes: list[dict] = []
26
+ links: list[dict] = []
27
+
28
+ # Nodes
29
+ for s in (spec.sessions or []):
30
+ nodes.append({
31
+ "id": f"sess:{s.session_id}",
32
+ "label": s.short_name,
33
+ "type": "session",
34
+ "hex": f"0x{s.session_id:02X}",
35
+ })
36
+ for sec in (spec.security_levels or []):
37
+ nodes.append({
38
+ "id": f"sec:{sec.security_level}",
39
+ "label": sec.short_name,
40
+ "type": "security",
41
+ "hex": f"0x{sec.security_level:02X}",
42
+ })
43
+ for svc in (spec.services or []):
44
+ nodes.append({
45
+ "id": f"svc:{svc.service_id}",
46
+ "label": svc.short_name,
47
+ "type": "service",
48
+ "hex": f"0x{svc.service_id:02X}",
49
+ "sub_funcs": len(svc.sub_functions or []),
50
+ "nrcs": len(svc.nrc_list or []),
51
+ })
52
+ for did in (spec.dids or []):
53
+ nodes.append({
54
+ "id": f"did:{did.did_id}",
55
+ "label": did.short_name,
56
+ "type": "did",
57
+ "hex": f"0x{did.did_id:04X}",
58
+ "read": bool(did.read_access),
59
+ "write": bool(did.write_access),
60
+ })
61
+ for rt in (spec.routines or []):
62
+ nodes.append({
63
+ "id": f"rid:{rt.routine_id}",
64
+ "label": rt.short_name,
65
+ "type": "routine",
66
+ "hex": f"0x{rt.routine_id:04X}",
67
+ "ops": (
68
+ ("S" if rt.start_supported else "")
69
+ + ("X" if rt.stop_supported else "")
70
+ + ("R" if rt.result_supported else "")
71
+ ),
72
+ })
73
+
74
+ # Index by short name for cross-references
75
+ sess_by_name = {s.short_name: f"sess:{s.session_id}"
76
+ for s in (spec.sessions or [])}
77
+ sec_by_name = {s.short_name: f"sec:{s.security_level}"
78
+ for s in (spec.security_levels or [])}
79
+
80
+ # Service → session links
81
+ for svc in (spec.services or []):
82
+ for sname in (svc.supported_sessions or []):
83
+ target = sess_by_name.get(sname)
84
+ if target:
85
+ links.append({"source": f"svc:{svc.service_id}",
86
+ "target": target, "kind": "supports"})
87
+ for sname in (svc.required_security_levels or []):
88
+ target = sec_by_name.get(sname)
89
+ if target:
90
+ links.append({"source": f"svc:{svc.service_id}",
91
+ "target": target, "kind": "requires-sec"})
92
+
93
+ # DID → session / security
94
+ for did in (spec.dids or []):
95
+ for sname in (did.supported_sessions or []):
96
+ target = sess_by_name.get(sname)
97
+ if target:
98
+ links.append({"source": f"did:{did.did_id}",
99
+ "target": target, "kind": "supports"})
100
+ for sname in (did.required_security_levels or []):
101
+ target = sec_by_name.get(sname)
102
+ if target:
103
+ links.append({"source": f"did:{did.did_id}",
104
+ "target": target, "kind": "requires-sec"})
105
+ # DIDs are operated on by 0x22 (read) and 0x2E (write)
106
+ if did.read_access:
107
+ for svc in (spec.services or []):
108
+ if svc.service_id == 0x22:
109
+ links.append({"source": f"svc:{svc.service_id}",
110
+ "target": f"did:{did.did_id}",
111
+ "kind": "reads"})
112
+ break
113
+ if did.write_access:
114
+ for svc in (spec.services or []):
115
+ if svc.service_id == 0x2E:
116
+ links.append({"source": f"svc:{svc.service_id}",
117
+ "target": f"did:{did.did_id}",
118
+ "kind": "writes"})
119
+ break
120
+
121
+ # Routine → session / security; RoutineControl 0x31 → routines
122
+ for rt in (spec.routines or []):
123
+ for sname in (rt.supported_sessions or []):
124
+ target = sess_by_name.get(sname)
125
+ if target:
126
+ links.append({"source": f"rid:{rt.routine_id}",
127
+ "target": target, "kind": "supports"})
128
+ for sname in (rt.required_security_levels or []):
129
+ target = sec_by_name.get(sname)
130
+ if target:
131
+ links.append({"source": f"rid:{rt.routine_id}",
132
+ "target": target, "kind": "requires-sec"})
133
+ for svc in (spec.services or []):
134
+ if svc.service_id == 0x31:
135
+ links.append({"source": f"svc:{svc.service_id}",
136
+ "target": f"rid:{rt.routine_id}",
137
+ "kind": "controls"})
138
+ break
139
+
140
+ return {"nodes": nodes, "links": links}
141
+
142
+
143
+ def render_interactive_page(spec: Any, ecu_name: str = "") -> str:
144
+ """Return a full self-contained HTML page with D3 force-directed graph."""
145
+ data = spec_to_d3_json(spec)
146
+ json_blob = json.dumps(data, ensure_ascii=False)
147
+
148
+ return r"""<!DOCTYPE html>
149
+ <html lang="en">
150
+ <head>
151
+ <meta charset="UTF-8">
152
+ <title>UDS Spec — Interactive Graph (""" + ecu_name + r""")</title>
153
+ <script src="https://d3js.org/d3.v7.min.js"></script>
154
+ <style>
155
+ * { box-sizing: border-box; }
156
+ body { margin: 0; font-family: Georgia, serif; background: #f4f3ef; color: #111; }
157
+ #container { display: flex; height: 100vh; }
158
+ #graph { flex: 1; background: #ffffff; }
159
+ #panel { width: 320px; padding: 1.2em; background: #fafafa; border-left: 1px solid #c8c8c4;
160
+ font-family: Arial, sans-serif; font-size: 13px; overflow-y: auto; }
161
+ #panel h1 { font-family: Georgia, serif; font-size: 1.2em; margin-top: 0;
162
+ border-bottom: 2px solid #111; padding-bottom: 0.3em; }
163
+ #panel h2 { font-family: Georgia, serif; font-size: 1em; margin-top: 1.2em;
164
+ border-bottom: 1px solid #999; padding-bottom: 0.15em; }
165
+ #panel .hex { font-family: 'Courier New', monospace; background: #ebebea; padding: 1px 5px; }
166
+ #legend span { display: inline-block; width: 12px; height: 12px; margin-right: 4px;
167
+ border-radius: 50%; vertical-align: middle; }
168
+ #legend li { margin: 3px 0; list-style: none; }
169
+ .toolbar { padding: 6px 12px; background: #3a3a3a; color: #ddd; font-size: 12px; font-family: Arial, sans-serif; }
170
+ .toolbar button { background: #555; color: #eee; border: 1px solid #444; padding: 3px 10px;
171
+ margin-right: 6px; cursor: pointer; font-family: inherit; }
172
+ .toolbar button:hover { background: #666; }
173
+ .toolbar input[type=text] { padding: 3px 6px; background: #444; color: #eee; border: 1px solid #555; }
174
+ .node { stroke: #fff; stroke-width: 1px; cursor: pointer; }
175
+ .node.dim { opacity: 0.15; }
176
+ .link { stroke: #aaa; stroke-opacity: 0.4; fill: none; }
177
+ .link.highlight { stroke: #cc4400; stroke-opacity: 0.9; stroke-width: 2px; }
178
+ .label { font-size: 9px; font-family: Arial, sans-serif; pointer-events: none; fill: #222; }
179
+ </style>
180
+ </head>
181
+ <body>
182
+ <div id="container">
183
+ <div style="flex:1; display:flex; flex-direction:column;">
184
+ <div class="toolbar">
185
+ <button id="btn-reset">Reset</button>
186
+ <input type="text" id="search" placeholder="Search by name…" size="20">
187
+ <label style="margin-left:1em"><input type="checkbox" id="show-labels" checked> Labels</label>
188
+ <span style="margin-left:1em">""" + ecu_name + r"""</span>
189
+ </div>
190
+ <svg id="graph"></svg>
191
+ </div>
192
+ <aside id="panel">
193
+ <h1>UDS Spec Inspector</h1>
194
+ <div id="info">
195
+ <p>Click a node to inspect. Drag nodes to reposition. Search to highlight.</p>
196
+ </div>
197
+ <h2>Legend</h2>
198
+ <ul id="legend">
199
+ <li><span style="background:#2a6e2a"></span>Session</li>
200
+ <li><span style="background:#996600"></span>Security level</li>
201
+ <li><span style="background:#334466"></span>Service</li>
202
+ <li><span style="background:#aa2222"></span>DID</li>
203
+ <li><span style="background:#664488"></span>Routine</li>
204
+ </ul>
205
+ <h2>Statistics</h2>
206
+ <div id="stats"></div>
207
+ </aside>
208
+ </div>
209
+ <script>
210
+ const data = """ + json_blob + r""";
211
+
212
+ const colorMap = {
213
+ session: '#2a6e2a',
214
+ security: '#996600',
215
+ service: '#334466',
216
+ did: '#aa2222',
217
+ routine: '#664488',
218
+ };
219
+ const radiusMap = { session: 10, security: 10, service: 9, did: 7, routine: 7 };
220
+
221
+ const svg = d3.select('#graph');
222
+ let width = window.innerWidth - 320;
223
+ let height = window.innerHeight - 36;
224
+ svg.attr('viewBox', [0, 0, width, height]);
225
+
226
+ const g = svg.append('g');
227
+ const linkSel = g.append('g').selectAll('line').data(data.links).enter()
228
+ .append('line').attr('class', 'link');
229
+ const nodeSel = g.append('g').selectAll('circle').data(data.nodes).enter()
230
+ .append('circle').attr('class', 'node')
231
+ .attr('r', d => radiusMap[d.type] || 6)
232
+ .attr('fill', d => colorMap[d.type] || '#888')
233
+ .on('click', (e, d) => inspect(d));
234
+ const labelSel = g.append('g').selectAll('text').data(data.nodes).enter()
235
+ .append('text').attr('class', 'label')
236
+ .text(d => d.hex)
237
+ .attr('dx', 8).attr('dy', 3);
238
+
239
+ const sim = d3.forceSimulation(data.nodes)
240
+ .force('link', d3.forceLink(data.links).id(d => d.id).distance(60))
241
+ .force('charge', d3.forceManyBody().strength(-180))
242
+ .force('center', d3.forceCenter(width / 2, height / 2))
243
+ .force('collide', d3.forceCollide().radius(d => (radiusMap[d.type] || 6) + 4))
244
+ .on('tick', () => {
245
+ linkSel.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
246
+ .attr('x2', d => d.target.x).attr('y2', d => d.target.y);
247
+ nodeSel.attr('cx', d => d.x).attr('cy', d => d.y);
248
+ labelSel.attr('x', d => d.x).attr('y', d => d.y);
249
+ });
250
+
251
+ nodeSel.call(d3.drag()
252
+ .on('start', (e, d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
253
+ .on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
254
+ .on('end', (e, d) => { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })
255
+ );
256
+
257
+ svg.call(d3.zoom().scaleExtent([0.3, 4]).on('zoom', e => g.attr('transform', e.transform)));
258
+
259
+ function inspect(d) {
260
+ const neighbors = new Set([d.id]);
261
+ data.links.forEach(l => {
262
+ const sId = l.source.id || l.source;
263
+ const tId = l.target.id || l.target;
264
+ if (sId === d.id) neighbors.add(tId);
265
+ if (tId === d.id) neighbors.add(sId);
266
+ });
267
+ nodeSel.classed('dim', n => !neighbors.has(n.id));
268
+ linkSel.classed('highlight', l => {
269
+ const sId = l.source.id || l.source;
270
+ const tId = l.target.id || l.target;
271
+ return sId === d.id || tId === d.id;
272
+ });
273
+ let html = '<h2>' + escapeHtml(d.label) + '</h2>';
274
+ html += '<p><strong>Type:</strong> ' + d.type + '</p>';
275
+ html += '<p><strong>ID:</strong> <span class="hex">' + d.hex + '</span></p>';
276
+ if (d.sub_funcs !== undefined) html += '<p><strong>Sub-functions:</strong> ' + d.sub_funcs + '</p>';
277
+ if (d.nrcs !== undefined) html += '<p><strong>NRCs:</strong> ' + d.nrcs + '</p>';
278
+ if (d.read !== undefined) html += '<p><strong>Read:</strong> ' + (d.read ? '✓' : '—') + '</p>';
279
+ if (d.write !== undefined) html += '<p><strong>Write:</strong> ' + (d.write ? '✓' : '—') + '</p>';
280
+ if (d.ops) html += '<p><strong>Ops:</strong> ' + d.ops + '</p>';
281
+ document.getElementById('info').innerHTML = html;
282
+ }
283
+
284
+ function escapeHtml(s) {
285
+ return String(s).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
286
+ }
287
+
288
+ document.getElementById('btn-reset').onclick = () => {
289
+ nodeSel.classed('dim', false);
290
+ linkSel.classed('highlight', false);
291
+ document.getElementById('info').innerHTML = '<p>Click a node to inspect.</p>';
292
+ };
293
+ document.getElementById('search').oninput = e => {
294
+ const q = e.target.value.toLowerCase().trim();
295
+ if (!q) {
296
+ nodeSel.classed('dim', false);
297
+ return;
298
+ }
299
+ nodeSel.classed('dim', n => !(n.label.toLowerCase().includes(q) || n.hex.toLowerCase().includes(q)));
300
+ };
301
+ document.getElementById('show-labels').onchange = e => {
302
+ labelSel.style('display', e.target.checked ? 'block' : 'none');
303
+ };
304
+
305
+ // Stats
306
+ const counts = {};
307
+ data.nodes.forEach(n => counts[n.type] = (counts[n.type] || 0) + 1);
308
+ document.getElementById('stats').innerHTML =
309
+ '<ul style="padding-left:1em;list-style:disc;">' +
310
+ Object.entries(counts).map(([k, v]) =>
311
+ '<li><strong>' + k + ':</strong> ' + v + '</li>'
312
+ ).join('') +
313
+ '<li><strong>links:</strong> ' + data.links.length + '</li></ul>';
314
+
315
+ window.addEventListener('resize', () => {
316
+ width = window.innerWidth - 320;
317
+ height = window.innerHeight - 36;
318
+ svg.attr('viewBox', [0, 0, width, height]);
319
+ sim.force('center', d3.forceCenter(width / 2, height / 2)).alpha(0.3).restart();
320
+ });
321
+ </script>
322
+ </body>
323
+ </html>
324
+ """