altium-toolkit 1.0.8 → 1.0.10
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.
- package/README.md +18 -6
- package/docs/api.md +78 -16
- package/docs/model-format.md +229 -8
- package/docs/schemas/altium_toolkit/ci_artifact_bundle_a1.schema.json +76 -0
- package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +35 -0
- package/docs/schemas/altium_toolkit/netlist_a1.schema.json +53 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +1826 -110
- package/docs/schemas/altium_toolkit/parser_compatibility_fuzz_a1.schema.json +25 -0
- package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +86 -0
- package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +63 -0
- package/docs/schemas/altium_toolkit/project_document_graph_a1.schema.json +33 -0
- package/docs/schemas/altium_toolkit/schematic_svg_semantics_a1.schema.json +50 -0
- package/docs/schemas/altium_toolkit/svg_model_cross_link_a1.schema.json +39 -0
- package/docs/testing.md +9 -3
- package/package.json +1 -1
- package/spec/library-scope.md +7 -1
- package/src/core/altium/AltiumLayoutParser.mjs +104 -8
- package/src/core/altium/AltiumParser.mjs +196 -45
- package/src/core/altium/CiArtifactBundleBuilder.mjs +202 -0
- package/src/core/altium/DraftsmanDigestParser.mjs +689 -0
- package/src/core/altium/EmbeddedFileInventoryBuilder.mjs +255 -0
- package/src/core/altium/IntLibModelParser.mjs +240 -0
- package/src/core/altium/IntLibStreamExtractor.mjs +366 -0
- package/src/core/altium/LibraryRenderManifestBuilder.mjs +417 -0
- package/src/core/altium/LibrarySearchIndex.mjs +215 -0
- package/src/core/altium/NormalizedModelSchema.mjs +36 -0
- package/src/core/altium/ParserCompatibilityFuzzer.mjs +192 -0
- package/src/core/altium/PcbCustomPadShapeParser.mjs +244 -0
- package/src/core/altium/PcbDefaultsParser.mjs +171 -0
- package/src/core/altium/PcbDimensionParser.mjs +229 -0
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +232 -6
- package/src/core/altium/PcbExtendedPrimitiveInformationParser.mjs +256 -0
- package/src/core/altium/PcbLibModelParser.mjs +235 -14
- package/src/core/altium/PcbLibStreamExtractor.mjs +62 -4
- package/src/core/altium/PcbMaskPasteResolver.mjs +354 -0
- package/src/core/altium/PcbMechanicalLayerPairParser.mjs +204 -0
- package/src/core/altium/PcbModelParser.mjs +495 -32
- package/src/core/altium/PcbOwnershipGraphBuilder.mjs +245 -0
- package/src/core/altium/PcbPadPrimitiveParser.mjs +78 -65
- package/src/core/altium/PcbPadStackParser.mjs +229 -2
- package/src/core/altium/PcbPickPlacePositionResolver.mjs +224 -0
- package/src/core/altium/PcbPrimitiveParameterParser.mjs +3 -2
- package/src/core/altium/PcbRawRecordRegistry.mjs +121 -130
- package/src/core/altium/PcbRegionPrimitiveParser.mjs +76 -3
- package/src/core/altium/PcbRouteAnalysisBuilder.mjs +730 -0
- package/src/core/altium/PcbRuleParser.mjs +354 -33
- package/src/core/altium/PcbSidecarRecordParser.mjs +177 -0
- package/src/core/altium/PcbSpecialStringResolver.mjs +220 -0
- package/src/core/altium/PcbStatisticsBuilder.mjs +541 -0
- package/src/core/altium/PcbStreamExtractor.mjs +111 -4
- package/src/core/altium/PcbTextPrimitiveParser.mjs +60 -0
- package/src/core/altium/PcbUnionParser.mjs +307 -0
- package/src/core/altium/PcbViaStackParser.mjs +98 -10
- package/src/core/altium/PcbViaStructureParser.mjs +335 -0
- package/src/core/altium/PrintableTextDecoder.mjs +53 -3
- package/src/core/altium/PrjPcbModelParser.mjs +281 -7
- package/src/core/altium/ProjectAnnotationParser.mjs +205 -0
- package/src/core/altium/ProjectDesignBundleBuilder.mjs +492 -0
- package/src/core/altium/ProjectDocumentGraphBuilder.mjs +280 -0
- package/src/core/altium/ProjectNetlistExporter.mjs +503 -0
- package/src/core/altium/ProjectOutJobDigestBuilder.mjs +109 -0
- package/src/core/altium/ProjectVariantViewBuilder.mjs +334 -0
- package/src/core/altium/SchematicBindingProvenanceParser.mjs +223 -0
- package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +312 -0
- package/src/core/altium/SchematicComponentTextResolver.mjs +72 -19
- package/src/core/altium/SchematicConnectivityQaBuilder.mjs +271 -0
- package/src/core/altium/SchematicCrossSheetConnectorParser.mjs +140 -0
- package/src/core/altium/SchematicDirectiveParser.mjs +312 -0
- package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +231 -0
- package/src/core/altium/SchematicHarnessParser.mjs +302 -0
- package/src/core/altium/SchematicImageParser.mjs +474 -3
- package/src/core/altium/SchematicImplementationParser.mjs +518 -0
- package/src/core/altium/SchematicNetlistBuilder.mjs +15 -2
- package/src/core/altium/SchematicOwnershipGraphParser.mjs +195 -0
- package/src/core/altium/SchematicPinParser.mjs +84 -1
- package/src/core/altium/SchematicPrimitiveParser.mjs +301 -0
- package/src/core/altium/SchematicProjectParameterResolver.mjs +361 -0
- package/src/core/altium/SchematicQaReportBuilder.mjs +284 -0
- package/src/core/altium/SchematicRecordTypeRegistry.mjs +137 -0
- package/src/core/altium/SchematicRepeatedChannelParser.mjs +229 -0
- package/src/core/altium/SchematicStreamExtractor.mjs +10 -1
- package/src/core/altium/SchematicTemplateParser.mjs +256 -0
- package/src/core/altium/SchematicTextParser.mjs +123 -0
- package/src/core/altium/SvgModelCrossLinkValidator.mjs +402 -0
- package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +136 -96
- package/src/core/circuit-json/CircuitJsonModelAdapterPcbElements.mjs +244 -0
- package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
- package/src/core/ole/OleCompoundDocument.mjs +20 -0
- package/src/parser.mjs +35 -0
- package/src/styles/altium-renderers.css +19 -0
- package/src/ui/PcbBarcodeTextRenderer.mjs +436 -0
- package/src/ui/PcbInteractionIndex.mjs +9 -4
- package/src/ui/PcbScene3dBuilder.mjs +137 -3
- package/src/ui/PcbScene3dModelRegistry.mjs +74 -0
- package/src/ui/PcbSvgRenderer.mjs +1252 -34
- package/src/ui/PcbTextPrimitiveRenderer.mjs +193 -7
- package/src/ui/SchematicNoteRenderer.mjs +9 -2
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +206 -0
- package/src/ui/SchematicShapeRenderer.mjs +362 -0
- package/src/ui/SchematicSvgRenderer.mjs +1442 -92
- package/src/ui/SchematicTypography.mjs +48 -5
- package/src/ui/TextGeometrySidecarBuilder.mjs +147 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds a generic inventory for embedded payload streams.
|
|
7
|
+
*/
|
|
8
|
+
export class EmbeddedFileInventoryBuilder {
|
|
9
|
+
static SCHEMA_ID = 'altium-toolkit.embedded-files.a1'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds an embedded-file inventory from a stream map.
|
|
13
|
+
* @param {Map<string, Uint8Array>} streams Compound-document streams.
|
|
14
|
+
* @param {{ skipStreamNames?: Iterable<string> }} [options] Inventory options.
|
|
15
|
+
* @returns {{ schema: string, files: object[], diagnostics: object[] }}
|
|
16
|
+
*/
|
|
17
|
+
static buildFromStreams(streams, options = {}) {
|
|
18
|
+
const skipStreamNames = new Set(options.skipStreamNames || [])
|
|
19
|
+
const files = []
|
|
20
|
+
const diagnostics = []
|
|
21
|
+
|
|
22
|
+
for (const [sourceStream, bytes] of streams || []) {
|
|
23
|
+
if (
|
|
24
|
+
skipStreamNames.has(sourceStream) ||
|
|
25
|
+
!EmbeddedFileInventoryBuilder.#isEmbeddedPayloadStream(
|
|
26
|
+
sourceStream
|
|
27
|
+
)
|
|
28
|
+
) {
|
|
29
|
+
continue
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength === 0) {
|
|
33
|
+
diagnostics.push({
|
|
34
|
+
code: 'embedded-file.empty',
|
|
35
|
+
severity: 'warning',
|
|
36
|
+
sourceStream,
|
|
37
|
+
message: 'Embedded payload stream was empty.'
|
|
38
|
+
})
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
files.push(
|
|
43
|
+
EmbeddedFileInventoryBuilder.#fileRecord(sourceStream, bytes)
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
schema: EmbeddedFileInventoryBuilder.SCHEMA_ID,
|
|
49
|
+
files: files.sort((left, right) =>
|
|
50
|
+
left.sourceStream.localeCompare(right.sourceStream)
|
|
51
|
+
),
|
|
52
|
+
diagnostics: diagnostics.sort((left, right) =>
|
|
53
|
+
left.sourceStream.localeCompare(right.sourceStream)
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns true when a stream name represents a payload-style embedded file.
|
|
60
|
+
* @param {string} sourceStream Stream name.
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
static #isEmbeddedPayloadStream(sourceStream) {
|
|
64
|
+
const normalized = String(sourceStream || '')
|
|
65
|
+
if (/\/(?:Data|Header)$/i.test(normalized)) {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
/^(EmbeddedFiles|Embedded|Attachments|Images?)\//i.test(
|
|
71
|
+
normalized
|
|
72
|
+
) ||
|
|
73
|
+
/^Models\/\d+$/i.test(normalized) ||
|
|
74
|
+
/\.[A-Za-z0-9]{2,8}$/.test(normalized)
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Builds one file inventory row.
|
|
80
|
+
* @param {string} sourceStream Stream name.
|
|
81
|
+
* @param {Uint8Array} bytes Payload bytes.
|
|
82
|
+
* @returns {object}
|
|
83
|
+
*/
|
|
84
|
+
static #fileRecord(sourceStream, bytes) {
|
|
85
|
+
return {
|
|
86
|
+
sourceStream,
|
|
87
|
+
name: EmbeddedFileInventoryBuilder.#basename(sourceStream),
|
|
88
|
+
format: EmbeddedFileInventoryBuilder.#format(sourceStream, bytes),
|
|
89
|
+
byteLength: bytes.byteLength,
|
|
90
|
+
checksum: {
|
|
91
|
+
algorithm: 'fnv1a32',
|
|
92
|
+
value: EmbeddedFileInventoryBuilder.#fnv1a32(bytes)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolves the payload basename from a stream path.
|
|
99
|
+
* @param {string} sourceStream Stream name.
|
|
100
|
+
* @returns {string}
|
|
101
|
+
*/
|
|
102
|
+
static #basename(sourceStream) {
|
|
103
|
+
return (
|
|
104
|
+
String(sourceStream || '')
|
|
105
|
+
.split('/')
|
|
106
|
+
.filter(Boolean)
|
|
107
|
+
.pop() || ''
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Classifies payload format from extension, magic bytes, and text probes.
|
|
113
|
+
* @param {string} sourceStream Stream name.
|
|
114
|
+
* @param {Uint8Array} bytes Payload bytes.
|
|
115
|
+
* @returns {string}
|
|
116
|
+
*/
|
|
117
|
+
static #format(sourceStream, bytes) {
|
|
118
|
+
const lower =
|
|
119
|
+
EmbeddedFileInventoryBuilder.#basename(sourceStream).toLowerCase()
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
lower.endsWith('.png') ||
|
|
123
|
+
EmbeddedFileInventoryBuilder.#hasPrefix(
|
|
124
|
+
bytes,
|
|
125
|
+
[0x89, 0x50, 0x4e, 0x47]
|
|
126
|
+
)
|
|
127
|
+
) {
|
|
128
|
+
return 'png'
|
|
129
|
+
}
|
|
130
|
+
if (
|
|
131
|
+
lower.endsWith('.jpg') ||
|
|
132
|
+
lower.endsWith('.jpeg') ||
|
|
133
|
+
EmbeddedFileInventoryBuilder.#hasPrefix(bytes, [0xff, 0xd8, 0xff])
|
|
134
|
+
) {
|
|
135
|
+
return 'jpeg'
|
|
136
|
+
}
|
|
137
|
+
if (
|
|
138
|
+
lower.endsWith('.gif') ||
|
|
139
|
+
EmbeddedFileInventoryBuilder.#asciiPrefix(bytes).startsWith('GIF')
|
|
140
|
+
) {
|
|
141
|
+
return 'gif'
|
|
142
|
+
}
|
|
143
|
+
if (
|
|
144
|
+
lower.endsWith('.bmp') ||
|
|
145
|
+
EmbeddedFileInventoryBuilder.#asciiPrefix(bytes).startsWith('BM')
|
|
146
|
+
) {
|
|
147
|
+
return 'bmp'
|
|
148
|
+
}
|
|
149
|
+
if (
|
|
150
|
+
lower.endsWith('.svg') ||
|
|
151
|
+
EmbeddedFileInventoryBuilder.#trimmedText(bytes).startsWith('<svg')
|
|
152
|
+
) {
|
|
153
|
+
return 'svg'
|
|
154
|
+
}
|
|
155
|
+
if (
|
|
156
|
+
lower.endsWith('.step') ||
|
|
157
|
+
lower.endsWith('.stp') ||
|
|
158
|
+
EmbeddedFileInventoryBuilder.#trimmedText(bytes).startsWith(
|
|
159
|
+
'ISO-10303-21'
|
|
160
|
+
)
|
|
161
|
+
) {
|
|
162
|
+
return 'step'
|
|
163
|
+
}
|
|
164
|
+
if (lower.endsWith('.sldprt') || lower.endsWith('.sldasm')) {
|
|
165
|
+
return 'solidworks'
|
|
166
|
+
}
|
|
167
|
+
if (lower.endsWith('.x_t') || lower.endsWith('.xmt_txt')) {
|
|
168
|
+
return 'parasolid-text'
|
|
169
|
+
}
|
|
170
|
+
if (lower.endsWith('.x_b') || lower.endsWith('.xmt_bin')) {
|
|
171
|
+
return 'parasolid-binary'
|
|
172
|
+
}
|
|
173
|
+
if (
|
|
174
|
+
lower.endsWith('.pdf') ||
|
|
175
|
+
EmbeddedFileInventoryBuilder.#asciiPrefix(bytes).startsWith('%PDF')
|
|
176
|
+
) {
|
|
177
|
+
return 'pdf'
|
|
178
|
+
}
|
|
179
|
+
if (EmbeddedFileInventoryBuilder.#isLikelyText(bytes)) {
|
|
180
|
+
return 'text'
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return 'binary'
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Returns true when bytes start with a prefix.
|
|
188
|
+
* @param {Uint8Array} bytes Payload bytes.
|
|
189
|
+
* @param {number[]} prefix Prefix bytes.
|
|
190
|
+
* @returns {boolean}
|
|
191
|
+
*/
|
|
192
|
+
static #hasPrefix(bytes, prefix) {
|
|
193
|
+
return prefix.every((value, index) => bytes[index] === value)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Decodes a short ASCII prefix.
|
|
198
|
+
* @param {Uint8Array} bytes Payload bytes.
|
|
199
|
+
* @returns {string}
|
|
200
|
+
*/
|
|
201
|
+
static #asciiPrefix(bytes) {
|
|
202
|
+
return new TextDecoder('latin1').decode(bytes.slice(0, 8))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Decodes and trims a text probe.
|
|
207
|
+
* @param {Uint8Array} bytes Payload bytes.
|
|
208
|
+
* @returns {string}
|
|
209
|
+
*/
|
|
210
|
+
static #trimmedText(bytes) {
|
|
211
|
+
return new TextDecoder('utf-8', { fatal: false })
|
|
212
|
+
.decode(bytes.slice(0, Math.min(bytes.byteLength, 256)))
|
|
213
|
+
.trim()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Returns true when a payload is printable enough to treat as text.
|
|
218
|
+
* @param {Uint8Array} bytes Payload bytes.
|
|
219
|
+
* @returns {boolean}
|
|
220
|
+
*/
|
|
221
|
+
static #isLikelyText(bytes) {
|
|
222
|
+
let printable = 0
|
|
223
|
+
const length = Math.min(bytes.byteLength, 256)
|
|
224
|
+
|
|
225
|
+
for (let index = 0; index < length; index += 1) {
|
|
226
|
+
const value = bytes[index]
|
|
227
|
+
if (
|
|
228
|
+
value === 0x09 ||
|
|
229
|
+
value === 0x0a ||
|
|
230
|
+
value === 0x0d ||
|
|
231
|
+
(value >= 0x20 && value <= 0x7e)
|
|
232
|
+
) {
|
|
233
|
+
printable += 1
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return length > 0 && printable / length >= 0.9
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Computes an FNV-1a 32-bit checksum.
|
|
242
|
+
* @param {Uint8Array} bytes Payload bytes.
|
|
243
|
+
* @returns {string}
|
|
244
|
+
*/
|
|
245
|
+
static #fnv1a32(bytes) {
|
|
246
|
+
let hash = 0x811c9dc5
|
|
247
|
+
|
|
248
|
+
for (const value of bytes) {
|
|
249
|
+
hash ^= value
|
|
250
|
+
hash = Math.imul(hash, 0x01000193) >>> 0
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return hash.toString(16).padStart(8, '0')
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { NormalizedModelSchema } from './NormalizedModelSchema.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Normalizes integrated-library extraction into the public parser model.
|
|
9
|
+
*/
|
|
10
|
+
export class IntLibModelParser {
|
|
11
|
+
/**
|
|
12
|
+
* Builds one integrated-library model.
|
|
13
|
+
* @param {string} fileName
|
|
14
|
+
* @param {{ version?: string, crossReferences?: object[], parameters?: Record<string, string>, parameterRecords?: object[], sources?: object[], streamNames?: string[], diagnostics?: Record<string, number> } | null} extraction
|
|
15
|
+
* @returns {{ schema: string, kind: 'integrated-library', fileType: 'IntLib', fileName: string, summary: Record<string, number | string>, diagnostics: { severity: 'info' | 'warning', message: string }[], integratedLibrary: object, bom: [] }}
|
|
16
|
+
*/
|
|
17
|
+
static parse(fileName, extraction = null) {
|
|
18
|
+
const normalizedExtraction = extraction || {}
|
|
19
|
+
const sources = Array.isArray(normalizedExtraction.sources)
|
|
20
|
+
? normalizedExtraction.sources
|
|
21
|
+
: []
|
|
22
|
+
const crossReferences = Array.isArray(
|
|
23
|
+
normalizedExtraction.crossReferences
|
|
24
|
+
)
|
|
25
|
+
? normalizedExtraction.crossReferences
|
|
26
|
+
: []
|
|
27
|
+
const parameters = normalizedExtraction.parameters || {}
|
|
28
|
+
const streamNames = Array.isArray(normalizedExtraction.streamNames)
|
|
29
|
+
? normalizedExtraction.streamNames
|
|
30
|
+
: []
|
|
31
|
+
|
|
32
|
+
return NormalizedModelSchema.attach({
|
|
33
|
+
kind: 'integrated-library',
|
|
34
|
+
fileType: 'IntLib',
|
|
35
|
+
fileName,
|
|
36
|
+
summary: {
|
|
37
|
+
title: IntLibModelParser.#stripExtension(fileName),
|
|
38
|
+
version: normalizedExtraction.version || '',
|
|
39
|
+
sourceCount: sources.length,
|
|
40
|
+
crossReferenceCount: crossReferences.length,
|
|
41
|
+
parameterCount: Object.keys(parameters).length
|
|
42
|
+
},
|
|
43
|
+
diagnostics: IntLibModelParser.#buildDiagnostics(
|
|
44
|
+
streamNames,
|
|
45
|
+
sources,
|
|
46
|
+
crossReferences,
|
|
47
|
+
parameters,
|
|
48
|
+
normalizedExtraction.diagnostics?.issues || []
|
|
49
|
+
),
|
|
50
|
+
integratedLibrary: {
|
|
51
|
+
version: normalizedExtraction.version || '',
|
|
52
|
+
streamNames,
|
|
53
|
+
crossReferences,
|
|
54
|
+
parameters,
|
|
55
|
+
parameterRecords: normalizedExtraction.parameterRecords || [],
|
|
56
|
+
diagnostics: {
|
|
57
|
+
...(normalizedExtraction.diagnostics || {}),
|
|
58
|
+
issues: normalizedExtraction.diagnostics?.issues || []
|
|
59
|
+
},
|
|
60
|
+
indexes: IntLibModelParser.#buildIndexes(
|
|
61
|
+
sources,
|
|
62
|
+
crossReferences
|
|
63
|
+
),
|
|
64
|
+
sources
|
|
65
|
+
},
|
|
66
|
+
bom: []
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Builds source and model lookup indexes for bundled library consumers.
|
|
72
|
+
* @param {object[]} sources Bundled source entries.
|
|
73
|
+
* @param {object[]} crossReferences Cross-reference rows.
|
|
74
|
+
* @returns {object}
|
|
75
|
+
*/
|
|
76
|
+
static #buildIndexes(sources, crossReferences) {
|
|
77
|
+
return {
|
|
78
|
+
sourcesByFileName:
|
|
79
|
+
IntLibModelParser.#buildSourcesByFileName(sources),
|
|
80
|
+
sourcesByKind: IntLibModelParser.#buildSourcesByKind(sources),
|
|
81
|
+
modelsByComponent:
|
|
82
|
+
IntLibModelParser.#buildModelsByComponent(crossReferences),
|
|
83
|
+
symbolsByComponent: IntLibModelParser.#buildModelNamesByKind(
|
|
84
|
+
crossReferences,
|
|
85
|
+
'SCH'
|
|
86
|
+
),
|
|
87
|
+
footprintsByComponent: IntLibModelParser.#buildModelNamesByKind(
|
|
88
|
+
crossReferences,
|
|
89
|
+
'PCB'
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Indexes source entries by file name.
|
|
96
|
+
* @param {object[]} sources Source entries.
|
|
97
|
+
* @returns {Record<string, object>}
|
|
98
|
+
*/
|
|
99
|
+
static #buildSourcesByFileName(sources) {
|
|
100
|
+
const index = {}
|
|
101
|
+
for (const [sourceIndex, source] of [...sources]
|
|
102
|
+
.map((source, index) => ({ ...source, sourceIndex: index }))
|
|
103
|
+
.sort((left, right) =>
|
|
104
|
+
String(left.fileName || '').localeCompare(
|
|
105
|
+
String(right.fileName || '')
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
.entries()) {
|
|
109
|
+
index[source.fileName] = {
|
|
110
|
+
index: source.sourceIndex ?? sourceIndex,
|
|
111
|
+
path: source.path,
|
|
112
|
+
fileType: source.fileType,
|
|
113
|
+
libraryKind: source.libraryKind
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return index
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Indexes source file names by source kind.
|
|
121
|
+
* @param {object[]} sources Source entries.
|
|
122
|
+
* @returns {Record<string, string[]>}
|
|
123
|
+
*/
|
|
124
|
+
static #buildSourcesByKind(sources) {
|
|
125
|
+
const index = {}
|
|
126
|
+
for (const source of [...sources].sort((left, right) =>
|
|
127
|
+
String(left.libraryKind || '').localeCompare(
|
|
128
|
+
String(right.libraryKind || '')
|
|
129
|
+
)
|
|
130
|
+
)) {
|
|
131
|
+
const kind = source.libraryKind || 'other'
|
|
132
|
+
index[kind] ||= []
|
|
133
|
+
index[kind].push(source.fileName)
|
|
134
|
+
}
|
|
135
|
+
return index
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Indexes cross-reference model rows by component name.
|
|
140
|
+
* @param {object[]} crossReferences Cross-reference rows.
|
|
141
|
+
* @returns {Record<string, object[]>}
|
|
142
|
+
*/
|
|
143
|
+
static #buildModelsByComponent(crossReferences) {
|
|
144
|
+
const index = {}
|
|
145
|
+
for (const row of crossReferences || []) {
|
|
146
|
+
if (!row.component || !row.model) continue
|
|
147
|
+
index[row.component] ||= []
|
|
148
|
+
index[row.component].push({
|
|
149
|
+
model: row.model,
|
|
150
|
+
kind: row.kind || ''
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
return index
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Indexes cross-reference model names of one kind by component.
|
|
158
|
+
* @param {object[]} crossReferences Cross-reference rows.
|
|
159
|
+
* @param {string} kind Model kind.
|
|
160
|
+
* @returns {Record<string, string[]>}
|
|
161
|
+
*/
|
|
162
|
+
static #buildModelNamesByKind(crossReferences, kind) {
|
|
163
|
+
const index = {}
|
|
164
|
+
const lookup = kind.toUpperCase()
|
|
165
|
+
for (const row of crossReferences || []) {
|
|
166
|
+
if (
|
|
167
|
+
!row.component ||
|
|
168
|
+
!row.model ||
|
|
169
|
+
String(row.kind || '').toUpperCase() !== lookup
|
|
170
|
+
) {
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
index[row.component] ||= []
|
|
174
|
+
index[row.component].push(row.model)
|
|
175
|
+
}
|
|
176
|
+
return index
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Builds parser diagnostics for one integrated-library model.
|
|
181
|
+
* @param {string[]} streamNames
|
|
182
|
+
* @param {object[]} sources
|
|
183
|
+
* @param {object[]} crossReferences
|
|
184
|
+
* @param {Record<string, string>} parameters
|
|
185
|
+
* @param {object[]} issues Structured parser issues.
|
|
186
|
+
* @returns {{ severity: 'info' | 'warning', message: string }[]}
|
|
187
|
+
*/
|
|
188
|
+
static #buildDiagnostics(
|
|
189
|
+
streamNames,
|
|
190
|
+
sources,
|
|
191
|
+
crossReferences,
|
|
192
|
+
parameters,
|
|
193
|
+
issues
|
|
194
|
+
) {
|
|
195
|
+
return [
|
|
196
|
+
{
|
|
197
|
+
severity: 'info',
|
|
198
|
+
message:
|
|
199
|
+
'Recovered ' +
|
|
200
|
+
streamNames.length +
|
|
201
|
+
' integrated-library data streams.'
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
severity: 'info',
|
|
205
|
+
message:
|
|
206
|
+
'Recovered ' +
|
|
207
|
+
sources.length +
|
|
208
|
+
' bundled library source entries.'
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
severity: 'info',
|
|
212
|
+
message:
|
|
213
|
+
'Recovered ' +
|
|
214
|
+
crossReferences.length +
|
|
215
|
+
' integrated-library cross references.'
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
severity: 'info',
|
|
219
|
+
message:
|
|
220
|
+
'Recovered ' +
|
|
221
|
+
Object.keys(parameters).length +
|
|
222
|
+
' integrated-library parameters.'
|
|
223
|
+
},
|
|
224
|
+
...(issues || []).map((issue) => ({
|
|
225
|
+
severity: issue.severity || 'warning',
|
|
226
|
+
code: issue.code,
|
|
227
|
+
message: issue.message
|
|
228
|
+
}))
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Returns a file name without its last extension.
|
|
234
|
+
* @param {string} fileName
|
|
235
|
+
* @returns {string}
|
|
236
|
+
*/
|
|
237
|
+
static #stripExtension(fileName) {
|
|
238
|
+
return String(fileName || '').replace(/\.[^.]+$/u, '')
|
|
239
|
+
}
|
|
240
|
+
}
|