@miragon/wardley-dsl 0.5.3 → 0.7.0
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 +38 -0
- package/dist/index.cjs +831 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -6
- package/dist/index.d.ts +43 -6
- package/dist/index.js +830 -77
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { validateMap, CURRENT_SCHEMA_VERSION } from '@miragon/wardley-schema-model';
|
|
1
|
+
import { DEFAULT_MAP_TITLE, validateMap, PLOT_MIN, PLOT_MAX, drawnPlotLength, CURRENT_SCHEMA_VERSION } from '@miragon/wardley-schema-model';
|
|
2
2
|
export { loadMap, parseMapJSON as mapFromJSON, serializeMap as mapToJSON } from '@miragon/wardley-schema-model';
|
|
3
3
|
|
|
4
4
|
// src/parser.ts
|
|
@@ -356,6 +356,10 @@ var UniqueNames = class {
|
|
|
356
356
|
this.used.add(name);
|
|
357
357
|
return name;
|
|
358
358
|
}
|
|
359
|
+
/** Takes a name as it is, even when it is already taken (a name the source text already uses). */
|
|
360
|
+
reserve(name) {
|
|
361
|
+
this.used.add(name);
|
|
362
|
+
}
|
|
359
363
|
};
|
|
360
364
|
|
|
361
365
|
// src/parser.ts
|
|
@@ -463,7 +467,7 @@ function unknownStatementMessage(line) {
|
|
|
463
467
|
if (flowValue) return `Flow ${flowValue[0]} has no direction \u2014 add ">", "<" or "<>"`;
|
|
464
468
|
return `Unknown statement "${line.split(/\s/)[0]}"`;
|
|
465
469
|
}
|
|
466
|
-
function buildEdges(links, idOfName, unresolved, labelOf) {
|
|
470
|
+
function buildEdges(links, idOfName, unresolved, labelOf, built) {
|
|
467
471
|
let dependencies = 0;
|
|
468
472
|
let flows = 0;
|
|
469
473
|
const edges = [];
|
|
@@ -475,29 +479,29 @@ function buildEdges(links, idOfName, unresolved, labelOf) {
|
|
|
475
479
|
continue;
|
|
476
480
|
}
|
|
477
481
|
if (link.kind === "dependency") {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
);
|
|
482
|
+
const edge2 = compact({
|
|
483
|
+
id: `dep_${++dependencies}`,
|
|
484
|
+
edgeType: "dependency",
|
|
485
|
+
from: fromId,
|
|
486
|
+
to: toId,
|
|
487
|
+
label: labelOf(link)
|
|
488
|
+
});
|
|
489
|
+
edges.push(edge2);
|
|
490
|
+
built(edge2, link);
|
|
487
491
|
continue;
|
|
488
492
|
}
|
|
489
493
|
const [from, to] = link.reverse ? [toId, fromId] : [fromId, toId];
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
);
|
|
494
|
+
const edge = compact({
|
|
495
|
+
id: `flow_${++flows}`,
|
|
496
|
+
edgeType: "flow",
|
|
497
|
+
from,
|
|
498
|
+
to,
|
|
499
|
+
bidirectional: link.bidirectional ? true : void 0,
|
|
500
|
+
flowValue: link.flowValue,
|
|
501
|
+
label: labelOf(link)
|
|
502
|
+
});
|
|
503
|
+
edges.push(edge);
|
|
504
|
+
built(edge, link);
|
|
501
505
|
}
|
|
502
506
|
return edges;
|
|
503
507
|
}
|
|
@@ -516,11 +520,18 @@ function parseDSL(text) {
|
|
|
516
520
|
return parseDSLWithDiagnostics(text).map;
|
|
517
521
|
}
|
|
518
522
|
function parseDSLWithDiagnostics(text) {
|
|
523
|
+
return readMap(text).result;
|
|
524
|
+
}
|
|
525
|
+
function parseDSLWithSourceMap(text) {
|
|
526
|
+
const reading = readMap(text);
|
|
527
|
+
return { ...reading.result, sourceMap: reading.sourceMap };
|
|
528
|
+
}
|
|
529
|
+
function readMap(text) {
|
|
519
530
|
const reading = new MapReader(/* @__PURE__ */ new Set()).read(text);
|
|
520
531
|
const needsDeclaredNames = reading.keywordLinks.some(
|
|
521
532
|
(link) => leftNameIsDeclared(link, reading.declaredNames)
|
|
522
533
|
);
|
|
523
|
-
return needsDeclaredNames ? new MapReader(reading.declaredNames).read(text)
|
|
534
|
+
return needsDeclaredNames ? new MapReader(reading.declaredNames).read(text) : reading;
|
|
524
535
|
}
|
|
525
536
|
var MapReader = class {
|
|
526
537
|
constructor(linkNames) {
|
|
@@ -540,7 +551,7 @@ var MapReader = class {
|
|
|
540
551
|
pendingEvolve = [];
|
|
541
552
|
pendingPipeline = [];
|
|
542
553
|
pendingMethod = [];
|
|
543
|
-
config = { title:
|
|
554
|
+
config = { title: DEFAULT_MAP_TITLE };
|
|
544
555
|
lineThatSetConfig = /* @__PURE__ */ new Map();
|
|
545
556
|
unusedConfigLines = [];
|
|
546
557
|
annotationCounter = 0;
|
|
@@ -553,6 +564,22 @@ var MapReader = class {
|
|
|
553
564
|
blockOpening = { line: 0, text: "" };
|
|
554
565
|
lineNo = 0;
|
|
555
566
|
currentLine = "";
|
|
567
|
+
declarationLineById = /* @__PURE__ */ new Map();
|
|
568
|
+
configLinesByKeyword = /* @__PURE__ */ new Map();
|
|
569
|
+
evolveLinesById = /* @__PURE__ */ new Map();
|
|
570
|
+
methodLinesById = /* @__PURE__ */ new Map();
|
|
571
|
+
blockLines = /* @__PURE__ */ new Map();
|
|
572
|
+
pipelineIds = /* @__PURE__ */ new Map();
|
|
573
|
+
rangeFromChildren = /* @__PURE__ */ new Set();
|
|
574
|
+
addElement(element, line = this.lineNo) {
|
|
575
|
+
this.elements.push(element);
|
|
576
|
+
this.declarationLineById.set(element.id, line);
|
|
577
|
+
}
|
|
578
|
+
recordBlockLine(pipeline, end) {
|
|
579
|
+
const lines = this.blockLines.get(pipeline) ?? {};
|
|
580
|
+
lines[end] = this.lineNo;
|
|
581
|
+
this.blockLines.set(pipeline, lines);
|
|
582
|
+
}
|
|
556
583
|
report = (message) => this.diag(message);
|
|
557
584
|
reportAt = (message, line, text) => this.diag(message, line, text);
|
|
558
585
|
diag(message, atLine = this.lineNo, text = this.currentLine) {
|
|
@@ -567,7 +594,7 @@ var MapReader = class {
|
|
|
567
594
|
}
|
|
568
595
|
/** For a line that looks like a known construct but cannot be parsed. */
|
|
569
596
|
failed(line) {
|
|
570
|
-
this.keep(line, "Line
|
|
597
|
+
this.keep(line, "Line not understood \u2014 kept as written");
|
|
571
598
|
}
|
|
572
599
|
// A reference binds to the name as written first, so `X 2` stays bound to an explicit "X 2" even
|
|
573
600
|
// when a duplicate "X" was renamed to "X 2"; the serializer then writes that reference by the
|
|
@@ -585,6 +612,9 @@ var MapReader = class {
|
|
|
585
612
|
this.diag(`Repeated "${keyword}" \u2014 replaces the one on line ${earlier}`);
|
|
586
613
|
}
|
|
587
614
|
this.lineThatSetConfig.set(keyword, this.lineNo);
|
|
615
|
+
const configLines2 = this.configLinesByKeyword.get(keyword) ?? [];
|
|
616
|
+
configLines2.push(this.lineNo);
|
|
617
|
+
this.configLinesByKeyword.set(keyword, configLines2);
|
|
588
618
|
this.config = update;
|
|
589
619
|
}
|
|
590
620
|
keepUnusedConfigLine(keyword, statement, reason) {
|
|
@@ -669,13 +699,16 @@ var MapReader = class {
|
|
|
669
699
|
openBlock() {
|
|
670
700
|
if (this.lastPipeline) {
|
|
671
701
|
this.blockPipeline = this.lastPipeline;
|
|
702
|
+
this.recordBlockLine(this.blockPipeline, "open");
|
|
672
703
|
this.blockOpening = { line: this.lineNo, text: this.currentLine };
|
|
673
704
|
} else this.diag('Ignored "{" \u2014 a block must directly follow a pipeline line');
|
|
674
705
|
this.lastPipeline = null;
|
|
675
706
|
}
|
|
676
707
|
closeBlock() {
|
|
677
|
-
if (this.blockPipeline)
|
|
678
|
-
|
|
708
|
+
if (this.blockPipeline) {
|
|
709
|
+
this.recordBlockLine(this.blockPipeline, "close");
|
|
710
|
+
this.blockPipeline = null;
|
|
711
|
+
} else this.diag('Ignored "}" \u2014 no pipeline block is open');
|
|
679
712
|
this.lastPipeline = null;
|
|
680
713
|
}
|
|
681
714
|
endUnclosedBlock() {
|
|
@@ -744,7 +777,10 @@ var MapReader = class {
|
|
|
744
777
|
const closesBlock = this.blockPipeline !== null && statement.endsWith("}");
|
|
745
778
|
if (closesBlock) statement = statement.slice(0, -1).trim();
|
|
746
779
|
if (statement) this.readStatement(statement);
|
|
747
|
-
if (closesBlock
|
|
780
|
+
if (closesBlock && this.blockPipeline) {
|
|
781
|
+
this.recordBlockLine(this.blockPipeline, "close");
|
|
782
|
+
this.blockPipeline = null;
|
|
783
|
+
}
|
|
748
784
|
}
|
|
749
785
|
readStatement(line) {
|
|
750
786
|
const kw = keywordOf(line);
|
|
@@ -837,7 +873,7 @@ var MapReader = class {
|
|
|
837
873
|
labelOffset: node.labelOffset,
|
|
838
874
|
color: node.color
|
|
839
875
|
});
|
|
840
|
-
this.
|
|
876
|
+
this.addElement(anchor);
|
|
841
877
|
this.register(node.name, label, id);
|
|
842
878
|
}
|
|
843
879
|
/** `component`/`market`/`ecosystem` and `build`/`buy`/`outsource` lines with coordinates. */
|
|
@@ -857,7 +893,7 @@ var MapReader = class {
|
|
|
857
893
|
decorators: Object.keys(decorators).length ? decorators : void 0,
|
|
858
894
|
color: node.color
|
|
859
895
|
});
|
|
860
|
-
this.
|
|
896
|
+
this.addElement(component);
|
|
861
897
|
if (node.urlRef) this.addUrlRef(node.urlRef);
|
|
862
898
|
this.register(node.name, label, id);
|
|
863
899
|
return true;
|
|
@@ -879,7 +915,7 @@ var MapReader = class {
|
|
|
879
915
|
position: this.position(split.coords.a, split.coords.b),
|
|
880
916
|
color
|
|
881
917
|
});
|
|
882
|
-
this.
|
|
918
|
+
this.addElement(note);
|
|
883
919
|
}
|
|
884
920
|
/** `pipeline X [s, e]`, `pipeline X` (block form, range from children) — optionally with `{` at the end. */
|
|
885
921
|
readPipeline(line, after) {
|
|
@@ -917,6 +953,7 @@ var MapReader = class {
|
|
|
917
953
|
this.pendingPipeline.push(pending);
|
|
918
954
|
if (opensBlock) {
|
|
919
955
|
this.blockPipeline = pending;
|
|
956
|
+
this.recordBlockLine(pending, "open");
|
|
920
957
|
this.blockOpening = { line: this.lineNo, text: this.currentLine };
|
|
921
958
|
} else this.lastPipeline = pending;
|
|
922
959
|
}
|
|
@@ -949,6 +986,13 @@ var MapReader = class {
|
|
|
949
986
|
this.reportTextBefore(split.name);
|
|
950
987
|
readSuffix(split.suffix, NO_SUFFIXES, this.report);
|
|
951
988
|
this.setConfig("size", { ...this.config, size: { width: coords.a, height: coords.b } });
|
|
989
|
+
const problem = sizeOutsideLimits(coords.a, coords.b);
|
|
990
|
+
if (problem) this.diag(problem);
|
|
991
|
+
if (!Number.isInteger(coords.a) || !Number.isInteger(coords.b)) {
|
|
992
|
+
this.diag(
|
|
993
|
+
`Map size ${coords.a}\xD7${coords.b} is not in whole pixels \u2014 OWM only reads whole-pixel sizes`
|
|
994
|
+
);
|
|
995
|
+
}
|
|
952
996
|
}
|
|
953
997
|
readEvolution(line, after) {
|
|
954
998
|
const parts = after.split("->").map((s) => s.trim());
|
|
@@ -1021,7 +1065,7 @@ var MapReader = class {
|
|
|
1021
1065
|
text,
|
|
1022
1066
|
...color ? { color } : {}
|
|
1023
1067
|
};
|
|
1024
|
-
this.
|
|
1068
|
+
this.addElement(annotation);
|
|
1025
1069
|
}
|
|
1026
1070
|
/** Project extension (freeform drawing): `line [[v,e], [v,e], …] (closed) (dashed) (color x)`. */
|
|
1027
1071
|
readDrawing(line, after) {
|
|
@@ -1041,7 +1085,7 @@ var MapReader = class {
|
|
|
1041
1085
|
...suffix.strokeStyle ? { strokeStyle: suffix.strokeStyle } : {},
|
|
1042
1086
|
...suffix.color ? { color: suffix.color } : {}
|
|
1043
1087
|
};
|
|
1044
|
-
this.
|
|
1088
|
+
this.addElement(drawing);
|
|
1045
1089
|
}
|
|
1046
1090
|
/** Canonical OWM form: `<kind> [vis1, mat1, vis2, mat2]` (two corners, normalized). */
|
|
1047
1091
|
readAttitude(line, after, kind) {
|
|
@@ -1051,7 +1095,7 @@ var MapReader = class {
|
|
|
1051
1095
|
const { color } = readSuffix(split.after, ATTITUDE_SUFFIXES, this.report);
|
|
1052
1096
|
const { a, b, c, d } = split.coords;
|
|
1053
1097
|
const corners = [this.clamp(a), this.clamp(b), this.clamp(c), this.clamp(d)];
|
|
1054
|
-
this.
|
|
1098
|
+
this.addElement(makeAttitude(this.ids, kind, corners, color));
|
|
1055
1099
|
}
|
|
1056
1100
|
/** OWM: `url Name [https://…]` — definition, referenced via `url(Name)` on elements. */
|
|
1057
1101
|
readUrlDefinition(line, after) {
|
|
@@ -1087,7 +1131,7 @@ var MapReader = class {
|
|
|
1087
1131
|
position: this.position(node.coords.a, node.coords.b),
|
|
1088
1132
|
...node.color ? { color: node.color } : {}
|
|
1089
1133
|
};
|
|
1090
|
-
this.
|
|
1134
|
+
this.addElement(accelerator);
|
|
1091
1135
|
this.register(node.name, label, id);
|
|
1092
1136
|
}
|
|
1093
1137
|
readSubmap(line, after) {
|
|
@@ -1102,7 +1146,7 @@ var MapReader = class {
|
|
|
1102
1146
|
position: this.position(node.coords.a, node.coords.b),
|
|
1103
1147
|
...node.color ? { color: node.color } : {}
|
|
1104
1148
|
};
|
|
1105
|
-
this.
|
|
1149
|
+
this.addElement(submap);
|
|
1106
1150
|
if (node.urlRef) this.addUrlRef(node.urlRef);
|
|
1107
1151
|
this.register(node.name, label, id);
|
|
1108
1152
|
}
|
|
@@ -1116,11 +1160,20 @@ var MapReader = class {
|
|
|
1116
1160
|
const indexById = new Map(this.elements.map((el, index) => [el.id, index]));
|
|
1117
1161
|
this.resolveEvolves(indexById);
|
|
1118
1162
|
this.resolveMethods(indexById);
|
|
1163
|
+
const urlReferences = /* @__PURE__ */ new Map();
|
|
1164
|
+
const usedDefinitions = /* @__PURE__ */ new Set();
|
|
1119
1165
|
const keptDefinitions = resolveUrlReferences(
|
|
1120
1166
|
this.elements,
|
|
1121
1167
|
this.pendingUrlRefs,
|
|
1122
1168
|
this.urlDefinitions,
|
|
1123
|
-
this.reportAt
|
|
1169
|
+
this.reportAt,
|
|
1170
|
+
(element, definition) => {
|
|
1171
|
+
if (definition) usedDefinitions.add(definition);
|
|
1172
|
+
urlReferences.set(
|
|
1173
|
+
element.id,
|
|
1174
|
+
definition ? { kind: "definition", name: definition.name } : { kind: "embedded" }
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1124
1177
|
);
|
|
1125
1178
|
for (const definition of keptDefinitions) {
|
|
1126
1179
|
this.keepLine(
|
|
@@ -1130,6 +1183,7 @@ var MapReader = class {
|
|
|
1130
1183
|
);
|
|
1131
1184
|
}
|
|
1132
1185
|
const declaredNames = /* @__PURE__ */ new Set([...this.idBySourceName.keys(), ...this.idByElementName.keys()]);
|
|
1186
|
+
const edgeLineById = /* @__PURE__ */ new Map();
|
|
1133
1187
|
const edges = buildEdges(
|
|
1134
1188
|
this.pendingLinks,
|
|
1135
1189
|
(name) => this.idOfName(name),
|
|
@@ -1137,10 +1191,18 @@ var MapReader = class {
|
|
|
1137
1191
|
this.keepLine(link.raw, link.lineNo, link.text);
|
|
1138
1192
|
this.diag(`Link: "${missing}" not found`, link.lineNo, link.text);
|
|
1139
1193
|
},
|
|
1140
|
-
(link) => link.label && this.readText(link.label, link.lineNo, link.text)
|
|
1194
|
+
(link) => link.label && this.readText(link.label, link.lineNo, link.text),
|
|
1195
|
+
(edge, link) => edgeLineById.set(edge.id, link.lineNo)
|
|
1141
1196
|
);
|
|
1142
1197
|
const kept = this.passthrough.filter((_, index) => !removedFromPassthrough.has(index));
|
|
1143
1198
|
const rawPassthrough = withoutClosersOfKeptComments(kept, this.reportAt);
|
|
1199
|
+
const sourceMap = this.sourceMap({
|
|
1200
|
+
removedFromPassthrough,
|
|
1201
|
+
usedDefinitions,
|
|
1202
|
+
urlReferences,
|
|
1203
|
+
edgeLineById,
|
|
1204
|
+
kept
|
|
1205
|
+
});
|
|
1144
1206
|
const map = compact({
|
|
1145
1207
|
schemaVersion: CURRENT_SCHEMA_VERSION,
|
|
1146
1208
|
config: this.config,
|
|
@@ -1150,10 +1212,58 @@ var MapReader = class {
|
|
|
1150
1212
|
});
|
|
1151
1213
|
return {
|
|
1152
1214
|
result: { map: validateMap(map), diagnostics: this.diagnostics },
|
|
1215
|
+
sourceMap,
|
|
1153
1216
|
declaredNames,
|
|
1154
1217
|
keywordLinks: this.keywordLinks
|
|
1155
1218
|
};
|
|
1156
1219
|
}
|
|
1220
|
+
sourceMap(parts) {
|
|
1221
|
+
const primaryLineOf = /* @__PURE__ */ new Map();
|
|
1222
|
+
const secondaryLinesOf = /* @__PURE__ */ new Map();
|
|
1223
|
+
const addSecondary = (key, line) => {
|
|
1224
|
+
const lines = secondaryLinesOf.get(key) ?? [];
|
|
1225
|
+
lines.push(line);
|
|
1226
|
+
secondaryLinesOf.set(key, lines);
|
|
1227
|
+
};
|
|
1228
|
+
const addRepeated = (key, lines) => {
|
|
1229
|
+
if (!lines?.length) return;
|
|
1230
|
+
primaryLineOf.set(key, lines[lines.length - 1]);
|
|
1231
|
+
for (const line of lines.slice(0, -1)) addSecondary(key, line);
|
|
1232
|
+
};
|
|
1233
|
+
for (const [keyword, lines] of this.configLinesByKeyword) {
|
|
1234
|
+
addRepeated(`config:${keyword}`, lines);
|
|
1235
|
+
}
|
|
1236
|
+
for (const unused of this.unusedConfigLines) {
|
|
1237
|
+
if (parts.removedFromPassthrough.has(unused.index)) {
|
|
1238
|
+
addSecondary(`config:${unused.keyword}`, unused.line);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
for (const definition of parts.usedDefinitions) {
|
|
1242
|
+
primaryLineOf.set(`url:${definition.name}`, definition.lineNo);
|
|
1243
|
+
}
|
|
1244
|
+
for (const el of this.elements) {
|
|
1245
|
+
const isChild = el.elementType === "component" && el.pipelineId !== void 0;
|
|
1246
|
+
const key = `${isChild ? "child" : "element"}:${el.id}`;
|
|
1247
|
+
const line = this.declarationLineById.get(el.id);
|
|
1248
|
+
if (line !== void 0) primaryLineOf.set(key, line);
|
|
1249
|
+
for (const methodLine of this.methodLinesById.get(el.id) ?? []) addSecondary(key, methodLine);
|
|
1250
|
+
addRepeated(`evolve:${el.id}`, this.evolveLinesById.get(el.id));
|
|
1251
|
+
}
|
|
1252
|
+
for (const [id, line] of parts.edgeLineById) primaryLineOf.set(`edge:${id}`, line);
|
|
1253
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
1254
|
+
for (const [pipeline, lines] of this.blockLines) {
|
|
1255
|
+
const id = this.pipelineIds.get(pipeline);
|
|
1256
|
+
if (id !== void 0) blocks.set(id, lines);
|
|
1257
|
+
}
|
|
1258
|
+
return {
|
|
1259
|
+
primaryLineOf,
|
|
1260
|
+
secondaryLinesOf,
|
|
1261
|
+
passthrough: parts.kept.map((entry) => ({ line: entry.lineNo, isComment: entry.isComment })),
|
|
1262
|
+
blocks,
|
|
1263
|
+
rangeFromChildren: this.rangeFromChildren,
|
|
1264
|
+
urlReferences: parts.urlReferences
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1157
1267
|
/**
|
|
1158
1268
|
* A pipeline pairs with the component written under its name: it takes that component's height,
|
|
1159
1269
|
* as the serializer assumes when it leaves out `(y …)`. Block children are not such components.
|
|
@@ -1191,6 +1301,8 @@ var MapReader = class {
|
|
|
1191
1301
|
}
|
|
1192
1302
|
const visibility = p.visibility ?? component?.position.visibility ?? 0.5;
|
|
1193
1303
|
const id = this.ids.alloc("pipeline", label);
|
|
1304
|
+
this.pipelineIds.set(p, id);
|
|
1305
|
+
if (writtenRange.fromChildren) this.rangeFromChildren.add(id);
|
|
1194
1306
|
if (this.idOfName(p.name) === void 0 && this.idOfName(label) === void 0) {
|
|
1195
1307
|
this.register(p.name, label, id);
|
|
1196
1308
|
}
|
|
@@ -1205,9 +1317,9 @@ var MapReader = class {
|
|
|
1205
1317
|
this.ids,
|
|
1206
1318
|
childNames
|
|
1207
1319
|
);
|
|
1208
|
-
this.
|
|
1320
|
+
this.addElement(pipeline, p.lineNo);
|
|
1209
1321
|
children.forEach((child, index) => {
|
|
1210
|
-
this.
|
|
1322
|
+
this.addElement(child, p.children[index].lineNo);
|
|
1211
1323
|
this.register(p.children[index].name, child.label, child.id);
|
|
1212
1324
|
});
|
|
1213
1325
|
}
|
|
@@ -1235,6 +1347,9 @@ var MapReader = class {
|
|
|
1235
1347
|
this.diag(message, ev.lineNo, ev.text);
|
|
1236
1348
|
}
|
|
1237
1349
|
evolveLineOf.set(component.id, ev.lineNo);
|
|
1350
|
+
const evolveLines = this.evolveLinesById.get(component.id) ?? [];
|
|
1351
|
+
evolveLines.push(ev.lineNo);
|
|
1352
|
+
this.evolveLinesById.set(component.id, evolveLines);
|
|
1238
1353
|
const movement = compact({
|
|
1239
1354
|
targetEvolution: ev.target,
|
|
1240
1355
|
newLabel: ev.newLabel && this.readText(ev.newLabel, ev.lineNo, ev.text),
|
|
@@ -1260,6 +1375,9 @@ var MapReader = class {
|
|
|
1260
1375
|
this.diag(message, pm.lineNo, pm.text);
|
|
1261
1376
|
}
|
|
1262
1377
|
this.elements[index] = { ...el, decorators: { ...el.decorators, method: pm.method } };
|
|
1378
|
+
const methodLines = this.methodLinesById.get(el.id) ?? [];
|
|
1379
|
+
methodLines.push(pm.lineNo);
|
|
1380
|
+
this.methodLinesById.set(el.id, methodLines);
|
|
1263
1381
|
}
|
|
1264
1382
|
}
|
|
1265
1383
|
};
|
|
@@ -1301,7 +1419,7 @@ function replacedConfigLines(unusedLines, config, report) {
|
|
|
1301
1419
|
}
|
|
1302
1420
|
return replacedIndexes;
|
|
1303
1421
|
}
|
|
1304
|
-
function resolveUrlReferences(elements, refs, definitions, report) {
|
|
1422
|
+
function resolveUrlReferences(elements, refs, definitions, report, resolved) {
|
|
1305
1423
|
const definitionByName = /* @__PURE__ */ new Map();
|
|
1306
1424
|
const replaced = /* @__PURE__ */ new Set();
|
|
1307
1425
|
for (const definition of definitions) {
|
|
@@ -1328,6 +1446,7 @@ function resolveUrlReferences(elements, refs, definitions, report) {
|
|
|
1328
1446
|
const el = elements[index];
|
|
1329
1447
|
if (el.elementType === "component") elements[index] = { ...el, url: address };
|
|
1330
1448
|
else if (el.elementType === "submap") elements[index] = { ...el, urlRef: address };
|
|
1449
|
+
resolved(el, definition);
|
|
1331
1450
|
}
|
|
1332
1451
|
return definitions.filter((definition) => replaced.has(definition) || !used.has(definition));
|
|
1333
1452
|
}
|
|
@@ -1578,6 +1697,17 @@ function parseEvolve(after, report) {
|
|
|
1578
1697
|
labelOffset: lo.labelOffset ?? void 0
|
|
1579
1698
|
});
|
|
1580
1699
|
}
|
|
1700
|
+
function sizeOutsideLimits(width, height) {
|
|
1701
|
+
const below = width < PLOT_MIN.width || height < PLOT_MIN.height;
|
|
1702
|
+
const above = width > PLOT_MAX.width || height > PLOT_MAX.height;
|
|
1703
|
+
if (!below && !above) return null;
|
|
1704
|
+
const drawnWidth = drawnPlotLength(width, "width");
|
|
1705
|
+
const drawnHeight = drawnPlotLength(height, "height");
|
|
1706
|
+
const minimum = `${PLOT_MIN.width}\xD7${PLOT_MIN.height}`;
|
|
1707
|
+
const maximum = `${PLOT_MAX.width}\xD7${PLOT_MAX.height}`;
|
|
1708
|
+
const limit = below && above ? `outside ${minimum} to ${maximum}` : below ? `below the minimum ${minimum}` : `above the maximum ${maximum}`;
|
|
1709
|
+
return `Map size ${width}\xD7${height} is ${limit} \u2014 drawn at ${drawnWidth}\xD7${drawnHeight}`;
|
|
1710
|
+
}
|
|
1581
1711
|
|
|
1582
1712
|
// src/serializer.ts
|
|
1583
1713
|
function urlOf(el) {
|
|
@@ -1600,16 +1730,44 @@ function uniqueNames(map) {
|
|
|
1600
1730
|
}
|
|
1601
1731
|
return byId;
|
|
1602
1732
|
}
|
|
1603
|
-
|
|
1733
|
+
var EMBEDDABLE_ADDRESS_RE = /^[a-z][\w+.-]*:\/\/[^\s()]*$/i;
|
|
1734
|
+
function planUrls(map, nameOf, keptDefinitionNames, hints) {
|
|
1604
1735
|
const namesInUse = new UniqueNames();
|
|
1605
1736
|
for (const name of keptDefinitionNames) namesInUse.claim(name);
|
|
1606
|
-
const
|
|
1737
|
+
const addressByHintedName = /* @__PURE__ */ new Map();
|
|
1738
|
+
const definitionNameById = /* @__PURE__ */ new Map();
|
|
1739
|
+
const referenceById = /* @__PURE__ */ new Map();
|
|
1740
|
+
for (const el of map.elements) {
|
|
1741
|
+
const address = urlOf(el);
|
|
1742
|
+
const hint = hints?.get(el.id);
|
|
1743
|
+
if (!address || !hint) continue;
|
|
1744
|
+
if (hint.kind === "embedded") {
|
|
1745
|
+
if (EMBEDDABLE_ADDRESS_RE.test(address)) referenceById.set(el.id, address);
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
const hintedAddress = addressByHintedName.get(hint.name);
|
|
1749
|
+
if (hintedAddress === void 0) {
|
|
1750
|
+
addressByHintedName.set(hint.name, address);
|
|
1751
|
+
namesInUse.reserve(hint.name);
|
|
1752
|
+
} else if (hintedAddress !== address) continue;
|
|
1753
|
+
definitionNameById.set(el.id, hint.name);
|
|
1754
|
+
}
|
|
1755
|
+
const definitions = [];
|
|
1756
|
+
const written = /* @__PURE__ */ new Set();
|
|
1607
1757
|
for (const el of map.elements) {
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1758
|
+
const address = urlOf(el);
|
|
1759
|
+
if (!address || referenceById.has(el.id)) continue;
|
|
1760
|
+
let name = definitionNameById.get(el.id);
|
|
1761
|
+
if (name === void 0) {
|
|
1762
|
+
const readable = nameOf(el).replace(/[()[\]<>]/g, " ").replace(/\s+/g, " ").trim();
|
|
1763
|
+
name = namesInUse.claim(`${readable} URL`.trim());
|
|
1764
|
+
}
|
|
1765
|
+
referenceById.set(el.id, name);
|
|
1766
|
+
if (written.has(name)) continue;
|
|
1767
|
+
written.add(name);
|
|
1768
|
+
definitions.push({ name, address });
|
|
1611
1769
|
}
|
|
1612
|
-
return
|
|
1770
|
+
return { definitions, referenceById };
|
|
1613
1771
|
}
|
|
1614
1772
|
function offsetSuffix(lo) {
|
|
1615
1773
|
return lo ? ` label [${r(lo.dx)}, ${r(lo.dy)}]` : "";
|
|
@@ -1629,20 +1787,21 @@ function decoratorSuffix(dec) {
|
|
|
1629
1787
|
return out;
|
|
1630
1788
|
}
|
|
1631
1789
|
function configLines(config) {
|
|
1632
|
-
const lines = [`title ${writtenText(config.title)}`];
|
|
1633
|
-
|
|
1634
|
-
if (config.
|
|
1790
|
+
const lines = [{ key: "config:title", text: `title ${writtenText(config.title)}` }];
|
|
1791
|
+
const add = (keyword, text) => lines.push({ key: `config:${keyword}`, text });
|
|
1792
|
+
if (config.style) add("style", `style ${config.style}`);
|
|
1793
|
+
if (config.size) add("size", `size [${r(config.size.width)}, ${r(config.size.height)}]`);
|
|
1635
1794
|
if (config.evolutionLabels) {
|
|
1636
|
-
|
|
1795
|
+
add("evolution", `evolution ${config.evolutionLabels.map(writtenText).join("->")}`);
|
|
1637
1796
|
}
|
|
1638
1797
|
if (config.yAxisLabel) {
|
|
1639
1798
|
const ends = config.yAxisEndLabels;
|
|
1640
1799
|
const endLabels = ends ? `->${writtenText(ends[0])}->${writtenText(ends[1])}` : "";
|
|
1641
|
-
|
|
1800
|
+
add("y-axis", `y-axis ${writtenText(config.yAxisLabel)}${endLabels}`);
|
|
1642
1801
|
}
|
|
1643
1802
|
if (config.annotationsBoxPosition) {
|
|
1644
1803
|
const b = config.annotationsBoxPosition;
|
|
1645
|
-
|
|
1804
|
+
add("annotations", `annotations [${r(b.visibility)}, ${r(b.evolution)}]`);
|
|
1646
1805
|
}
|
|
1647
1806
|
return lines;
|
|
1648
1807
|
}
|
|
@@ -1652,16 +1811,16 @@ function passthroughLines(map) {
|
|
|
1652
1811
|
if (map.config.yAxisLabel) emitted.add("y-axis");
|
|
1653
1812
|
const comments = new CommentStripper();
|
|
1654
1813
|
const lines = [];
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1814
|
+
const urlDefinitionNames = [];
|
|
1815
|
+
(map.rawPassthrough ?? []).forEach((raw, index) => {
|
|
1657
1816
|
const code = comments.strip(raw).code;
|
|
1658
1817
|
const keyword = keywordOf(code);
|
|
1659
|
-
if (emitted.has(keyword))
|
|
1660
|
-
lines.push(raw);
|
|
1818
|
+
if (emitted.has(keyword)) return;
|
|
1819
|
+
lines.push({ key: `raw:${index}`, text: raw });
|
|
1661
1820
|
const definition = keyword === "url" ? readUrlDefinition(code.trim().slice(3)) : null;
|
|
1662
|
-
if (definition)
|
|
1663
|
-
}
|
|
1664
|
-
return { lines, urlDefinitionNames
|
|
1821
|
+
if (definition) urlDefinitionNames.push(definition.name);
|
|
1822
|
+
});
|
|
1823
|
+
return { lines, urlDefinitionNames };
|
|
1665
1824
|
}
|
|
1666
1825
|
function pipelineHeights(map, pipelineIds, nameOf) {
|
|
1667
1826
|
const componentVisibilityByName = /* @__PURE__ */ new Map();
|
|
@@ -1683,24 +1842,27 @@ function edgeLines(map, nameOf) {
|
|
|
1683
1842
|
const from = namesById.get(edge.from) ?? edge.from;
|
|
1684
1843
|
const to = namesById.get(edge.to) ?? edge.to;
|
|
1685
1844
|
const annotation = edge.label ? `; ${writtenText(edge.label)}` : "";
|
|
1686
|
-
|
|
1845
|
+
const key = `edge:${edge.id}`;
|
|
1846
|
+
if (edge.edgeType === "dependency") return { key, text: `${from} -> ${to}${annotation}` };
|
|
1687
1847
|
const op = edge.flowValue ? `+'${edge.flowValue}'${edge.bidirectional ? "<>" : ">"}` : edge.bidirectional ? "+<>" : "+>";
|
|
1688
|
-
return `${from} ${op} ${to}${annotation}
|
|
1848
|
+
return { key, text: `${from} ${op} ${to}${annotation}` };
|
|
1689
1849
|
});
|
|
1690
1850
|
}
|
|
1691
1851
|
function serializeDSL(map) {
|
|
1852
|
+
return [...serializeKeyedLines(map).map((line) => line.text), ""].join("\n");
|
|
1853
|
+
}
|
|
1854
|
+
function serializeKeyedLines(map, hints = {}) {
|
|
1692
1855
|
const names = uniqueNames(map);
|
|
1693
1856
|
const nameOf = (el) => names.get(el.id) ?? writtenText(el.label);
|
|
1694
1857
|
const passthrough = passthroughLines(map);
|
|
1695
1858
|
const lines = configLines(map.config);
|
|
1696
|
-
const
|
|
1697
|
-
for (const
|
|
1698
|
-
|
|
1699
|
-
if (defName) lines.push(`url ${defName} [${urlOf(el)}]`);
|
|
1859
|
+
const urls = planUrls(map, nameOf, passthrough.urlDefinitionNames, hints.urlReferences);
|
|
1860
|
+
for (const { name, address } of urls.definitions) {
|
|
1861
|
+
lines.push({ key: `url:${name}`, text: `url ${name} [${address}]` });
|
|
1700
1862
|
}
|
|
1701
1863
|
const urlSuffix = (el) => {
|
|
1702
|
-
const
|
|
1703
|
-
return
|
|
1864
|
+
const reference = urls.referenceById.get(el.id);
|
|
1865
|
+
return reference ? ` url(${reference})` : "";
|
|
1704
1866
|
};
|
|
1705
1867
|
const pipelineIds = new Set(
|
|
1706
1868
|
map.elements.filter((e) => e.elementType === "pipeline").map((e) => e.id)
|
|
@@ -1718,24 +1880,28 @@ function serializeDSL(map) {
|
|
|
1718
1880
|
const evolveLines = [];
|
|
1719
1881
|
for (const el of map.elements) {
|
|
1720
1882
|
if (el.elementType === "component" && el.movement) {
|
|
1721
|
-
evolveLines.push(evolveLine(el, nameOf(el)));
|
|
1883
|
+
evolveLines.push({ key: `evolve:${el.id}`, text: evolveLine(el, nameOf(el)) });
|
|
1722
1884
|
}
|
|
1723
1885
|
if (el.elementType === "component" && el.pipelineId && pipelineIds.has(el.pipelineId)) {
|
|
1724
1886
|
continue;
|
|
1725
1887
|
}
|
|
1726
|
-
lines.push(
|
|
1888
|
+
lines.push({
|
|
1889
|
+
key: `element:${el.id}`,
|
|
1890
|
+
text: elementLine(el, nameOf(el)) + pipelineHeight(el) + urlSuffix(el)
|
|
1891
|
+
});
|
|
1727
1892
|
const kids = childrenByPipeline.get(el.id) ?? [];
|
|
1728
1893
|
if (kids.length) {
|
|
1729
|
-
lines.push("{");
|
|
1894
|
+
lines.push({ key: `block-open:${el.id}`, text: "{" });
|
|
1730
1895
|
for (const k of kids) {
|
|
1731
|
-
lines.push(
|
|
1732
|
-
`
|
|
1733
|
-
|
|
1896
|
+
lines.push({
|
|
1897
|
+
key: `child:${k.id}`,
|
|
1898
|
+
text: ` component ${nameOf(k)} [${r(k.position.evolution)}]${decoratorSuffix(k.decorators)}${colorSuffix(k)}${offsetSuffix(k.labelOffset)}`
|
|
1899
|
+
});
|
|
1734
1900
|
}
|
|
1735
|
-
lines.push("}");
|
|
1901
|
+
lines.push({ key: `block-close:${el.id}`, text: "}" });
|
|
1736
1902
|
}
|
|
1737
1903
|
}
|
|
1738
|
-
return [...lines, ...evolveLines, ...edgeLines(map, nameOf), ...passthrough.lines
|
|
1904
|
+
return [...lines, ...evolveLines, ...edgeLines(map, nameOf), ...passthrough.lines];
|
|
1739
1905
|
}
|
|
1740
1906
|
function elementLine(el, name) {
|
|
1741
1907
|
const p = el.position;
|
|
@@ -1773,6 +1939,593 @@ function evolveLine(el, name) {
|
|
|
1773
1939
|
return `evolve ${label} ${r(mv.targetEvolution)}${method}${offsetSuffix(mv.labelOffset)}`;
|
|
1774
1940
|
}
|
|
1775
1941
|
|
|
1776
|
-
|
|
1942
|
+
// src/patch.ts
|
|
1943
|
+
function patchDSL(originalText, map, options = {}) {
|
|
1944
|
+
return patchDSLWithOutcome(originalText, map, options).text;
|
|
1945
|
+
}
|
|
1946
|
+
function patchDSLWithOutcome(originalText, map, options = {}) {
|
|
1947
|
+
const original = typeof originalText === "string" ? originalText : "";
|
|
1948
|
+
let canonical;
|
|
1949
|
+
try {
|
|
1950
|
+
canonical = serializeDSL(map);
|
|
1951
|
+
} catch {
|
|
1952
|
+
return { text: original, outcome: "unchanged" };
|
|
1953
|
+
}
|
|
1954
|
+
try {
|
|
1955
|
+
const expected = fingerprint(parseDSL(canonical));
|
|
1956
|
+
const source = readSource(original);
|
|
1957
|
+
const edited = alignToSource(source.map, map, options.previous);
|
|
1958
|
+
for (const mode of ["minimal", "rewrite"]) {
|
|
1959
|
+
const text = patchLines(source, edited, mode);
|
|
1960
|
+
if (text !== null && fingerprint(parseDSL(text)) === expected) {
|
|
1961
|
+
return { text, outcome: mode === "minimal" ? "patched" : "rewritten" };
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
return { text: withLineBreaksOf(source, canonical), outcome: "serialized" };
|
|
1965
|
+
} catch {
|
|
1966
|
+
return { text: canonical, outcome: "serialized" };
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
function readSource(text) {
|
|
1970
|
+
const lines = splitLines(text);
|
|
1971
|
+
const parsed = parseDSLWithSourceMap(text);
|
|
1972
|
+
const crlf = (text.match(/\r\n/g) ?? []).length;
|
|
1973
|
+
const lf = (text.match(/\n/g) ?? []).length - crlf;
|
|
1974
|
+
return {
|
|
1975
|
+
lines,
|
|
1976
|
+
shapes: shapeLines(lines),
|
|
1977
|
+
map: parsed.map,
|
|
1978
|
+
sourceMap: parsed.sourceMap,
|
|
1979
|
+
lineBreak: crlf > lf ? "\r\n" : "\n",
|
|
1980
|
+
endsWithLineBreak: text === "" || text.endsWith("\n")
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
function splitLines(text) {
|
|
1984
|
+
const lines = [];
|
|
1985
|
+
const lineBreaks = /\r?\n/g;
|
|
1986
|
+
let start = 0;
|
|
1987
|
+
for (let match = lineBreaks.exec(text); match; match = lineBreaks.exec(text)) {
|
|
1988
|
+
lines.push({ text: text.slice(start, match.index), lineBreak: match[0] });
|
|
1989
|
+
start = match.index + match[0].length;
|
|
1990
|
+
}
|
|
1991
|
+
if (start < text.length) lines.push({ text: text.slice(start), lineBreak: "" });
|
|
1992
|
+
return lines;
|
|
1993
|
+
}
|
|
1994
|
+
function withoutTrailingCarriageReturns2(text) {
|
|
1995
|
+
let end = text.length;
|
|
1996
|
+
while (end > 0 && text.charCodeAt(end - 1) === 13) end--;
|
|
1997
|
+
return text.slice(0, end);
|
|
1998
|
+
}
|
|
1999
|
+
function shapeLines(lines) {
|
|
2000
|
+
const comments = new CommentStripper();
|
|
2001
|
+
return lines.map(({ text }) => {
|
|
2002
|
+
const raw = withoutTrailingCarriageReturns2(text);
|
|
2003
|
+
const startsInBlockComment = comments.insideBlockComment;
|
|
2004
|
+
let fragments = comments.strip(raw).comments;
|
|
2005
|
+
let rest = raw;
|
|
2006
|
+
let commentHead = "";
|
|
2007
|
+
let commentTail = "";
|
|
2008
|
+
let editable = true;
|
|
2009
|
+
if (startsInBlockComment) {
|
|
2010
|
+
const head = fragments[0];
|
|
2011
|
+
if (head?.endsWith("*/") && raw.startsWith(head)) {
|
|
2012
|
+
commentHead = head;
|
|
2013
|
+
rest = raw.slice(head.length);
|
|
2014
|
+
fragments = fragments.slice(1);
|
|
2015
|
+
} else {
|
|
2016
|
+
editable = false;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
let codePart = rest;
|
|
2020
|
+
if (editable && fragments.length) {
|
|
2021
|
+
const comment = fragments[0];
|
|
2022
|
+
if (fragments.length > 1 || !rest.endsWith(comment)) {
|
|
2023
|
+
editable = false;
|
|
2024
|
+
} else {
|
|
2025
|
+
const beforeComment = rest.slice(0, rest.length - comment.length);
|
|
2026
|
+
codePart = beforeComment.trimEnd();
|
|
2027
|
+
commentTail = beforeComment.slice(codePart.length) + comment;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
return {
|
|
2031
|
+
indent: /^\s*/.exec(codePart)[0],
|
|
2032
|
+
code: codePart.trim(),
|
|
2033
|
+
commentHead,
|
|
2034
|
+
commentTail,
|
|
2035
|
+
editable,
|
|
2036
|
+
endsInBlockComment: comments.insideBlockComment
|
|
2037
|
+
};
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
function withLineBreaksOf(source, text) {
|
|
2041
|
+
if (source.lines.length === 0) return text;
|
|
2042
|
+
const withBreaks = source.lineBreak === "\n" ? text : text.replace(/\n/g, source.lineBreak);
|
|
2043
|
+
return source.endsWithLineBreak ? withBreaks : withBreaks.replace(/\r?\n$/, "");
|
|
2044
|
+
}
|
|
2045
|
+
function identityOf(el) {
|
|
2046
|
+
switch (el.elementType) {
|
|
2047
|
+
case "annotation":
|
|
2048
|
+
return `annotation\0${el.number}`;
|
|
2049
|
+
case "attitude":
|
|
2050
|
+
return `attitude\0${el.kind}`;
|
|
2051
|
+
default:
|
|
2052
|
+
return `${el.elementType}\0${el.label}`;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
function edgeIdentityOf(edge) {
|
|
2056
|
+
return `${edge.edgeType}\0${edge.from}\0${edge.to}`;
|
|
2057
|
+
}
|
|
2058
|
+
function matchElements(source, edited, hint = /* @__PURE__ */ new Map()) {
|
|
2059
|
+
const unmatchedSource = new Map(source.elements.map((el) => [el.id, el]));
|
|
2060
|
+
const idByEditedId = /* @__PURE__ */ new Map();
|
|
2061
|
+
const claim = (editedId, sourceId) => {
|
|
2062
|
+
idByEditedId.set(editedId, sourceId);
|
|
2063
|
+
unmatchedSource.delete(sourceId);
|
|
2064
|
+
};
|
|
2065
|
+
const unclaimed = () => edited.elements.filter((el) => !idByEditedId.has(el.id));
|
|
2066
|
+
for (const el of edited.elements) {
|
|
2067
|
+
const same = unmatchedSource.get(el.id);
|
|
2068
|
+
if (same && identityOf(same) === identityOf(el)) claim(el.id, el.id);
|
|
2069
|
+
}
|
|
2070
|
+
for (const el of unclaimed()) {
|
|
2071
|
+
const hinted = hint.get(el.id);
|
|
2072
|
+
const candidate = hinted === void 0 ? void 0 : unmatchedSource.get(hinted);
|
|
2073
|
+
if (candidate?.elementType === el.elementType) claim(el.id, candidate.id);
|
|
2074
|
+
}
|
|
2075
|
+
const sourceIdsByIdentity = /* @__PURE__ */ new Map();
|
|
2076
|
+
for (const el of unmatchedSource.values()) {
|
|
2077
|
+
const ids = sourceIdsByIdentity.get(identityOf(el)) ?? [];
|
|
2078
|
+
ids.push(el.id);
|
|
2079
|
+
sourceIdsByIdentity.set(identityOf(el), ids);
|
|
2080
|
+
}
|
|
2081
|
+
for (const el of unclaimed()) {
|
|
2082
|
+
const match = sourceIdsByIdentity.get(identityOf(el))?.shift();
|
|
2083
|
+
if (match !== void 0) claim(el.id, match);
|
|
2084
|
+
}
|
|
2085
|
+
for (const el of unclaimed()) {
|
|
2086
|
+
if (unmatchedSource.get(el.id)?.elementType === el.elementType) claim(el.id, el.id);
|
|
2087
|
+
}
|
|
2088
|
+
return idByEditedId;
|
|
2089
|
+
}
|
|
2090
|
+
function alignToSource(source, edited, previous) {
|
|
2091
|
+
const hint = previous ? matchElements(source, previous) : void 0;
|
|
2092
|
+
const idByEditedId = matchElements(source, edited, hint);
|
|
2093
|
+
const usedIds = /* @__PURE__ */ new Set([...source.elements.map((el) => el.id), ...idByEditedId.values()]);
|
|
2094
|
+
const freshId = (id) => {
|
|
2095
|
+
let candidate = id;
|
|
2096
|
+
for (let suffix = 2; usedIds.has(candidate); suffix++) candidate = `${id}~${suffix}`;
|
|
2097
|
+
usedIds.add(candidate);
|
|
2098
|
+
return candidate;
|
|
2099
|
+
};
|
|
2100
|
+
for (const el of edited.elements) {
|
|
2101
|
+
if (!idByEditedId.has(el.id)) idByEditedId.set(el.id, freshId(el.id));
|
|
2102
|
+
}
|
|
2103
|
+
const rename = (id) => idByEditedId.get(id) ?? id;
|
|
2104
|
+
const elements = edited.elements.map((el) => {
|
|
2105
|
+
const renamed = { ...el, id: rename(el.id) };
|
|
2106
|
+
if (renamed.elementType === "component" && renamed.pipelineId !== void 0) {
|
|
2107
|
+
return { ...renamed, pipelineId: rename(renamed.pipelineId) };
|
|
2108
|
+
}
|
|
2109
|
+
if (renamed.elementType === "pipeline") {
|
|
2110
|
+
return { ...renamed, childIds: renamed.childIds.map(rename) };
|
|
2111
|
+
}
|
|
2112
|
+
return renamed;
|
|
2113
|
+
});
|
|
2114
|
+
const edges = alignEdges(
|
|
2115
|
+
source.edges,
|
|
2116
|
+
edited.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
|
|
2117
|
+
);
|
|
2118
|
+
return {
|
|
2119
|
+
...edited,
|
|
2120
|
+
elements: inSourceOrder(elements, source.elements),
|
|
2121
|
+
edges: inSourceOrder(edges, source.edges)
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
function alignEdges(sourceEdges, editedEdges) {
|
|
2125
|
+
const unmatchedSource = new Map(sourceEdges.map((edge) => [edge.id, edge]));
|
|
2126
|
+
const idByIndex = /* @__PURE__ */ new Map();
|
|
2127
|
+
const claim = (index, id) => {
|
|
2128
|
+
idByIndex.set(index, id);
|
|
2129
|
+
unmatchedSource.delete(id);
|
|
2130
|
+
};
|
|
2131
|
+
editedEdges.forEach((edge, index) => {
|
|
2132
|
+
const same = unmatchedSource.get(edge.id);
|
|
2133
|
+
if (same && edgeIdentityOf(same) === edgeIdentityOf(edge)) claim(index, edge.id);
|
|
2134
|
+
});
|
|
2135
|
+
editedEdges.forEach((edge, index) => {
|
|
2136
|
+
if (idByIndex.has(index)) return;
|
|
2137
|
+
const match = [...unmatchedSource.values()].find(
|
|
2138
|
+
(candidate) => edgeIdentityOf(candidate) === edgeIdentityOf(edge)
|
|
2139
|
+
);
|
|
2140
|
+
if (match) claim(index, match.id);
|
|
2141
|
+
});
|
|
2142
|
+
editedEdges.forEach((edge, index) => {
|
|
2143
|
+
if (idByIndex.has(index)) return;
|
|
2144
|
+
if (unmatchedSource.get(edge.id)?.edgeType === edge.edgeType) claim(index, edge.id);
|
|
2145
|
+
});
|
|
2146
|
+
const usedIds = /* @__PURE__ */ new Set([...sourceEdges.map((edge) => edge.id), ...idByIndex.values()]);
|
|
2147
|
+
return editedEdges.map((edge, index) => {
|
|
2148
|
+
let id = idByIndex.get(index);
|
|
2149
|
+
if (id === void 0) {
|
|
2150
|
+
id = edge.id;
|
|
2151
|
+
for (let suffix = 2; usedIds.has(id); suffix++) id = `${edge.id}~${suffix}`;
|
|
2152
|
+
usedIds.add(id);
|
|
2153
|
+
}
|
|
2154
|
+
return { ...edge, id };
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
function inSourceOrder(items, sourceItems) {
|
|
2158
|
+
const sourceIndex = new Map(sourceItems.map((item, index) => [item.id, index]));
|
|
2159
|
+
const position = (item) => sourceIndex.get(item.id) ?? Number.POSITIVE_INFINITY;
|
|
2160
|
+
return items.map((item, index) => ({ item, index })).sort((a, b) => position(a.item) - position(b.item) || a.index - b.index).map(({ item }) => item);
|
|
2161
|
+
}
|
|
2162
|
+
var isRawKey = (key) => key.startsWith("raw:");
|
|
2163
|
+
function keyedTexts(lines) {
|
|
2164
|
+
return new Map(lines.filter((line) => !isRawKey(line.key)).map((line) => [line.key, line.text]));
|
|
2165
|
+
}
|
|
2166
|
+
function listsByLine(lineCount) {
|
|
2167
|
+
return Array.from({ length: lineCount }, () => []);
|
|
2168
|
+
}
|
|
2169
|
+
function diffPassthrough(before, after) {
|
|
2170
|
+
const remainingByText = /* @__PURE__ */ new Map();
|
|
2171
|
+
for (const line of before.filter((entry) => isRawKey(entry.key))) {
|
|
2172
|
+
const indexes = remainingByText.get(line.text) ?? [];
|
|
2173
|
+
indexes.push(Number(line.key.slice("raw:".length)));
|
|
2174
|
+
remainingByText.set(line.text, indexes);
|
|
2175
|
+
}
|
|
2176
|
+
const added = [];
|
|
2177
|
+
for (const line of after.filter((entry) => isRawKey(entry.key))) {
|
|
2178
|
+
if (remainingByText.get(line.text)?.shift() === void 0) added.push(line);
|
|
2179
|
+
}
|
|
2180
|
+
return { removed: new Set([...remainingByText.values()].flat()), added };
|
|
2181
|
+
}
|
|
2182
|
+
function pipelineOfEachChild(map) {
|
|
2183
|
+
const pipelineById = /* @__PURE__ */ new Map();
|
|
2184
|
+
for (const el of map.elements) {
|
|
2185
|
+
if (el.elementType === "component" && el.pipelineId !== void 0) {
|
|
2186
|
+
pipelineById.set(el.id, el.pipelineId);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
return pipelineById;
|
|
2190
|
+
}
|
|
2191
|
+
function reparentedChildKeys(source, edited) {
|
|
2192
|
+
const pipelineBefore = pipelineOfEachChild(source);
|
|
2193
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2194
|
+
for (const [id, pipelineId] of pipelineOfEachChild(edited)) {
|
|
2195
|
+
const previous = pipelineBefore.get(id);
|
|
2196
|
+
if (previous !== void 0 && previous !== pipelineId) keys.add(`child:${id}`);
|
|
2197
|
+
}
|
|
2198
|
+
return keys;
|
|
2199
|
+
}
|
|
2200
|
+
function pipelinesNoLongerSpannedByChildren(edited, rangeFromChildren) {
|
|
2201
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2202
|
+
if (rangeFromChildren.size === 0) return keys;
|
|
2203
|
+
const evolutionsByPipeline = /* @__PURE__ */ new Map();
|
|
2204
|
+
for (const el of edited.elements) {
|
|
2205
|
+
if (el.elementType !== "component" || el.pipelineId === void 0) continue;
|
|
2206
|
+
const evolutions = evolutionsByPipeline.get(el.pipelineId) ?? [];
|
|
2207
|
+
evolutions.push(roundAsWritten(el.position.evolution));
|
|
2208
|
+
evolutionsByPipeline.set(el.pipelineId, evolutions);
|
|
2209
|
+
}
|
|
2210
|
+
for (const el of edited.elements) {
|
|
2211
|
+
if (el.elementType !== "pipeline" || !rangeFromChildren.has(el.id)) continue;
|
|
2212
|
+
const evolutions = evolutionsByPipeline.get(el.id) ?? [];
|
|
2213
|
+
const read = evolutions.length > 0 ? widenEmptyRange({ start: Math.min(...evolutions), end: Math.max(...evolutions) }) : void 0;
|
|
2214
|
+
const spanned = read !== void 0 && read.start === roundAsWritten(el.evolutionStart) && read.end === roundAsWritten(el.evolutionEnd);
|
|
2215
|
+
if (!spanned) keys.add(`element:${el.id}`);
|
|
2216
|
+
}
|
|
2217
|
+
return keys;
|
|
2218
|
+
}
|
|
2219
|
+
function patchLines(source, edited, mode) {
|
|
2220
|
+
const { lines, shapes, sourceMap } = source;
|
|
2221
|
+
const hints = { urlReferences: sourceMap.urlReferences };
|
|
2222
|
+
const before = serializeKeyedLines(source.map, hints);
|
|
2223
|
+
const after = serializeKeyedLines(edited, hints);
|
|
2224
|
+
const textBefore = keyedTexts(before);
|
|
2225
|
+
const textAfter = keyedTexts(after);
|
|
2226
|
+
const passthrough = diffPassthrough(before, after);
|
|
2227
|
+
const reparented = reparentedChildKeys(source.map, edited);
|
|
2228
|
+
const unspanned = pipelinesNoLongerSpannedByChildren(edited, sourceMap.rangeFromChildren);
|
|
2229
|
+
const statusOf = (key) => {
|
|
2230
|
+
const written = textAfter.get(key);
|
|
2231
|
+
if (written === void 0 || reparented.has(key)) return "deleted";
|
|
2232
|
+
if (mode === "rewrite" || written !== textBefore.get(key) || unspanned.has(key)) {
|
|
2233
|
+
return "changed";
|
|
2234
|
+
}
|
|
2235
|
+
return "same";
|
|
2236
|
+
};
|
|
2237
|
+
const primaryKeysByLine = listsByLine(lines.length);
|
|
2238
|
+
for (const [key, line] of sourceMap.primaryLineOf) {
|
|
2239
|
+
if (textBefore.has(key)) primaryKeysByLine[line - 1]?.push(key);
|
|
2240
|
+
}
|
|
2241
|
+
const secondaryKeysByLine = listsByLine(lines.length);
|
|
2242
|
+
for (const [key, secondaryLines] of sourceMap.secondaryLinesOf) {
|
|
2243
|
+
if (!textBefore.has(key)) continue;
|
|
2244
|
+
for (const line of secondaryLines) secondaryKeysByLine[line - 1]?.push(key);
|
|
2245
|
+
}
|
|
2246
|
+
const passthroughByLine = listsByLine(lines.length);
|
|
2247
|
+
sourceMap.passthrough.forEach((entry, index) => passthroughByLine[entry.line - 1]?.push(index));
|
|
2248
|
+
const bracesByLine = listsByLine(lines.length);
|
|
2249
|
+
for (const [pipelineId, block] of sourceMap.blocks) {
|
|
2250
|
+
if (block.open !== void 0) {
|
|
2251
|
+
bracesByLine[block.open - 1]?.push({ pipelineId, end: "open" });
|
|
2252
|
+
}
|
|
2253
|
+
if (block.close !== void 0) {
|
|
2254
|
+
bracesByLine[block.close - 1]?.push({ pipelineId, end: "close" });
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
const braceRemoved = (pipelineId) => !textAfter.has(`block-open:${pipelineId}`) && (textBefore.has(`block-open:${pipelineId}`) || !textAfter.has(`element:${pipelineId}`));
|
|
2258
|
+
const result = [];
|
|
2259
|
+
for (let index = 0; index < lines.length; index++) {
|
|
2260
|
+
const patched = patchLine(index);
|
|
2261
|
+
if (patched === void 0) return null;
|
|
2262
|
+
result.push(patched);
|
|
2263
|
+
}
|
|
2264
|
+
function patchLine(index) {
|
|
2265
|
+
const line = lines[index];
|
|
2266
|
+
const shape = shapes[index];
|
|
2267
|
+
const primaries = primaryKeysByLine[index];
|
|
2268
|
+
const removedPassthrough = passthroughByLine[index].filter(
|
|
2269
|
+
(entry) => passthrough.removed.has(entry)
|
|
2270
|
+
);
|
|
2271
|
+
const braces = bracesByLine[index];
|
|
2272
|
+
const changed = primaries.some((key) => statusOf(key) !== "same") || secondaryKeysByLine[index].some((key) => statusOf(key) !== "same") || removedPassthrough.length > 0 || braces.some((brace) => braceRemoved(brace.pipelineId));
|
|
2273
|
+
if (!changed) return line.text;
|
|
2274
|
+
if (!shape.editable || primaries.length > 1) return void 0;
|
|
2275
|
+
if (removedPassthrough.some((entry) => sourceMap.passthrough[entry].isComment)) {
|
|
2276
|
+
return void 0;
|
|
2277
|
+
}
|
|
2278
|
+
let statement = null;
|
|
2279
|
+
const primary = primaries[0];
|
|
2280
|
+
if (primary !== void 0) {
|
|
2281
|
+
const status = statusOf(primary);
|
|
2282
|
+
if (status === "changed") statement = textAfter.get(primary).trim();
|
|
2283
|
+
else if (status === "deleted") statement = "";
|
|
2284
|
+
} else if (secondaryKeysByLine[index].length || removedPassthrough.length) {
|
|
2285
|
+
statement = "";
|
|
2286
|
+
}
|
|
2287
|
+
const open = braces.find((brace) => brace.end === "open");
|
|
2288
|
+
const close = braces.find((brace) => brace.end === "close");
|
|
2289
|
+
let rest = shape.code;
|
|
2290
|
+
let prefix = "";
|
|
2291
|
+
let suffix = "";
|
|
2292
|
+
if (open && primary !== `element:${open.pipelineId}` && rest.startsWith("{")) {
|
|
2293
|
+
prefix = braceRemoved(open.pipelineId) ? "" : "{";
|
|
2294
|
+
rest = rest.slice(1).trimStart();
|
|
2295
|
+
} else if (open && rest.endsWith("{")) {
|
|
2296
|
+
suffix = braceRemoved(open.pipelineId) ? "" : "{";
|
|
2297
|
+
rest = rest.slice(0, -1).trimEnd();
|
|
2298
|
+
} else if (open) return void 0;
|
|
2299
|
+
if (close && rest.endsWith("}") && !(open && suffix)) {
|
|
2300
|
+
suffix = braceRemoved(close.pipelineId) ? "" : "}";
|
|
2301
|
+
rest = rest.slice(0, -1).trimEnd();
|
|
2302
|
+
} else if (close) return void 0;
|
|
2303
|
+
const assembled = [prefix, statement ?? rest, suffix].filter(Boolean).join(" ");
|
|
2304
|
+
if (assembled) return shape.commentHead + shape.indent + assembled + shape.commentTail;
|
|
2305
|
+
if (shape.commentHead) return shape.commentHead + shape.commentTail;
|
|
2306
|
+
return shape.commentTail ? shape.indent + shape.commentTail.trimStart() : null;
|
|
2307
|
+
}
|
|
2308
|
+
const insertions = planInsertions(source, edited, after, textBefore, textAfter, reparented);
|
|
2309
|
+
if (!insertions) return null;
|
|
2310
|
+
passthrough.added.forEach((line, index) => {
|
|
2311
|
+
addInsertion(insertions, lines.length - 1, line.text, after.length + index);
|
|
2312
|
+
});
|
|
2313
|
+
return assemble(source, result, insertions);
|
|
2314
|
+
}
|
|
2315
|
+
function addInsertion(insertions, afterLine, text, order) {
|
|
2316
|
+
const list = insertions.get(afterLine) ?? [];
|
|
2317
|
+
list.push({ text, order });
|
|
2318
|
+
insertions.set(afterLine, list);
|
|
2319
|
+
}
|
|
2320
|
+
function planInsertions(source, edited, after, textBefore, textAfter, reparented) {
|
|
2321
|
+
const { lines, shapes, sourceMap } = source;
|
|
2322
|
+
const lastLine = lines.length - 1;
|
|
2323
|
+
const lineOf = (key) => {
|
|
2324
|
+
const line = sourceMap.primaryLineOf.get(key);
|
|
2325
|
+
return line === void 0 ? void 0 : line - 1;
|
|
2326
|
+
};
|
|
2327
|
+
const pipelineLineOf = (pipelineId) => lineOf(`element:${pipelineId}`);
|
|
2328
|
+
const childLinesOf = (pipelineId) => source.map.elements.flatMap((el) => {
|
|
2329
|
+
if (el.elementType !== "component" || el.pipelineId !== pipelineId) return [];
|
|
2330
|
+
const line = lineOf(`child:${el.id}`);
|
|
2331
|
+
return line === void 0 ? [] : [line];
|
|
2332
|
+
});
|
|
2333
|
+
const blockEndOf = (pipelineId) => {
|
|
2334
|
+
const block = sourceMap.blocks.get(pipelineId);
|
|
2335
|
+
if (block?.open === void 0 || pipelineLineOf(pipelineId) === void 0) return void 0;
|
|
2336
|
+
if (block.close !== void 0) return block.close - 1;
|
|
2337
|
+
return Math.max(block.open - 1, ...childLinesOf(pipelineId));
|
|
2338
|
+
};
|
|
2339
|
+
const anchorInBlock = (pipelineId) => {
|
|
2340
|
+
const block = sourceMap.blocks.get(pipelineId);
|
|
2341
|
+
let anchor;
|
|
2342
|
+
if (block.close === void 0) anchor = blockEndOf(pipelineId);
|
|
2343
|
+
else if (block.close !== block.open && shapes[block.close - 1].code === "}") {
|
|
2344
|
+
anchor = block.close - 2;
|
|
2345
|
+
} else return null;
|
|
2346
|
+
return shapes[anchor]?.endsInBlockComment ? null : anchor;
|
|
2347
|
+
};
|
|
2348
|
+
const hasSourceBlock = (pipelineId) => blockEndOf(pipelineId) !== void 0;
|
|
2349
|
+
const endOf = (key) => {
|
|
2350
|
+
const line = lineOf(key);
|
|
2351
|
+
if (line === void 0 || !key.startsWith("element:")) return line;
|
|
2352
|
+
return Math.max(line, blockEndOf(key.slice("element:".length)) ?? line);
|
|
2353
|
+
};
|
|
2354
|
+
const sourceElementTypes = new Map(source.map.elements.map((el) => [el.id, el.elementType]));
|
|
2355
|
+
const editedElementById = new Map(edited.elements.map((el) => [el.id, el]));
|
|
2356
|
+
const lastLineOf = (accepts) => {
|
|
2357
|
+
let last;
|
|
2358
|
+
for (const key of sourceMap.primaryLineOf.keys()) {
|
|
2359
|
+
if (!accepts(key) || !textBefore.has(key)) continue;
|
|
2360
|
+
const end = endOf(key);
|
|
2361
|
+
if (end !== void 0 && (last === void 0 || end > last)) last = end;
|
|
2362
|
+
}
|
|
2363
|
+
return last;
|
|
2364
|
+
};
|
|
2365
|
+
const isElementOfType = (type) => (key) => key.startsWith("element:") && sourceElementTypes.get(key.slice("element:".length)) === type;
|
|
2366
|
+
const startsWith = (prefix) => (key) => key.startsWith(prefix);
|
|
2367
|
+
const settle = (line) => {
|
|
2368
|
+
if (line < 0) return -1;
|
|
2369
|
+
let settled = line;
|
|
2370
|
+
for (const pipelineId of sourceMap.blocks.keys()) {
|
|
2371
|
+
const start = pipelineLineOf(pipelineId);
|
|
2372
|
+
const end = blockEndOf(pipelineId);
|
|
2373
|
+
if (start === void 0 || end === void 0) continue;
|
|
2374
|
+
if (settled >= start && settled < end) settled = end;
|
|
2375
|
+
}
|
|
2376
|
+
while (settled < lastLine && shapes[settled].endsInBlockComment) settled++;
|
|
2377
|
+
return settled;
|
|
2378
|
+
};
|
|
2379
|
+
const firstOf = (...candidates) => {
|
|
2380
|
+
for (const candidate of candidates) if (candidate !== void 0) return candidate;
|
|
2381
|
+
return lastLine;
|
|
2382
|
+
};
|
|
2383
|
+
const needsInsertion = (key) => reparented.has(key) || !sourceMap.primaryLineOf.has(key) && textBefore.get(key) !== textAfter.get(key);
|
|
2384
|
+
const anchorOfElement = (id) => {
|
|
2385
|
+
const type = editedElementById.get(id)?.elementType ?? "";
|
|
2386
|
+
return settle(
|
|
2387
|
+
firstOf(
|
|
2388
|
+
lastLineOf(isElementOfType(type)),
|
|
2389
|
+
lastLineOf(startsWith("element:")),
|
|
2390
|
+
lastLineOf(startsWith("url:")),
|
|
2391
|
+
lastLineOf(startsWith("config:"))
|
|
2392
|
+
)
|
|
2393
|
+
);
|
|
2394
|
+
};
|
|
2395
|
+
const anchorOfNewBlock = (pipelineId) => pipelineLineOf(pipelineId) ?? anchorOfElement(pipelineId);
|
|
2396
|
+
const insertions = /* @__PURE__ */ new Map();
|
|
2397
|
+
for (let order = 0; order < after.length; order++) {
|
|
2398
|
+
const { key, text } = after[order];
|
|
2399
|
+
if (isRawKey(key)) continue;
|
|
2400
|
+
const separator = key.indexOf(":");
|
|
2401
|
+
const kind = key.slice(0, separator);
|
|
2402
|
+
const id = key.slice(separator + 1);
|
|
2403
|
+
let anchor;
|
|
2404
|
+
if (kind === "block-open" || kind === "block-close") {
|
|
2405
|
+
if (textBefore.has(`block-open:${id}`)) continue;
|
|
2406
|
+
if (hasSourceBlock(id)) {
|
|
2407
|
+
if (anchorInBlock(id) === null) return null;
|
|
2408
|
+
continue;
|
|
2409
|
+
}
|
|
2410
|
+
anchor = anchorOfNewBlock(id);
|
|
2411
|
+
} else if (!needsInsertion(key)) {
|
|
2412
|
+
continue;
|
|
2413
|
+
} else if (kind === "config") {
|
|
2414
|
+
anchor = settle(firstOf(lastLineOf(startsWith("config:")), -1));
|
|
2415
|
+
} else if (kind === "url") {
|
|
2416
|
+
const lastUrl = lastLineOf(startsWith("url:"));
|
|
2417
|
+
anchor = settle(firstOf(lastUrl, lastLineOf(startsWith("config:")), -1));
|
|
2418
|
+
} else if (kind === "element") {
|
|
2419
|
+
anchor = anchorOfElement(id);
|
|
2420
|
+
} else if (kind === "child") {
|
|
2421
|
+
const child = editedElementById.get(id);
|
|
2422
|
+
const pipelineId = child?.elementType === "component" ? child.pipelineId : void 0;
|
|
2423
|
+
if (pipelineId === void 0) return null;
|
|
2424
|
+
anchor = hasSourceBlock(pipelineId) ? anchorInBlock(pipelineId) : anchorOfNewBlock(pipelineId);
|
|
2425
|
+
} else if (kind === "evolve") {
|
|
2426
|
+
anchor = settle(firstOf(lastLineOf(startsWith("evolve:")), endOf(`element:${id}`)));
|
|
2427
|
+
} else if (kind === "edge") {
|
|
2428
|
+
anchor = settle(firstOf(lastLineOf(startsWith("edge:"))));
|
|
2429
|
+
} else return null;
|
|
2430
|
+
if (anchor === null) return null;
|
|
2431
|
+
addInsertion(insertions, anchor, text, order);
|
|
2432
|
+
}
|
|
2433
|
+
return insertions;
|
|
2434
|
+
}
|
|
2435
|
+
function assemble(source, result, insertions) {
|
|
2436
|
+
const output = [];
|
|
2437
|
+
const insertAfter = (line) => {
|
|
2438
|
+
const list = [...insertions.get(line) ?? []].sort((a, b) => a.order - b.order);
|
|
2439
|
+
for (const { text } of list) output.push({ text, lineBreak: "" });
|
|
2440
|
+
};
|
|
2441
|
+
insertAfter(-1);
|
|
2442
|
+
result.forEach((text, index) => {
|
|
2443
|
+
if (text !== null) output.push({ text, lineBreak: source.lines[index].lineBreak });
|
|
2444
|
+
insertAfter(index);
|
|
2445
|
+
});
|
|
2446
|
+
return output.map(({ text, lineBreak }, index) => {
|
|
2447
|
+
const isLast = index === output.length - 1;
|
|
2448
|
+
if (isLast && !source.endsWithLineBreak) return text;
|
|
2449
|
+
return text + (lineBreak || source.lineBreak);
|
|
2450
|
+
}).join("");
|
|
2451
|
+
}
|
|
2452
|
+
function normalized(value) {
|
|
2453
|
+
if (typeof value === "number") return roundAsWritten(value);
|
|
2454
|
+
if (Array.isArray(value)) return value.map(normalized);
|
|
2455
|
+
if (value !== null && typeof value === "object") {
|
|
2456
|
+
return Object.fromEntries(
|
|
2457
|
+
Object.entries(value).filter(([, entry]) => entry !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entry]) => [key, normalized(entry)])
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
return value;
|
|
2461
|
+
}
|
|
2462
|
+
function fingerprint(map) {
|
|
2463
|
+
const elementById = new Map(map.elements.map((el) => [el.id, el]));
|
|
2464
|
+
const reference = (id) => {
|
|
2465
|
+
const el = elementById.get(id);
|
|
2466
|
+
return el ? `${el.elementType}:${el.label}` : `?${id}`;
|
|
2467
|
+
};
|
|
2468
|
+
const entries = [`config ${JSON.stringify(normalized(map.config))}`];
|
|
2469
|
+
for (const el of map.elements) {
|
|
2470
|
+
const { id: _id, ...rest } = el;
|
|
2471
|
+
const described = { ...rest };
|
|
2472
|
+
if (el.elementType === "component" && el.pipelineId !== void 0) {
|
|
2473
|
+
described["pipelineId"] = reference(el.pipelineId);
|
|
2474
|
+
}
|
|
2475
|
+
if (el.elementType === "pipeline") described["childIds"] = el.childIds.map(reference).sort();
|
|
2476
|
+
entries.push(`element ${JSON.stringify(normalized(described))}`);
|
|
2477
|
+
}
|
|
2478
|
+
for (const edge of map.edges) {
|
|
2479
|
+
const { id: _id, ...rest } = edge;
|
|
2480
|
+
const described = { ...rest, from: reference(edge.from), to: reference(edge.to) };
|
|
2481
|
+
entries.push(`edge ${JSON.stringify(normalized(described))}`);
|
|
2482
|
+
}
|
|
2483
|
+
for (const raw of map.rawPassthrough ?? []) entries.push(`raw ${raw}`);
|
|
2484
|
+
return entries.sort().join("\n");
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
// src/read-back-ids.ts
|
|
2488
|
+
var ELEMENT_KEY = /^(?:element|child):(.*)$/s;
|
|
2489
|
+
function elementIdOf(key) {
|
|
2490
|
+
return ELEMENT_KEY.exec(key)?.[1];
|
|
2491
|
+
}
|
|
2492
|
+
function readBackIds(map) {
|
|
2493
|
+
const lines = serializeKeyedLines(map);
|
|
2494
|
+
const { sourceMap } = parseDSLWithSourceMap([...lines.map((line) => line.text), ""].join("\n"));
|
|
2495
|
+
const readIdByLine = /* @__PURE__ */ new Map();
|
|
2496
|
+
for (const [key, line] of sourceMap.primaryLineOf) {
|
|
2497
|
+
const id = elementIdOf(key);
|
|
2498
|
+
if (id !== void 0) readIdByLine.set(line, id);
|
|
2499
|
+
}
|
|
2500
|
+
const ids = /* @__PURE__ */ new Map();
|
|
2501
|
+
lines.forEach((line, index) => {
|
|
2502
|
+
const id = elementIdOf(line.key);
|
|
2503
|
+
const readId = readIdByLine.get(index + 1);
|
|
2504
|
+
if (id !== void 0 && readId !== void 0) ids.set(id, readId);
|
|
2505
|
+
});
|
|
2506
|
+
return ids;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// src/raw-passthrough.ts
|
|
2510
|
+
function withoutRawStatements(rawPassthrough, keyword) {
|
|
2511
|
+
if (!rawPassthrough) return rawPassthrough;
|
|
2512
|
+
const wanted = keyword.toLowerCase();
|
|
2513
|
+
const comments = new CommentStripper();
|
|
2514
|
+
let changed = false;
|
|
2515
|
+
const kept = [];
|
|
2516
|
+
for (const raw of rawPassthrough) {
|
|
2517
|
+
const stripped = comments.strip(raw);
|
|
2518
|
+
if (keywordOf(stripped.code) !== wanted) {
|
|
2519
|
+
kept.push(raw);
|
|
2520
|
+
continue;
|
|
2521
|
+
}
|
|
2522
|
+
changed = true;
|
|
2523
|
+
if (stripped.comments.length) kept.push(stripped.comments.join(" "));
|
|
2524
|
+
}
|
|
2525
|
+
if (!changed) return rawPassthrough;
|
|
2526
|
+
return kept.length ? kept : void 0;
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
export { keywordOf, parseCoords, parseDSL, parseDSLWithDiagnostics, parseDecorators, parseLabelOffset, patchDSL, readBackIds, serializeDSL, slug, withoutRawStatements };
|
|
1777
2530
|
//# sourceMappingURL=index.js.map
|
|
1778
2531
|
//# sourceMappingURL=index.js.map
|