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