@itsthecsguy/linework 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/NOTICE +11 -0
- package/README.md +206 -2
- package/assets/drawio/LICENSE +201 -0
- package/assets/drawio/VERSION +1 -0
- package/assets/drawio/favicon.ico +0 -0
- package/assets/drawio/images/1x1.png +0 -0
- package/assets/drawio/images/2x2.png +0 -0
- package/assets/drawio/images/3x3.png +0 -0
- package/assets/drawio/images/ajax-loader.gif +0 -0
- package/assets/drawio/images/android-chrome-512x512.png +0 -0
- package/assets/drawio/images/apple-touch-icon.png +0 -0
- package/assets/drawio/images/aui-wait.gif +0 -0
- package/assets/drawio/images/broken.png +0 -0
- package/assets/drawio/images/browserconfig.xml +9 -0
- package/assets/drawio/images/close.png +0 -0
- package/assets/drawio/images/cloud-connector-atlas.png +0 -0
- package/assets/drawio/images/collapsed.gif +0 -0
- package/assets/drawio/images/delete.png +0 -0
- package/assets/drawio/images/document-google-drive-icon-80.png +0 -0
- package/assets/drawio/images/draw-search.png +0 -0
- package/assets/drawio/images/drawio.svg +19 -0
- package/assets/drawio/images/drawlogo-color.svg +50 -0
- package/assets/drawio/images/drawlogo-gray.svg +18 -0
- package/assets/drawio/images/drawlogo-text-bottom.svg +75 -0
- package/assets/drawio/images/drawlogo.svg +18 -0
- package/assets/drawio/images/drawlogo128.png +0 -0
- package/assets/drawio/images/drawlogo144.png +0 -0
- package/assets/drawio/images/drawlogo256.png +0 -0
- package/assets/drawio/images/drawlogo48-gray.png +0 -0
- package/assets/drawio/images/drawlogo48.png +0 -0
- package/assets/drawio/images/drawlogo80.png +0 -0
- package/assets/drawio/images/droptarget.png +0 -0
- package/assets/drawio/images/edit.gif +0 -0
- package/assets/drawio/images/edit16.png +0 -0
- package/assets/drawio/images/edit32.png +0 -0
- package/assets/drawio/images/edit80.png +0 -0
- package/assets/drawio/images/embed-icon.png +0 -0
- package/assets/drawio/images/expanded.gif +0 -0
- package/assets/drawio/images/facebook.png +0 -0
- package/assets/drawio/images/favicon-16x16.png +0 -0
- package/assets/drawio/images/favicon-32x32.png +0 -0
- package/assets/drawio/images/file.png +0 -0
- package/assets/drawio/images/folder.png +0 -0
- package/assets/drawio/images/gadget-screenshot.png +0 -0
- package/assets/drawio/images/gadget-thumb.png +0 -0
- package/assets/drawio/images/github-logo-white.svg +6 -0
- package/assets/drawio/images/github-logo.svg +17 -0
- package/assets/drawio/images/gitlab-logo-white.svg +1 -0
- package/assets/drawio/images/gitlab-logo.svg +1 -0
- package/assets/drawio/images/glyphicons_blogger.png +0 -0
- package/assets/drawio/images/glyphicons_facebook.png +0 -0
- package/assets/drawio/images/glyphicons_github.png +0 -0
- package/assets/drawio/images/glyphicons_google.png +0 -0
- package/assets/drawio/images/glyphicons_star.png +0 -0
- package/assets/drawio/images/glyphicons_twitter.png +0 -0
- package/assets/drawio/images/glyphicons_youtube.png +0 -0
- package/assets/drawio/images/google-drive-connector-atlas.png +0 -0
- package/assets/drawio/images/google-drive-connector-atlas.svg +121 -0
- package/assets/drawio/images/google-drive-logo-white.svg +9 -0
- package/assets/drawio/images/google-drive-logo.svg +17 -0
- package/assets/drawio/images/google-share.png +0 -0
- package/assets/drawio/images/icon-192-maskable.png +0 -0
- package/assets/drawio/images/icon-192.png +0 -0
- package/assets/drawio/images/icon-512-maskable.png +0 -0
- package/assets/drawio/images/icon-512.png +0 -0
- package/assets/drawio/images/icon-search.svg +21 -0
- package/assets/drawio/images/img-hi-res.png +0 -0
- package/assets/drawio/images/img-lo-res.png +0 -0
- package/assets/drawio/images/list.svg +16 -0
- package/assets/drawio/images/locked.png +0 -0
- package/assets/drawio/images/log.png +0 -0
- package/assets/drawio/images/logo-confluence.png +0 -0
- package/assets/drawio/images/logo-flat-small.png +0 -0
- package/assets/drawio/images/logo-flat.png +0 -0
- package/assets/drawio/images/logo-jira.png +0 -0
- package/assets/drawio/images/logo-small.gif +0 -0
- package/assets/drawio/images/logo-white.png +0 -0
- package/assets/drawio/images/logo.png +0 -0
- package/assets/drawio/images/manifest.json +25 -0
- package/assets/drawio/images/move.png +0 -0
- package/assets/drawio/images/notion-logo-white.svg +1 -0
- package/assets/drawio/images/notion-logo.svg +1 -0
- package/assets/drawio/images/onedrive-connector-atlas.png +0 -0
- package/assets/drawio/images/onedrive-connector-atlas.svg +248 -0
- package/assets/drawio/images/onedrive-logo-white.svg +29 -0
- package/assets/drawio/images/onedrive-logo.svg +29 -0
- package/assets/drawio/images/osa_database.png +0 -0
- package/assets/drawio/images/osa_drive-harddisk.png +0 -0
- package/assets/drawio/images/plant-uml-atlas.png +0 -0
- package/assets/drawio/images/samepage-icon-color.svg +37 -0
- package/assets/drawio/images/save.png +0 -0
- package/assets/drawio/images/sidebar-android.png +0 -0
- package/assets/drawio/images/sidebar-archimate.png +0 -0
- package/assets/drawio/images/sidebar-arrows.png +0 -0
- package/assets/drawio/images/sidebar-arrows2.png +0 -0
- package/assets/drawio/images/sidebar-basic.png +0 -0
- package/assets/drawio/images/sidebar-c4.png +0 -0
- package/assets/drawio/images/sidebar-cabinets.png +0 -0
- package/assets/drawio/images/sidebar-cumulus.png +0 -0
- package/assets/drawio/images/sidebar-dfd.png +0 -0
- package/assets/drawio/images/sidebar-dynamics365.png +0 -0
- package/assets/drawio/images/sidebar-eip.png +0 -0
- package/assets/drawio/images/sidebar-er.png +0 -0
- package/assets/drawio/images/sidebar-floorplans.png +0 -0
- package/assets/drawio/images/sidebar-flowchart.png +0 -0
- package/assets/drawio/images/sidebar-gcp3.png +0 -0
- package/assets/drawio/images/sidebar-general.png +0 -0
- package/assets/drawio/images/sidebar-kubernetes.png +0 -0
- package/assets/drawio/images/sidebar-leanmapping.png +0 -0
- package/assets/drawio/images/sidebar-mockups.png +0 -0
- package/assets/drawio/images/sidebar-network.png +0 -0
- package/assets/drawio/images/sidebar-network2.png +0 -0
- package/assets/drawio/images/sidebar-openstack.png +0 -0
- package/assets/drawio/images/sidebar-sitemap.png +0 -0
- package/assets/drawio/images/sidebar-threatmodeling.png +0 -0
- package/assets/drawio/images/sidebar-uml.png +0 -0
- package/assets/drawio/images/sketch-logo128.png +0 -0
- package/assets/drawio/images/spin.gif +0 -0
- package/assets/drawio/images/sprites.png +0 -0
- package/assets/drawio/images/stop-flat-icon-80.png +0 -0
- package/assets/drawio/images/submenu.gif +0 -0
- package/assets/drawio/images/touch-submenu.png +0 -0
- package/assets/drawio/images/transparent.gif +0 -0
- package/assets/drawio/images/trello-logo-white-orange.svg +6 -0
- package/assets/drawio/images/trello-logo-white.svg +6 -0
- package/assets/drawio/images/trello-logo.svg +1 -0
- package/assets/drawio/images/tweet.png +0 -0
- package/assets/drawio/images/update16.png +0 -0
- package/assets/drawio/images/update32.png +0 -0
- package/assets/drawio/images/update80.png +0 -0
- package/assets/drawio/images/youtube.png +0 -0
- package/assets/drawio/img/clipart/Battery_0_128x128.png +0 -0
- package/assets/drawio/img/clipart/Battery_100_128x128.png +0 -0
- package/assets/drawio/img/clipart/Battery_50_128x128.png +0 -0
- package/assets/drawio/img/clipart/Battery_75_128x128.png +0 -0
- package/assets/drawio/img/clipart/Battery_allstates_128x128.png +0 -0
- package/assets/drawio/img/clipart/Bluetooth_128x128.png +0 -0
- package/assets/drawio/img/clipart/Earth_globe_128x128.png +0 -0
- package/assets/drawio/img/clipart/Empty_Folder_128x128.png +0 -0
- package/assets/drawio/img/clipart/Full_Folder_128x128.png +0 -0
- package/assets/drawio/img/clipart/Gear_128x128.png +0 -0
- package/assets/drawio/img/clipart/Keys_128x128.png +0 -0
- package/assets/drawio/img/clipart/Lock_128x128.png +0 -0
- package/assets/drawio/img/clipart/Mouse_Pointer_128x128.png +0 -0
- package/assets/drawio/img/clipart/Plug_128x128.png +0 -0
- package/assets/drawio/img/clipart/Ships_Wheel_128x128.png +0 -0
- package/assets/drawio/img/clipart/Star_128x128.png +0 -0
- package/assets/drawio/img/clipart/Tire_128x128.png +0 -0
- package/assets/drawio/index.html +92 -0
- package/assets/drawio/js/PostConfig.js +7 -0
- package/assets/drawio/js/PreConfig.js +13 -0
- package/assets/drawio/js/app.min.js +18466 -0
- package/assets/drawio/js/bootstrap.js +428 -0
- package/assets/drawio/js/diagramly/LibavoidRouting.js +4459 -0
- package/assets/drawio/js/export-init.js +21 -0
- package/assets/drawio/js/extensions.min.js +10478 -0
- package/assets/drawio/js/libavoid-js/LICENSE +505 -0
- package/assets/drawio/js/libavoid-js/libavoid-routing.js +1633 -0
- package/assets/drawio/js/libavoid-js/libavoid.min.js +59 -0
- package/assets/drawio/js/main.js +22 -0
- package/assets/drawio/js/rough/rough.min.js +1 -0
- package/assets/drawio/js/shapes-14-6-5.min.js +3457 -0
- package/assets/drawio/js/stencils.min.js +447 -0
- package/assets/drawio/mxgraph/css/common.css +147 -0
- package/assets/drawio/resources/dia.txt +2901 -0
- package/assets/drawio/styles/dark-default.xml +109 -0
- package/assets/drawio/styles/default-old.xml +109 -0
- package/assets/drawio/styles/default.xml +203 -0
- package/assets/drawio/styles/fonts/ArchitectsDaughter-Regular.ttf +0 -0
- package/assets/drawio/styles/grapheditor.css +4501 -0
- package/assets/drawio/styles/high-contrast.css +128 -0
- package/assets/drawio/styles/thumb_horz.png +0 -0
- package/assets/drawio/styles/thumb_vertical.png +0 -0
- package/assets/fonts/ibm-plex-mono-LICENSE.txt +93 -0
- package/assets/fonts/ibm-plex-sans-LICENSE.txt +93 -0
- package/assets/fonts/kalam-400.woff2 +0 -0
- package/assets/fonts/kalam-700.woff2 +0 -0
- package/assets/fonts/kalam-LICENSE.txt +93 -0
- package/assets/fonts/plex-mono-400.woff2 +0 -0
- package/assets/fonts/plex-mono-600.woff2 +0 -0
- package/assets/fonts/plex-sans-400.woff2 +0 -0
- package/assets/fonts/plex-sans-600.woff2 +0 -0
- package/dist/api.js +218 -0
- package/dist/cli.js +275 -0
- package/dist/drawio/icons.js +121 -0
- package/dist/drawio/merge.js +309 -0
- package/dist/drawio/read.js +180 -0
- package/dist/drawio/show.js +501 -0
- package/dist/drawio/theme.js +425 -0
- package/dist/drawio/write.js +300 -0
- package/dist/edit/server.js +212 -0
- package/dist/git.js +46 -0
- package/dist/import/mermaid.js +569 -0
- package/dist/layout/layout.js +212 -0
- package/dist/layout/sequence.js +333 -0
- package/dist/layout/text.js +73 -0
- package/dist/lint/check.js +188 -0
- package/dist/lint/fixlabels.js +128 -0
- package/dist/mcp/server.js +239 -0
- package/dist/publish.js +121 -0
- package/dist/render/browser.js +236 -0
- package/dist/render/fonts.js +66 -0
- package/dist/report.js +36 -0
- package/dist/spec/model.js +53 -0
- package/dist/spec/parse.js +497 -0
- package/package.json +71 -4
- package/skills/linework/SKILL.md +233 -0
- package/skills/linework/examples/billing-er.yaml +31 -0
- package/skills/linework/examples/checkout.yaml +28 -0
- package/skills/linework/examples/pay-sequence.yaml +28 -0
- package/skills/linework/examples/payment-state.yaml +22 -0
- package/skills/linework/examples/refund-flow.yaml +29 -0
- package/skills/linework/examples/serverless-aws.yaml +23 -0
- package/skills/linework/references/google-docs.md +46 -0
- package/skills/linework/references/spec.md +312 -0
- package/skills/linework/references/style-guide.md +53 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
// Mermaid -> linework Diagram. Covers the subset people actually use in design docs:
|
|
2
|
+
// flowchart/graph, stateDiagram(-v2), sequenceDiagram and erDiagram. Styling
|
|
3
|
+
// directives (classDef, style, linkStyle, click) are dropped with a warning.
|
|
4
|
+
import { humanize } from '../spec/parse.js';
|
|
5
|
+
export function importMermaid(src) {
|
|
6
|
+
const lines = src
|
|
7
|
+
.replace(/^---[\s\S]*?\n---\s*\n/, '') // front matter
|
|
8
|
+
.split('\n')
|
|
9
|
+
.map((l) => l.replace(/%%.*$/, '').trimEnd())
|
|
10
|
+
.filter((l) => l.trim());
|
|
11
|
+
const head = (lines[0] ?? '').trim();
|
|
12
|
+
if (/^(flowchart|graph)\b/i.test(head))
|
|
13
|
+
return flowchart(lines);
|
|
14
|
+
if (/^stateDiagram(-v2)?\b/.test(head))
|
|
15
|
+
return stateDiagram(lines);
|
|
16
|
+
if (/^sequenceDiagram\b/.test(head))
|
|
17
|
+
return sequenceDiagram(lines);
|
|
18
|
+
if (/^erDiagram\b/.test(head))
|
|
19
|
+
return erDiagram(lines);
|
|
20
|
+
throw new Error(`unsupported Mermaid diagram "${head.split(/\s/)[0]}". linework imports flowchart/graph, stateDiagram, sequenceDiagram and erDiagram.`);
|
|
21
|
+
}
|
|
22
|
+
/** Pull ```mermaid blocks out of a Markdown document. */
|
|
23
|
+
export function mermaidBlocks(markdown) {
|
|
24
|
+
return [...markdown.matchAll(/^```mermaid[^\n]*\n([\s\S]*?)^```/gm)].map((m) => m[1]);
|
|
25
|
+
}
|
|
26
|
+
const clean = (s) => s
|
|
27
|
+
.trim()
|
|
28
|
+
.replace(/^"(.*)"$/s, '$1')
|
|
29
|
+
.replace(/^`(.*)`$/s, '$1')
|
|
30
|
+
.replace(/\*\*(.*?)\*\*/g, '$1')
|
|
31
|
+
.replace(/"/g, '"')
|
|
32
|
+
.replace(/#quot;/g, '"')
|
|
33
|
+
.trim();
|
|
34
|
+
const brSplit = (s) => clean(s).split(/<br\s*\/?>|\\n/i).map((x) => x.replace(/<[^>]+>/g, '').trim()).filter(Boolean);
|
|
35
|
+
function safeId(raw, used, prefer) {
|
|
36
|
+
let id = (prefer ?? raw).replace(/[^\w.-]+/g, '_').replace(/^[^A-Za-z_]+/, '');
|
|
37
|
+
if (!id)
|
|
38
|
+
id = 'n';
|
|
39
|
+
const base = id;
|
|
40
|
+
let k = 2;
|
|
41
|
+
while (used.has(id))
|
|
42
|
+
id = `${base}_${k++}`;
|
|
43
|
+
used.add(id);
|
|
44
|
+
return id;
|
|
45
|
+
}
|
|
46
|
+
// ---------- flowchart ----------
|
|
47
|
+
const SHAPES = [
|
|
48
|
+
['(((', ')))', 'circle'],
|
|
49
|
+
['([', '])', 'stadium'],
|
|
50
|
+
['[(', ')]', 'cylinder'],
|
|
51
|
+
['[[', ']]', 'subroutine'],
|
|
52
|
+
['((', '))', 'circle'],
|
|
53
|
+
['{{', '}}', 'hexagon'],
|
|
54
|
+
['[/', '/]', 'para'],
|
|
55
|
+
['[\\', '\\]', 'para'],
|
|
56
|
+
['[/', '\\]', 'trap'],
|
|
57
|
+
['[\\', '/]', 'trap'],
|
|
58
|
+
['[', ']', 'rect'],
|
|
59
|
+
['(', ')', 'round'],
|
|
60
|
+
['{', '}', 'diamond'],
|
|
61
|
+
['>', ']', 'flag'],
|
|
62
|
+
];
|
|
63
|
+
function flowchart(lines) {
|
|
64
|
+
const warnings = new Set();
|
|
65
|
+
const dirTok = /^(?:flowchart|graph)\s+(TD|TB|BT|RL|LR)/i.exec(lines[0].trim())?.[1]?.toUpperCase();
|
|
66
|
+
const direction = dirTok === 'TD' ? 'TB' : (dirTok ?? 'auto');
|
|
67
|
+
const nodes = new Map();
|
|
68
|
+
const order = [];
|
|
69
|
+
const edges = [];
|
|
70
|
+
const groups = [];
|
|
71
|
+
const stack = [];
|
|
72
|
+
const touch = (n) => {
|
|
73
|
+
const cur = nodes.get(n.id);
|
|
74
|
+
if (!cur) {
|
|
75
|
+
nodes.set(n.id, { ...n, group: stack[stack.length - 1] });
|
|
76
|
+
order.push(n.id);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
if (n.text != null)
|
|
80
|
+
cur.text = n.text;
|
|
81
|
+
if (n.shape)
|
|
82
|
+
cur.shape = n.shape;
|
|
83
|
+
// Mermaid puts a node in the subgraph that mentions it, even if it was used earlier.
|
|
84
|
+
if (!cur.group && stack.length)
|
|
85
|
+
cur.group = stack[stack.length - 1];
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
for (const raw of lines.slice(1)) {
|
|
89
|
+
for (const stmt of splitStatements(raw.trim())) {
|
|
90
|
+
if (!stmt)
|
|
91
|
+
continue;
|
|
92
|
+
let m;
|
|
93
|
+
if ((m = /^subgraph\s+(.+)$/.exec(stmt))) {
|
|
94
|
+
const body = m[1].trim();
|
|
95
|
+
const g = /^([\w.-]+)\s*\[(.*)\]$/.exec(body) ?? /^([\w.-]+)\s+\[(.*)\]$/.exec(body);
|
|
96
|
+
const rawId = g ? g[1] : body.replace(/^"|"$/g, '');
|
|
97
|
+
const label = g ? clean(g[2]) : clean(body);
|
|
98
|
+
groups.push({ raw: rawId, label, parent: stack[stack.length - 1] });
|
|
99
|
+
stack.push(rawId);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (stmt === 'end') {
|
|
103
|
+
stack.pop();
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (/^(classDef|class|style|linkStyle|click|accTitle|accDescr)\b/.test(stmt)) {
|
|
107
|
+
warnings.add('styling directives (classDef, style, click, ...) are dropped; linework applies its own theme');
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (/^direction\s+/.test(stmt))
|
|
111
|
+
continue;
|
|
112
|
+
parseChain(stmt, touch, edges, warnings);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Pick a type: diamonds mean a process; cylinders or subgraphs mean architecture.
|
|
116
|
+
const shapes = [...nodes.values()].map((n) => n.shape);
|
|
117
|
+
const type = shapes.includes('diamond') ? 'flow' : shapes.includes('cylinder') || groups.length ? 'architecture' : 'flow';
|
|
118
|
+
const used = new Set();
|
|
119
|
+
const gid = new Map();
|
|
120
|
+
for (const g of groups)
|
|
121
|
+
gid.set(g.raw, safeId(g.raw, used));
|
|
122
|
+
const nid = new Map();
|
|
123
|
+
for (const id of order)
|
|
124
|
+
nid.set(id, safeId(id, used));
|
|
125
|
+
const outGroups = groups.map((g) => ({ id: gid.get(g.raw), label: g.label, parent: g.parent ? gid.get(g.parent) : undefined }));
|
|
126
|
+
const outNodes = order.map((raw) => {
|
|
127
|
+
const n = nodes.get(raw);
|
|
128
|
+
const parts = brSplit(n.text ?? raw);
|
|
129
|
+
const label = parts[0] ?? raw;
|
|
130
|
+
const node = { id: nid.get(raw), label, kind: kindFor(type, n.shape, label), parent: n.group ? gid.get(n.group) : undefined };
|
|
131
|
+
if (type === 'architecture' && parts.length > 1)
|
|
132
|
+
node.tech = parts.slice(1).join(' ');
|
|
133
|
+
else if (parts.length > 1)
|
|
134
|
+
node.label = parts.join(' ');
|
|
135
|
+
return node;
|
|
136
|
+
});
|
|
137
|
+
const counts = new Map();
|
|
138
|
+
const outEdges = edges.map((e) => {
|
|
139
|
+
const from = nid.get(e.from), to = nid.get(e.to);
|
|
140
|
+
const base = `${from}->${to}`;
|
|
141
|
+
const k = (counts.get(base) ?? 0) + 1;
|
|
142
|
+
counts.set(base, k);
|
|
143
|
+
return { id: k === 1 ? base : `${base}#${k}`, from, to, label: e.label, style: e.style, ...(e.highlight ? { highlight: true } : {}) };
|
|
144
|
+
});
|
|
145
|
+
return { diagram: { type, direction, theme: 'clean', groups: outGroups, nodes: outNodes, edges: outEdges }, warnings: [...warnings] };
|
|
146
|
+
}
|
|
147
|
+
function splitStatements(line) {
|
|
148
|
+
// Split on ';' outside brackets and quotes.
|
|
149
|
+
const out = [];
|
|
150
|
+
let depth = 0, q = false, cur = '';
|
|
151
|
+
for (const ch of line) {
|
|
152
|
+
if (ch === '"')
|
|
153
|
+
q = !q;
|
|
154
|
+
if (!q && '[({'.includes(ch))
|
|
155
|
+
depth++;
|
|
156
|
+
if (!q && '])}'.includes(ch))
|
|
157
|
+
depth = Math.max(0, depth - 1);
|
|
158
|
+
if (ch === ';' && !q && depth === 0) {
|
|
159
|
+
out.push(cur.trim());
|
|
160
|
+
cur = '';
|
|
161
|
+
}
|
|
162
|
+
else
|
|
163
|
+
cur += ch;
|
|
164
|
+
}
|
|
165
|
+
out.push(cur.trim());
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
const ID_AT = /^[\w.À--]+?(?=\s|$|[\[({>&|]|-->|---|-\.|==|--|<--|<==)/u;
|
|
169
|
+
function parseNodeAt(s, pos) {
|
|
170
|
+
const rest = s.slice(pos);
|
|
171
|
+
const idm = ID_AT.exec(rest) ?? /^[\w.-]+/.exec(rest);
|
|
172
|
+
if (!idm)
|
|
173
|
+
return null;
|
|
174
|
+
let end = pos + idm[0].length;
|
|
175
|
+
const node = { id: idm[0] };
|
|
176
|
+
// new-style shape syntax: A@{ shape: cyl, label: "x" }
|
|
177
|
+
const at = /^@\{([^}]*)\}/.exec(s.slice(end));
|
|
178
|
+
if (at) {
|
|
179
|
+
const lab = /label:\s*"([^"]*)"/.exec(at[1]);
|
|
180
|
+
const shape = /shape:\s*([\w-]+)/.exec(at[1])?.[1];
|
|
181
|
+
node.text = lab?.[1];
|
|
182
|
+
node.shape = shape === 'cyl' || shape === 'database' ? 'cylinder' : shape === 'diam' || shape === 'decision' ? 'diamond' : shape === 'stadium' || shape === 'terminal' ? 'stadium' : 'rect';
|
|
183
|
+
return { node, end: end + at[0].length };
|
|
184
|
+
}
|
|
185
|
+
for (const [open, close, shape] of SHAPES) {
|
|
186
|
+
if (!s.startsWith(open, end))
|
|
187
|
+
continue;
|
|
188
|
+
const textStart = end + open.length;
|
|
189
|
+
let i = textStart;
|
|
190
|
+
if (s[i] === '"') {
|
|
191
|
+
const qEnd = s.indexOf('"', i + 1);
|
|
192
|
+
if (qEnd < 0)
|
|
193
|
+
continue;
|
|
194
|
+
i = qEnd + 1;
|
|
195
|
+
}
|
|
196
|
+
const closeAt = s.indexOf(close, i);
|
|
197
|
+
if (closeAt < 0)
|
|
198
|
+
continue;
|
|
199
|
+
node.text = s.slice(textStart, closeAt);
|
|
200
|
+
node.shape = shape;
|
|
201
|
+
end = closeAt + close.length;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
return { node, end };
|
|
205
|
+
}
|
|
206
|
+
function parseEdgeAt(s, pos) {
|
|
207
|
+
const rest = s.slice(pos);
|
|
208
|
+
// "-- text -->", "== text ==>", "-. text .->"
|
|
209
|
+
let m = /^\s*(<)?(--|==|-\.)\s+([^>|]+?)\s+(-{2,}|={2,}|\.+-)(>|o|x)?(?=\s|[\w"])/.exec(rest);
|
|
210
|
+
if (m)
|
|
211
|
+
return { style: styleOf(m[1], m[2] + m[4], m[5]), label: clean(m[3]), end: pos + m[0].length, thick: m[2] === '==' };
|
|
212
|
+
// "-->", "---", "-.->", "==>", "<-->", "--o", "--x", with optional "|text|"
|
|
213
|
+
m = /^\s*(<)?(-{2,}|={2,}|-\.+-?|\.-+)(>|o|x)?\s*(?:\|([^|]*)\|)?/.exec(rest);
|
|
214
|
+
if (m && /[-=.]/.test(m[2]) && m[0].trim())
|
|
215
|
+
return { style: styleOf(m[1], m[2], m[3]), label: m[4] != null ? clean(m[4]) : undefined, end: pos + m[0].length, thick: m[2].startsWith('=') };
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
function styleOf(back, line, head) {
|
|
219
|
+
const arrow = head === '>' || head === 'x' || head === 'o';
|
|
220
|
+
if (back && arrow)
|
|
221
|
+
return 'bidir';
|
|
222
|
+
if (!arrow && !back)
|
|
223
|
+
return 'line';
|
|
224
|
+
if (line.includes('.'))
|
|
225
|
+
return 'async';
|
|
226
|
+
return 'sync';
|
|
227
|
+
}
|
|
228
|
+
function parseChain(stmt, touch, edges, warnings) {
|
|
229
|
+
let pos = 0;
|
|
230
|
+
const readGroup = () => {
|
|
231
|
+
const ids = [];
|
|
232
|
+
for (;;) {
|
|
233
|
+
while (stmt[pos] === ' ')
|
|
234
|
+
pos++;
|
|
235
|
+
const r = parseNodeAt(stmt, pos);
|
|
236
|
+
if (!r)
|
|
237
|
+
return ids.length ? ids : null;
|
|
238
|
+
touch(r.node);
|
|
239
|
+
ids.push(r.node.id);
|
|
240
|
+
pos = r.end;
|
|
241
|
+
const amp = /^\s*&\s*/.exec(stmt.slice(pos));
|
|
242
|
+
if (!amp)
|
|
243
|
+
return ids;
|
|
244
|
+
pos += amp[0].length;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
let left = readGroup();
|
|
248
|
+
if (!left) {
|
|
249
|
+
warnings.add(`skipped a line linework couldn't read: "${stmt.slice(0, 60)}"`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
for (;;) {
|
|
253
|
+
const e = parseEdgeAt(stmt, pos);
|
|
254
|
+
if (!e)
|
|
255
|
+
break;
|
|
256
|
+
pos = e.end;
|
|
257
|
+
const right = readGroup();
|
|
258
|
+
if (!right)
|
|
259
|
+
break;
|
|
260
|
+
// Mermaid's thick links (==>) mark what matters: linework's highlight.
|
|
261
|
+
for (const a of left)
|
|
262
|
+
for (const b of right)
|
|
263
|
+
edges.push({ from: a, to: b, label: e.label || undefined, style: e.style, ...(e.thick ? { highlight: true } : {}) });
|
|
264
|
+
left = right;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function kindFor(type, shape, label) {
|
|
268
|
+
if (type === 'flow') {
|
|
269
|
+
if (shape === 'diamond')
|
|
270
|
+
return 'decision';
|
|
271
|
+
if (shape === 'stadium' || shape === 'circle')
|
|
272
|
+
return 'terminal';
|
|
273
|
+
if (shape === 'para' || shape === 'cylinder')
|
|
274
|
+
return 'data';
|
|
275
|
+
if (/\b(error|fail|reject|deny|denied)/i.test(label))
|
|
276
|
+
return 'error';
|
|
277
|
+
return 'step';
|
|
278
|
+
}
|
|
279
|
+
if (shape === 'cylinder')
|
|
280
|
+
return /bucket|s3|blob|storage|lake/i.test(label) ? 'storage' : 'database';
|
|
281
|
+
if (/^(users?|customers?|admins?|shoppers?|operators?|developers?|actors?)$/i.test(label))
|
|
282
|
+
return 'person';
|
|
283
|
+
if (/\b(browser|mobile|frontend|front-end|web ?app|spa|cli|ios|android)\b/i.test(label))
|
|
284
|
+
return 'client';
|
|
285
|
+
if (/\b(queue|kafka|sqs|pub\/?sub|topic|stream|rabbit|kinesis|nats)\b/i.test(label) || shape === 'stadium')
|
|
286
|
+
return 'queue';
|
|
287
|
+
if (/\b(redis|cache|memcache|cdn)\b/i.test(label))
|
|
288
|
+
return 'cache';
|
|
289
|
+
if (/\b(stripe|twilio|sendgrid|auth0|okta|github|slack|third.party|external)\b/i.test(label))
|
|
290
|
+
return 'external';
|
|
291
|
+
return 'service';
|
|
292
|
+
}
|
|
293
|
+
// ---------- state ----------
|
|
294
|
+
function stateDiagram(lines) {
|
|
295
|
+
const warnings = new Set();
|
|
296
|
+
let direction = 'auto';
|
|
297
|
+
const labels = new Map();
|
|
298
|
+
const order = [];
|
|
299
|
+
const parent = new Map();
|
|
300
|
+
const groups = [];
|
|
301
|
+
const stack = [];
|
|
302
|
+
const edges = [];
|
|
303
|
+
const touch = (id) => {
|
|
304
|
+
if (id === '[*]')
|
|
305
|
+
return;
|
|
306
|
+
if (!order.includes(id)) {
|
|
307
|
+
order.push(id);
|
|
308
|
+
parent.set(id, stack[stack.length - 1]);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
for (const raw of lines.slice(1)) {
|
|
312
|
+
const l = raw.trim();
|
|
313
|
+
let m;
|
|
314
|
+
if ((m = /^direction\s+(LR|TB|RL|BT)/.exec(l))) {
|
|
315
|
+
if (!stack.length)
|
|
316
|
+
direction = m[1];
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if ((m = /^state\s+"([^"]+)"\s+as\s+([\w.-]+)/.exec(l))) {
|
|
320
|
+
labels.set(m[2], m[1]);
|
|
321
|
+
touch(m[2]);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if ((m = /^state\s+([\w.-]+)\s*\{$/.exec(l))) {
|
|
325
|
+
groups.push({ raw: m[1], parent: stack[stack.length - 1] });
|
|
326
|
+
stack.push(m[1]);
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if ((m = /^state\s+([\w.-]+)\s*<<\w+>>/.exec(l))) {
|
|
330
|
+
touch(m[1]);
|
|
331
|
+
warnings.add('choice/fork/join states become plain states');
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (l === '}') {
|
|
335
|
+
stack.pop();
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (/^(note|classDef|class|style)\b/.test(l)) {
|
|
339
|
+
warnings.add('notes and styling in state diagrams are dropped');
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if ((m = /^(\[\*\]|[\w.-]+)\s*-->\s*(\[\*\]|[\w.-]+)\s*(?::\s*(.*))?$/.exec(l))) {
|
|
343
|
+
if (stack.length && (m[1] === '[*]' || m[2] === '[*]'))
|
|
344
|
+
warnings.add('start/end markers inside composite states are dropped');
|
|
345
|
+
if (stack.length && (m[1] === '[*]' || m[2] === '[*]')) {
|
|
346
|
+
touch(m[1]);
|
|
347
|
+
touch(m[2]);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
touch(m[1]);
|
|
351
|
+
touch(m[2]);
|
|
352
|
+
edges.push({ from: m[1], to: m[2], label: m[3] ? clean(m[3]) : undefined });
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if ((m = /^([\w.-]+)\s*:\s*(.+)$/.exec(l))) {
|
|
356
|
+
labels.set(m[1], clean(m[2]));
|
|
357
|
+
touch(m[1]);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if ((m = /^([\w.-]+)$/.exec(l))) {
|
|
361
|
+
touch(m[1]);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
warnings.add(`skipped a line linework couldn't read: "${l.slice(0, 60)}"`);
|
|
365
|
+
}
|
|
366
|
+
const groupIds = new Set(groups.map((g) => g.raw));
|
|
367
|
+
const used = new Set();
|
|
368
|
+
const ids = new Map();
|
|
369
|
+
for (const g of groups)
|
|
370
|
+
ids.set(g.raw, safeId(g.raw, used));
|
|
371
|
+
for (const s of order)
|
|
372
|
+
if (!groupIds.has(s))
|
|
373
|
+
ids.set(s, safeId(s, used));
|
|
374
|
+
const nodes = order
|
|
375
|
+
.filter((s) => !groupIds.has(s))
|
|
376
|
+
.map((s) => {
|
|
377
|
+
const label = labels.get(s) ?? (/[a-z][A-Z]/.test(s) ? s.replace(/([a-z])([A-Z])/g, '$1 $2') : humanize(s));
|
|
378
|
+
return { id: ids.get(s), label, kind: /fail|error|reject|declin|cancel|abort/i.test(label) ? 'error' : /complete|succe|paid|done|deliver|captur|settled/i.test(label) ? 'success' : 'state', parent: parent.get(s) ? ids.get(parent.get(s)) : undefined };
|
|
379
|
+
});
|
|
380
|
+
// Edges into a composite state go to its first child instead.
|
|
381
|
+
const firstChild = (g) => nodes.find((n) => n.parent === ids.get(g))?.id;
|
|
382
|
+
let startUsed = false;
|
|
383
|
+
const endFor = new Set();
|
|
384
|
+
const counts = new Map();
|
|
385
|
+
const outEdges = edges.flatMap((e) => {
|
|
386
|
+
let from = e.from === '[*]' ? '_start' : groupIds.has(e.from) ? firstChild(e.from) : ids.get(e.from);
|
|
387
|
+
let to = e.to === '[*]' ? undefined : groupIds.has(e.to) ? firstChild(e.to) : ids.get(e.to);
|
|
388
|
+
if (!from)
|
|
389
|
+
return [];
|
|
390
|
+
if (e.from === '[*]')
|
|
391
|
+
startUsed = true;
|
|
392
|
+
if (e.to === '[*]') {
|
|
393
|
+
endFor.add(from);
|
|
394
|
+
to = `_end_${from === '_start' ? 'start' : from}`;
|
|
395
|
+
}
|
|
396
|
+
if (!to)
|
|
397
|
+
return [];
|
|
398
|
+
const base = `${from}->${to}`;
|
|
399
|
+
const k = (counts.get(base) ?? 0) + 1;
|
|
400
|
+
counts.set(base, k);
|
|
401
|
+
return [{ id: k === 1 ? base : `${base}#${k}`, from, to, label: e.label, style: 'sync' }];
|
|
402
|
+
});
|
|
403
|
+
if (startUsed)
|
|
404
|
+
nodes.unshift({ id: '_start', label: '', kind: 'start' });
|
|
405
|
+
for (const f of endFor)
|
|
406
|
+
nodes.push({ id: `_end_${f === '_start' ? 'start' : f}`, label: '', kind: 'end' });
|
|
407
|
+
const outGroups = groups.map((g) => ({ id: ids.get(g.raw), label: labels.get(g.raw) ?? humanize(g.raw), parent: g.parent ? ids.get(g.parent) : undefined }));
|
|
408
|
+
return { diagram: { type: 'state', direction, theme: 'clean', groups: outGroups, nodes, edges: outEdges }, warnings: [...warnings] };
|
|
409
|
+
}
|
|
410
|
+
// ---------- sequence ----------
|
|
411
|
+
const SEQ_ARROWS = [
|
|
412
|
+
['-->>', 'reply'],
|
|
413
|
+
['->>', 'sync'],
|
|
414
|
+
['--x', 'reply'],
|
|
415
|
+
['-x', 'sync'],
|
|
416
|
+
['--)', 'async'],
|
|
417
|
+
['-)', 'async'],
|
|
418
|
+
['-->', 'reply'],
|
|
419
|
+
['->', 'sync'],
|
|
420
|
+
];
|
|
421
|
+
function sequenceDiagram(lines) {
|
|
422
|
+
const warnings = new Set();
|
|
423
|
+
const parts = new Map();
|
|
424
|
+
const used = new Set();
|
|
425
|
+
const pid = new Map();
|
|
426
|
+
const declare = (raw, label, kind = 'service') => {
|
|
427
|
+
if (pid.has(raw)) {
|
|
428
|
+
if (label)
|
|
429
|
+
parts.get(pid.get(raw)).label = label;
|
|
430
|
+
return pid.get(raw);
|
|
431
|
+
}
|
|
432
|
+
const id = safeId(raw, used);
|
|
433
|
+
pid.set(raw, id);
|
|
434
|
+
parts.set(id, { id, label: label ?? (raw.length <= 3 ? raw : humanize(raw)), kind: inferParticipant(label ?? raw, kind) });
|
|
435
|
+
return id;
|
|
436
|
+
};
|
|
437
|
+
const root = [];
|
|
438
|
+
const stack = [];
|
|
439
|
+
const cur = () => (stack.length ? stack[stack.length - 1].branches[stack[stack.length - 1].branches.length - 1].items : root);
|
|
440
|
+
for (const raw of lines.slice(1)) {
|
|
441
|
+
const l = raw.trim();
|
|
442
|
+
let m;
|
|
443
|
+
if ((m = /^(participant|actor)\s+(\S+?)(?:\s+as\s+(.+))?$/.exec(l))) {
|
|
444
|
+
declare(m[2], m[3] ? clean(m[3]) : undefined, m[1] === 'actor' ? 'person' : 'service');
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if ((m = /^(alt|opt|loop|par|critical|break)\b\s*(.*)$/.exec(l))) {
|
|
448
|
+
stack.push({ op: m[1], branches: [{ when: m[2] ? clean(m[2]) : undefined, items: [] }] });
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if ((m = /^(else|and|option)\b\s*(.*)$/.exec(l))) {
|
|
452
|
+
stack[stack.length - 1]?.branches.push({ when: m[2] ? clean(m[2]) : undefined, items: [] });
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (/^rect\b/.test(l)) {
|
|
456
|
+
stack.push({ op: 'rect', branches: [{ items: [] }] });
|
|
457
|
+
warnings.add('rect highlights are dropped');
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (l === 'end') {
|
|
461
|
+
const f = stack.pop();
|
|
462
|
+
if (!f)
|
|
463
|
+
continue;
|
|
464
|
+
if (f.op === 'rect')
|
|
465
|
+
cur().push(...f.branches[0].items);
|
|
466
|
+
else
|
|
467
|
+
cur().push({ kind: 'frame', op: f.op, branches: f.branches });
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if ((m = /^note\s+(over|left of|right of)\s+([^:]+):\s*(.*)$/i.exec(l))) {
|
|
471
|
+
const over = m[2].split(',').map((s) => declare(s.trim()));
|
|
472
|
+
const side = m[1].toLowerCase().startsWith('left') ? 'left' : m[1].toLowerCase().startsWith('right') ? 'right' : 'over';
|
|
473
|
+
cur().push({ kind: 'note', over, side, text: clean(m[3]).replace(/<br\s*\/?>/gi, ' ') });
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (/^(activate|deactivate|autonumber|box|end box|title|create|destroy|links?|properties|details)\b/.test(l))
|
|
477
|
+
continue;
|
|
478
|
+
let matched = false;
|
|
479
|
+
for (const [tok, style] of SEQ_ARROWS) {
|
|
480
|
+
const i = l.indexOf(tok);
|
|
481
|
+
if (i <= 0)
|
|
482
|
+
continue;
|
|
483
|
+
const from = l.slice(0, i).trim();
|
|
484
|
+
const after = l.slice(i + tok.length);
|
|
485
|
+
const mm = /^\s*[+-]?\s*([^:]+?)\s*:\s*(.*)$/.exec(after) ?? /^\s*[+-]?\s*(\S+)\s*$/.exec(after);
|
|
486
|
+
if (!mm || !from)
|
|
487
|
+
continue;
|
|
488
|
+
cur().push({ kind: 'msg', from: declare(from), to: declare(mm[1].trim()), label: mm[2] ? clean(mm[2]) : undefined, style });
|
|
489
|
+
matched = true;
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
if (!matched)
|
|
493
|
+
warnings.add(`skipped a line linework couldn't read: "${l.slice(0, 60)}"`);
|
|
494
|
+
}
|
|
495
|
+
return { diagram: { type: 'sequence', direction: 'auto', theme: 'clean', groups: [], nodes: [...parts.values()], edges: [], seq: root }, warnings: [...warnings] };
|
|
496
|
+
}
|
|
497
|
+
function inferParticipant(label, kind) {
|
|
498
|
+
if (kind === 'person')
|
|
499
|
+
return 'person';
|
|
500
|
+
if (/\b(db|database|postgres|mysql|redis|dynamo|mongo|cache)\b/i.test(label))
|
|
501
|
+
return 'database';
|
|
502
|
+
if (/\b(queue|kafka|sqs|topic|bus)\b/i.test(label))
|
|
503
|
+
return 'queue';
|
|
504
|
+
if (/\b(browser|client|frontend|web ?app|mobile|ui)\b/i.test(label))
|
|
505
|
+
return 'client';
|
|
506
|
+
if (/\b(stripe|twilio|sendgrid|auth0|okta|github|slack|external|third.party|provider)\b/i.test(label))
|
|
507
|
+
return 'external';
|
|
508
|
+
return 'service';
|
|
509
|
+
}
|
|
510
|
+
// ---------- ER ----------
|
|
511
|
+
const L_CARD = { '|o': 'zero_or_one', '||': 'one', '}o': 'many', '}|': 'one_or_many' };
|
|
512
|
+
const R_CARD = { 'o|': 'zero_or_one', '||': 'one', 'o{': 'many', '|{': 'one_or_many' };
|
|
513
|
+
function erDiagram(lines) {
|
|
514
|
+
const warnings = new Set();
|
|
515
|
+
const ents = new Map();
|
|
516
|
+
const used = new Set();
|
|
517
|
+
const eid = new Map();
|
|
518
|
+
const ent = (raw) => {
|
|
519
|
+
const name = raw.replace(/^"|"$/g, '');
|
|
520
|
+
if (!eid.has(name)) {
|
|
521
|
+
const id = safeId(name.toLowerCase(), used);
|
|
522
|
+
eid.set(name, id);
|
|
523
|
+
ents.set(id, { id, label: name, kind: 'entity', fields: [] });
|
|
524
|
+
}
|
|
525
|
+
return ents.get(eid.get(name));
|
|
526
|
+
};
|
|
527
|
+
const edges = [];
|
|
528
|
+
let open = null;
|
|
529
|
+
for (const raw of lines.slice(1)) {
|
|
530
|
+
const l = raw.trim();
|
|
531
|
+
let m;
|
|
532
|
+
if (open) {
|
|
533
|
+
if (l === '}') {
|
|
534
|
+
open = null;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if ((m = /^([\w()[\],.-]+)\s+([\w.-]+)((?:\s+(?:PK|FK|UK)\s*,?)*)\s*(?:"[^"]*")?$/i.exec(l))) {
|
|
538
|
+
const flags = (m[3] ?? '').split(/[\s,]+/).filter(Boolean).map((f) => (f.toLowerCase() === 'uk' ? 'unique' : f.toLowerCase()));
|
|
539
|
+
open.fields.push({ name: m[2], type: m[1], flags });
|
|
540
|
+
}
|
|
541
|
+
else
|
|
542
|
+
warnings.add(`skipped a field line: "${l.slice(0, 60)}"`);
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if ((m = /^("?[\w.-]+"?)\s*(?:\["[^"]*"\])?\s*\{$/.exec(l))) {
|
|
546
|
+
open = ent(m[1]);
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if ((m = /^("?[\w.-]+"?)\s+([|}][o|])(--|\.\.)([o|][|{])\s+("?[\w.-]+"?)\s*:\s*(.+)$/.exec(l))) {
|
|
550
|
+
const a = ent(m[1]), b = ent(m[5]);
|
|
551
|
+
const base = `${a.id}->${b.id}`;
|
|
552
|
+
const k = edges.filter((e) => e.id.startsWith(base)).length + 1;
|
|
553
|
+
edges.push({ id: k === 1 ? base : `${base}#${k}`, from: a.id, to: b.id, label: clean(m[6]) || undefined, style: 'line', card: { from: L_CARD[m[2]], to: R_CARD[m[4]], identifying: m[3] === '--' } });
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if ((m = /^("?[\w.-]+"?)$/.exec(l))) {
|
|
557
|
+
ent(m[1]);
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
if (/^(direction|title|classDef|class|style)\b/.test(l))
|
|
561
|
+
continue;
|
|
562
|
+
warnings.add(`skipped a line linework couldn't read: "${l.slice(0, 60)}"`);
|
|
563
|
+
}
|
|
564
|
+
// Mermaid entity names are often UPPERCASE; show them as written.
|
|
565
|
+
for (const n of ents.values())
|
|
566
|
+
if (!n.fields?.length)
|
|
567
|
+
delete n.fields;
|
|
568
|
+
return { diagram: { type: 'er', direction: 'auto', theme: 'clean', groups: [], nodes: [...ents.values()], edges }, warnings: [...warnings] };
|
|
569
|
+
}
|