dsh-model-everything 0.5.0 → 0.5.2
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.en-US.md +12 -0
- package/README.md +4 -0
- package/lib/client.js +2829 -224
- package/lib/render/channels.js +243 -0
- package/lib/render/collapse.js +12 -20
- package/lib/render/components.js +99 -0
- package/lib/render/coords.js +288 -0
- package/lib/render/cycle-break.js +147 -0
- package/lib/render/dummy.js +43 -0
- package/lib/render/from-mermaid.js +34 -10
- package/lib/render/from-plantuml.js +318 -46
- package/lib/render/layer.js +91 -0
- package/lib/render/layout.js +27 -11
- package/lib/render/network-simplex.js +272 -0
- package/lib/render/order.js +122 -0
- package/lib/render/paint.js +1227 -69
- package/lib/render/place.js +393 -121
- package/lib/render/route.js +225 -0
- package/lib/render/svg.js +146 -21
- package/lib/render/symbol.js +108 -0
- package/lib/render/text.js +74 -0
- package/lib/render/trunk.js +213 -0
- package/lib/render/wrap.js +87 -0
- package/lib/types/render/channels.d.ts +63 -0
- package/lib/types/render/components.d.ts +57 -0
- package/lib/types/render/coords.d.ts +64 -0
- package/lib/types/render/cycle-break.d.ts +45 -0
- package/lib/types/render/dummy.d.ts +32 -0
- package/lib/types/render/from-plantuml.d.ts +12 -6
- package/lib/types/render/layer.d.ts +28 -0
- package/lib/types/render/layout.d.ts +43 -11
- package/lib/types/render/model.d.ts +17 -0
- package/lib/types/render/network-simplex.d.ts +54 -0
- package/lib/types/render/order.d.ts +27 -0
- package/lib/types/render/paint.d.ts +157 -10
- package/lib/types/render/place.d.ts +40 -19
- package/lib/types/render/route.d.ts +77 -0
- package/lib/types/render/svg.d.ts +1 -1
- package/lib/types/render/symbol.d.ts +73 -0
- package/lib/types/render/text.d.ts +38 -0
- package/lib/types/render/trunk.d.ts +104 -0
- package/lib/types/render/wrap.d.ts +27 -0
- package/package.json +148 -148
- package/skills/model-everything-diagram/SKILL.md +85 -82
- package/src/render/channels.ts +309 -0
- package/src/render/collapse.ts +150 -155
- package/src/render/components.ts +126 -0
- package/src/render/coords.ts +328 -0
- package/src/render/cycle-break.ts +164 -0
- package/src/render/dummy.ts +65 -0
- package/src/render/from-mermaid.ts +261 -236
- package/src/render/from-plantuml.ts +469 -178
- package/src/render/layer.ts +98 -0
- package/src/render/layout.ts +101 -62
- package/src/render/model.ts +86 -67
- package/src/render/network-simplex.ts +290 -0
- package/src/render/order.ts +139 -0
- package/src/render/paint.ts +1570 -290
- package/src/render/place.ts +532 -234
- package/src/render/route.ts +269 -0
- package/src/render/svg.ts +274 -127
- package/src/render/symbol.ts +137 -0
- package/src/render/text.ts +86 -0
- package/src/render/trunk.ts +275 -0
- package/src/render/wrap.ts +103 -0
|
@@ -1,236 +1,261 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mermaid text to a diagram, for the subset this renderer can actually draw.
|
|
3
|
-
*
|
|
4
|
-
* The subset is chosen by what a reader of an architecture document writes: a flowchart of boxes and
|
|
5
|
-
* arrows, a state diagram with a start marker, and the group syntax that turns a flat graph into a layered
|
|
6
|
-
* one. Everything outside it is refused **with its line number** rather than half-drawn: a picture that
|
|
7
|
-
* silently drops a lifeline or an interaction is worse than no picture, because it looks complete.
|
|
8
|
-
*
|
|
9
|
-
* Supported:
|
|
10
|
-
*
|
|
11
|
-
* - `graph TD|TB|LR` and `flowchart TD|TB|LR`, with direction carried into the layout as a hint;
|
|
12
|
-
* - node declarations `A[text]`, `A(text)`, `A{text}`, `A((text))`, and a bare `A`;
|
|
13
|
-
* - edges `-->`, `---`, `-.->`, `==>`, with a label as `-->|text|` or `-- text -->`;
|
|
14
|
-
* - `stateDiagram` / `stateDiagram-v2`: `[*] --> A`, `A --> B: event`, `state "text" as id`;
|
|
15
|
-
* - `subgraph name ... end`, which becomes a group;
|
|
16
|
-
* - `%%` comments and blank lines.
|
|
17
|
-
*
|
|
18
|
-
* Refused, by name: `sequenceDiagram`, `classDiagram`, `erDiagram`, `gantt`, `pie`, `journey`, `mindmap`,
|
|
19
|
-
* `timeline`, `quadrantChart`, `gitGraph`, `C4Context`, and `click`/`style`/`classDef` directives.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { emptyDiagram, type Diagram, type DiagramEdge, type DiagramGroup, type DiagramNode } from './model.js'
|
|
23
|
-
|
|
24
|
-
export interface Parsed {
|
|
25
|
-
diagram: Diagram
|
|
26
|
-
/** Lines the parser refused to interpret, with the reason. Empty when the whole source was understood. */
|
|
27
|
-
refused: string[]
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const CURVED_EDGE = /^(.+?)\s*(-->|---|-\.->|==>)\s*(?:\|([^|]*)\|\s*)?(.+)$/
|
|
31
|
-
const LABELLED_EDGE = /^(.+?)\s*--\s*([^->]+?)\s*-->\s*(.+)$/
|
|
32
|
-
const NODE = /^([A-Za-z_][\w-]*)\s*(?:\[\((.*)\)\]|\[\[(.*)\]\]|\[(.*)\]|\((.*)\)|\{(.*)\}|<<(.*)>>)?$/
|
|
33
|
-
const TRANSITION = /^([A-Za-z_][\w-]*|\[\*\])\s*-->\s*([A-Za-z_][\w-]*|\[\*\])\s*(?::\s*(.*))?$/
|
|
34
|
-
const STATE_ALIAS = /^state\s+["'](.+?)["']\s+as\s+([A-Za-z_][\w-]*)$/
|
|
35
|
-
const DIRECTIVE = /^(click|style|classDef|class|linkStyle|accTitle|accDescr)\b/
|
|
36
|
-
const UNSUPPORTED = new Set([
|
|
37
|
-
'sequencediagram',
|
|
38
|
-
'classdiagram',
|
|
39
|
-
'erdiagram',
|
|
40
|
-
'gantt',
|
|
41
|
-
'pie',
|
|
42
|
-
'journey',
|
|
43
|
-
'mindmap',
|
|
44
|
-
'timeline',
|
|
45
|
-
'quadrantchart',
|
|
46
|
-
'gitgraph',
|
|
47
|
-
'c4context',
|
|
48
|
-
'requirementdiagram',
|
|
49
|
-
'sankey-beta',
|
|
50
|
-
])
|
|
51
|
-
|
|
52
|
-
interface Builder {
|
|
53
|
-
nodes: Map<string, DiagramNode>
|
|
54
|
-
edges: DiagramEdge[]
|
|
55
|
-
groups: DiagramGroup[]
|
|
56
|
-
/** The boundaries currently open, outermost first. */
|
|
57
|
-
open: DiagramGroup[]
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function ensure(builder: Builder, id: string, label?: string): void {
|
|
61
|
-
const existing = builder.nodes.get(id)
|
|
62
|
-
if (existing === undefined) {
|
|
63
|
-
builder.nodes.set(id, {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Mermaid text to a diagram, for the subset this renderer can actually draw.
|
|
3
|
+
*
|
|
4
|
+
* The subset is chosen by what a reader of an architecture document writes: a flowchart of boxes and
|
|
5
|
+
* arrows, a state diagram with a start marker, and the group syntax that turns a flat graph into a layered
|
|
6
|
+
* one. Everything outside it is refused **with its line number** rather than half-drawn: a picture that
|
|
7
|
+
* silently drops a lifeline or an interaction is worse than no picture, because it looks complete.
|
|
8
|
+
*
|
|
9
|
+
* Supported:
|
|
10
|
+
*
|
|
11
|
+
* - `graph TD|TB|LR` and `flowchart TD|TB|LR`, with direction carried into the layout as a hint;
|
|
12
|
+
* - node declarations `A[text]`, `A(text)`, `A{text}`, `A((text))`, and a bare `A`;
|
|
13
|
+
* - edges `-->`, `---`, `-.->`, `==>`, with a label as `-->|text|` or `-- text -->`;
|
|
14
|
+
* - `stateDiagram` / `stateDiagram-v2`: `[*] --> A`, `A --> B: event`, `state "text" as id`;
|
|
15
|
+
* - `subgraph name ... end`, which becomes a group;
|
|
16
|
+
* - `%%` comments and blank lines.
|
|
17
|
+
*
|
|
18
|
+
* Refused, by name: `sequenceDiagram`, `classDiagram`, `erDiagram`, `gantt`, `pie`, `journey`, `mindmap`,
|
|
19
|
+
* `timeline`, `quadrantChart`, `gitGraph`, `C4Context`, and `click`/`style`/`classDef` directives.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { emptyDiagram, type Diagram, type DiagramEdge, type DiagramGroup, type DiagramNode } from './model.js'
|
|
23
|
+
|
|
24
|
+
export interface Parsed {
|
|
25
|
+
diagram: Diagram
|
|
26
|
+
/** Lines the parser refused to interpret, with the reason. Empty when the whole source was understood. */
|
|
27
|
+
refused: string[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const CURVED_EDGE = /^(.+?)\s*(-->|---|-\.->|==>)\s*(?:\|([^|]*)\|\s*)?(.+)$/
|
|
31
|
+
const LABELLED_EDGE = /^(.+?)\s*--\s*([^->]+?)\s*-->\s*(.+)$/
|
|
32
|
+
const NODE = /^([A-Za-z_][\w-]*)\s*(?:\[\((.*)\)\]|\[\[(.*)\]\]|\[(.*)\]|\((.*)\)|\{(.*)\}|<<(.*)>>)?$/
|
|
33
|
+
const TRANSITION = /^([A-Za-z_][\w-]*|\[\*\])\s*-->\s*([A-Za-z_][\w-]*|\[\*\])\s*(?::\s*(.*))?$/
|
|
34
|
+
const STATE_ALIAS = /^state\s+["'](.+?)["']\s+as\s+([A-Za-z_][\w-]*)$/
|
|
35
|
+
const DIRECTIVE = /^(click|style|classDef|class|linkStyle|accTitle|accDescr)\b/
|
|
36
|
+
const UNSUPPORTED = new Set([
|
|
37
|
+
'sequencediagram',
|
|
38
|
+
'classdiagram',
|
|
39
|
+
'erdiagram',
|
|
40
|
+
'gantt',
|
|
41
|
+
'pie',
|
|
42
|
+
'journey',
|
|
43
|
+
'mindmap',
|
|
44
|
+
'timeline',
|
|
45
|
+
'quadrantchart',
|
|
46
|
+
'gitgraph',
|
|
47
|
+
'c4context',
|
|
48
|
+
'requirementdiagram',
|
|
49
|
+
'sankey-beta',
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
interface Builder {
|
|
53
|
+
nodes: Map<string, DiagramNode>
|
|
54
|
+
edges: DiagramEdge[]
|
|
55
|
+
groups: DiagramGroup[]
|
|
56
|
+
/** The boundaries currently open, outermost first. */
|
|
57
|
+
open: DiagramGroup[]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ensure(builder: Builder, id: string, label?: string, shape?: DiagramNode['shape']): void {
|
|
61
|
+
const existing = builder.nodes.get(id)
|
|
62
|
+
if (existing === undefined) {
|
|
63
|
+
builder.nodes.set(id, {
|
|
64
|
+
id,
|
|
65
|
+
role: 'normal',
|
|
66
|
+
...(label === undefined ? {} : { label }),
|
|
67
|
+
...(shape === undefined ? {} : { shape }),
|
|
68
|
+
})
|
|
69
|
+
// Membership is decided where the node is first written: the innermost open boundary. Naming it again
|
|
70
|
+
// later from an outer boundary does not move it - the outer boundary holds it through `parent`.
|
|
71
|
+
const innermost = builder.open[builder.open.length - 1]
|
|
72
|
+
if (innermost !== undefined && !innermost.members.includes(id)) innermost.members.push(id)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
if (label !== undefined && (existing.label === undefined || existing.label === '')) existing.label = label
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function nodeFrom(token: string): { id: string; label?: string; shape?: DiagramNode['shape'] } | undefined {
|
|
79
|
+
const match = NODE.exec(token.trim())
|
|
80
|
+
if (match === null) return undefined
|
|
81
|
+
const id = match[1]
|
|
82
|
+
const label = [match[2], match[3], match[4], match[5], match[6], match[7]].find((value) => value !== undefined && value.trim() !== '')
|
|
83
|
+
// Group 2 `[(x)]` is a datastore, 3 `[[x]]` a subroutine, 4 `[x]` a rectangle, 5 `(x)` a rounded box, 6 `{x}` a
|
|
84
|
+
// decision, 7 `<<x>>` a hexagon. Mermaid's own mapping; a shape the notation did not state stays undefined.
|
|
85
|
+
const shape: DiagramNode['shape'] | undefined =
|
|
86
|
+
match[2] !== undefined
|
|
87
|
+
? 'cylinder'
|
|
88
|
+
: match[3] !== undefined
|
|
89
|
+
? 'subroutine'
|
|
90
|
+
: match[4] !== undefined
|
|
91
|
+
? 'rect'
|
|
92
|
+
: match[5] !== undefined
|
|
93
|
+
? 'stadium'
|
|
94
|
+
: match[6] !== undefined
|
|
95
|
+
? 'diamond'
|
|
96
|
+
: match[7] !== undefined
|
|
97
|
+
? 'hexagon'
|
|
98
|
+
: undefined
|
|
99
|
+
return {
|
|
100
|
+
id,
|
|
101
|
+
...(label === undefined ? {} : { label: label.trim().replace(/^["']|["']$/g, '') }),
|
|
102
|
+
...(shape === undefined ? {} : { shape }),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function fromMermaid(source: string, title = 'mermaid'): Parsed {
|
|
107
|
+
const lines = source.split(/\r?\n/)
|
|
108
|
+
const refused: string[] = []
|
|
109
|
+
const builder: Builder = { nodes: new Map(), edges: [], groups: [], open: [] }
|
|
110
|
+
let mode: 'none' | 'graph' | 'state' = 'none'
|
|
111
|
+
let startId: string | undefined
|
|
112
|
+
let endCount = 0
|
|
113
|
+
|
|
114
|
+
const push = (edge: DiagramEdge): void => {
|
|
115
|
+
builder.edges.push(edge)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
lines.forEach((raw, index) => {
|
|
119
|
+
const line = raw.replace(/%%(.*)$/, '').trim()
|
|
120
|
+
if (line === '') return
|
|
121
|
+
const where = 'line ' + String(index + 1) + ': '
|
|
122
|
+
const lower = line.toLowerCase()
|
|
123
|
+
const keyword = lower.split(/[\s{}]/)[0]
|
|
124
|
+
|
|
125
|
+
if (UNSUPPORTED.has(keyword)) {
|
|
126
|
+
refused.push(where + '`' + keyword + '` is not a diagram this renderer draws')
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
if (DIRECTIVE.test(line)) {
|
|
130
|
+
refused.push(where + '`' + line.split(/\s/)[0] + '` is a directive this renderer ignores')
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
if (/^(graph|flowchart)\b/.test(lower)) {
|
|
134
|
+
mode = 'graph'
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
if (/^statediagram(-v2)?\b/.test(lower)) {
|
|
138
|
+
mode = 'state'
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if (/^subgraph\b/.test(lower)) {
|
|
142
|
+
const label = line.replace(/^subgraph\b/i, '').trim().replace(/^["']|["']$/g, '')
|
|
143
|
+
const id = 'subgraph-' + String(++endCount)
|
|
144
|
+
builder.open.push({ id, label: label === '' ? id : label, members: [] })
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
if (lower === 'end') {
|
|
148
|
+
const closed = builder.open.pop()
|
|
149
|
+
if (closed !== undefined) {
|
|
150
|
+
const container = builder.open[builder.open.length - 1]
|
|
151
|
+
if (container !== undefined) closed.parent = container.id
|
|
152
|
+
builder.groups.push(closed)
|
|
153
|
+
}
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (mode === 'state') {
|
|
158
|
+
const alias = STATE_ALIAS.exec(line)
|
|
159
|
+
if (alias !== null) {
|
|
160
|
+
ensure(builder, alias[2], alias[1])
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
const transition = TRANSITION.exec(line)
|
|
164
|
+
if (transition === null) {
|
|
165
|
+
// A bare `state X` declaration, or a state named on its own line.
|
|
166
|
+
const bare = /^state\s+([A-Za-z_][\w-]*)$/.exec(line)
|
|
167
|
+
if (bare !== null) {
|
|
168
|
+
ensure(builder, bare[1])
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
if (/^[A-Za-z_][\w-]*$/.test(line)) {
|
|
172
|
+
ensure(builder, line)
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
refused.push(where + '`' + line + '` is not a state transition this renderer draws')
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
const from = transition[1]
|
|
179
|
+
const to = transition[2]
|
|
180
|
+
const label = (transition[3] ?? '').trim()
|
|
181
|
+
if (from === '[*]') {
|
|
182
|
+
// A start marker names the state it enters, not a box of its own.
|
|
183
|
+
ensure(builder, to)
|
|
184
|
+
if (startId === undefined) startId = to
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
if (to === '[*]') {
|
|
188
|
+
ensure(builder, from)
|
|
189
|
+
push({ from, to: from, label: label === '' ? 'end' : label, dashed: true })
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
ensure(builder, from)
|
|
193
|
+
ensure(builder, to)
|
|
194
|
+
push({ from, to, label })
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const labelled = LABELLED_EDGE.exec(line)
|
|
199
|
+
if (labelled !== null) {
|
|
200
|
+
const left = nodeFrom(labelled[1])
|
|
201
|
+
const right = nodeFrom(labelled[3])
|
|
202
|
+
if (left === undefined || right === undefined) {
|
|
203
|
+
refused.push(where + '`' + line + '` does not name two nodes')
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
ensure(builder, left.id, left.label, left.shape)
|
|
207
|
+
ensure(builder, right.id, right.label, right.shape)
|
|
208
|
+
push({ from: left.id, to: right.id, label: labelled[2].trim() })
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const edge = CURVED_EDGE.exec(line)
|
|
213
|
+
if (edge !== null) {
|
|
214
|
+
const left = nodeFrom(edge[1])
|
|
215
|
+
const right = nodeFrom(edge[4])
|
|
216
|
+
if (left === undefined || right === undefined) {
|
|
217
|
+
refused.push(where + '`' + line + '` does not name two nodes')
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
ensure(builder, left.id, left.label, left.shape)
|
|
221
|
+
ensure(builder, right.id, right.label, right.shape)
|
|
222
|
+
push({
|
|
223
|
+
from: left.id,
|
|
224
|
+
to: right.id,
|
|
225
|
+
label: (edge[3] ?? '').trim(),
|
|
226
|
+
...(edge[2] === '-.->' || edge[2] === '==>' ? { dashed: true } : {}),
|
|
227
|
+
})
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const single = nodeFrom(line)
|
|
232
|
+
if (single !== undefined) {
|
|
233
|
+
ensure(builder, single.id, single.label, single.shape)
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
refused.push(where + '`' + line + '` is not something this renderer draws')
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
if (mode === 'none') {
|
|
241
|
+
return { diagram: emptyDiagram(title, 'no mermaid diagram found'), refused }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (builder.nodes.size === 0) {
|
|
245
|
+
return { diagram: emptyDiagram(title, 'no nodes found'), refused }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const diagram: Diagram = {
|
|
249
|
+
kind: mode === 'state' ? 'machine' : 'graph',
|
|
250
|
+
title,
|
|
251
|
+
...(startId === undefined ? {} : { init: startId }),
|
|
252
|
+
nodes: [...builder.nodes.values()],
|
|
253
|
+
edges: builder.edges,
|
|
254
|
+
...(builder.groups.length === 0 ? {} : { groups: builder.groups }),
|
|
255
|
+
caption:
|
|
256
|
+
mode + ', ' + String(builder.nodes.size) + ' box' + (builder.nodes.size === 1 ? '' : 'es') + ', ' +
|
|
257
|
+
String(builder.edges.length) + ' arrow' + (builder.edges.length === 1 ? '' : 's') +
|
|
258
|
+
(refused.length === 0 ? '' : ', ' + String(refused.length) + ' line(s) refused'),
|
|
259
|
+
}
|
|
260
|
+
return { diagram, refused }
|
|
261
|
+
}
|