dsh-model-everything 0.5.0 → 0.5.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.
Files changed (62) hide show
  1. package/README.en-US.md +12 -0
  2. package/README.md +4 -0
  3. package/lib/client.js +2437 -216
  4. package/lib/render/channels.js +243 -0
  5. package/lib/render/collapse.js +12 -20
  6. package/lib/render/components.js +99 -0
  7. package/lib/render/coords.js +288 -0
  8. package/lib/render/cycle-break.js +147 -0
  9. package/lib/render/dummy.js +43 -0
  10. package/lib/render/from-mermaid.js +34 -10
  11. package/lib/render/from-plantuml.js +318 -46
  12. package/lib/render/layer.js +91 -0
  13. package/lib/render/layout.js +27 -11
  14. package/lib/render/network-simplex.js +272 -0
  15. package/lib/render/order.js +122 -0
  16. package/lib/render/paint.js +764 -67
  17. package/lib/render/place.js +393 -121
  18. package/lib/render/route.js +225 -0
  19. package/lib/render/svg.js +143 -21
  20. package/lib/render/symbol.js +108 -0
  21. package/lib/render/text.js +74 -0
  22. package/lib/render/wrap.js +87 -0
  23. package/lib/types/render/channels.d.ts +63 -0
  24. package/lib/types/render/components.d.ts +57 -0
  25. package/lib/types/render/coords.d.ts +64 -0
  26. package/lib/types/render/cycle-break.d.ts +45 -0
  27. package/lib/types/render/dummy.d.ts +32 -0
  28. package/lib/types/render/from-plantuml.d.ts +12 -6
  29. package/lib/types/render/layer.d.ts +28 -0
  30. package/lib/types/render/layout.d.ts +43 -11
  31. package/lib/types/render/model.d.ts +17 -0
  32. package/lib/types/render/network-simplex.d.ts +54 -0
  33. package/lib/types/render/order.d.ts +27 -0
  34. package/lib/types/render/paint.d.ts +42 -9
  35. package/lib/types/render/place.d.ts +35 -19
  36. package/lib/types/render/route.d.ts +77 -0
  37. package/lib/types/render/svg.d.ts +1 -1
  38. package/lib/types/render/symbol.d.ts +73 -0
  39. package/lib/types/render/text.d.ts +38 -0
  40. package/lib/types/render/wrap.d.ts +27 -0
  41. package/package.json +148 -148
  42. package/skills/model-everything-diagram/SKILL.md +84 -82
  43. package/src/render/channels.ts +309 -0
  44. package/src/render/collapse.ts +150 -155
  45. package/src/render/components.ts +126 -0
  46. package/src/render/coords.ts +328 -0
  47. package/src/render/cycle-break.ts +164 -0
  48. package/src/render/dummy.ts +65 -0
  49. package/src/render/from-mermaid.ts +261 -236
  50. package/src/render/from-plantuml.ts +469 -178
  51. package/src/render/layer.ts +98 -0
  52. package/src/render/layout.ts +101 -62
  53. package/src/render/model.ts +86 -67
  54. package/src/render/network-simplex.ts +290 -0
  55. package/src/render/order.ts +139 -0
  56. package/src/render/paint.ts +1006 -290
  57. package/src/render/place.ts +527 -234
  58. package/src/render/route.ts +269 -0
  59. package/src/render/svg.ts +270 -127
  60. package/src/render/symbol.ts +137 -0
  61. package/src/render/text.ts +86 -0
  62. 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, { id, role: 'normal', ...(label === undefined ? {} : { label }) })
64
- // Membership is decided where the node is first written: the innermost open boundary. Naming it again
65
- // later from an outer boundary does not move it - the outer boundary holds it through `parent`.
66
- const innermost = builder.open[builder.open.length - 1]
67
- if (innermost !== undefined && !innermost.members.includes(id)) innermost.members.push(id)
68
- return
69
- }
70
- if (label !== undefined && (existing.label === undefined || existing.label === '')) existing.label = label
71
- }
72
-
73
- function nodeFrom(token: string): { id: string; label?: string } | undefined {
74
- const match = NODE.exec(token.trim())
75
- if (match === null) return undefined
76
- const id = match[1]
77
- const label = [match[2], match[3], match[4], match[5], match[6], match[7]].find((value) => value !== undefined && value.trim() !== '')
78
- return { id, ...(label === undefined ? {} : { label: label.trim().replace(/^["']|["']$/g, '') }) }
79
- }
80
-
81
- export function fromMermaid(source: string, title = 'mermaid'): Parsed {
82
- const lines = source.split(/\r?\n/)
83
- const refused: string[] = []
84
- const builder: Builder = { nodes: new Map(), edges: [], groups: [], open: [] }
85
- let mode: 'none' | 'graph' | 'state' = 'none'
86
- let startId: string | undefined
87
- let endCount = 0
88
-
89
- const push = (edge: DiagramEdge): void => {
90
- builder.edges.push(edge)
91
- }
92
-
93
- lines.forEach((raw, index) => {
94
- const line = raw.replace(/%%(.*)$/, '').trim()
95
- if (line === '') return
96
- const where = 'line ' + String(index + 1) + ': '
97
- const lower = line.toLowerCase()
98
- const keyword = lower.split(/[\s{}]/)[0] ?? ''
99
-
100
- if (UNSUPPORTED.has(keyword)) {
101
- refused.push(where + '`' + keyword + '` is not a diagram this renderer draws')
102
- return
103
- }
104
- if (DIRECTIVE.test(line)) {
105
- refused.push(where + '`' + (line.split(/\s/)[0] ?? '') + '` is a directive this renderer ignores')
106
- return
107
- }
108
- if (/^(graph|flowchart)\b/.test(lower)) {
109
- mode = 'graph'
110
- return
111
- }
112
- if (/^statediagram(-v2)?\b/.test(lower)) {
113
- mode = 'state'
114
- return
115
- }
116
- if (/^subgraph\b/.test(lower)) {
117
- const label = line.replace(/^subgraph\b/i, '').trim().replace(/^["']|["']$/g, '')
118
- const id = 'subgraph-' + String(++endCount)
119
- builder.open.push({ id, label: label === '' ? id : label, members: [] })
120
- return
121
- }
122
- if (lower === 'end') {
123
- const closed = builder.open.pop()
124
- if (closed !== undefined) {
125
- const container = builder.open[builder.open.length - 1]
126
- if (container !== undefined) closed.parent = container.id
127
- builder.groups.push(closed)
128
- }
129
- return
130
- }
131
-
132
- if (mode === 'state') {
133
- const alias = STATE_ALIAS.exec(line)
134
- if (alias !== null) {
135
- ensure(builder, alias[2], alias[1])
136
- return
137
- }
138
- const transition = TRANSITION.exec(line)
139
- if (transition === null) {
140
- // A bare `state X` declaration, or a state named on its own line.
141
- const bare = /^state\s+([A-Za-z_][\w-]*)$/.exec(line)
142
- if (bare !== null) {
143
- ensure(builder, bare[1])
144
- return
145
- }
146
- if (/^[A-Za-z_][\w-]*$/.test(line)) {
147
- ensure(builder, line)
148
- return
149
- }
150
- refused.push(where + '`' + line + '` is not a state transition this renderer draws')
151
- return
152
- }
153
- const from = transition[1]
154
- const to = transition[2]
155
- const label = (transition[3] ?? '').trim()
156
- if (from === '[*]') {
157
- // A start marker names the state it enters, not a box of its own.
158
- ensure(builder, to)
159
- if (startId === undefined) startId = to
160
- return
161
- }
162
- if (to === '[*]') {
163
- ensure(builder, from)
164
- push({ from, to: from, label: label === '' ? 'end' : label, dashed: true })
165
- return
166
- }
167
- ensure(builder, from)
168
- ensure(builder, to)
169
- push({ from, to, label })
170
- return
171
- }
172
-
173
- const labelled = LABELLED_EDGE.exec(line)
174
- if (labelled !== null) {
175
- const left = nodeFrom(labelled[1])
176
- const right = nodeFrom(labelled[3])
177
- if (left === undefined || right === undefined) {
178
- refused.push(where + '`' + line + '` does not name two nodes')
179
- return
180
- }
181
- ensure(builder, left.id, left.label)
182
- ensure(builder, right.id, right.label)
183
- push({ from: left.id, to: right.id, label: labelled[2].trim() })
184
- return
185
- }
186
-
187
- const edge = CURVED_EDGE.exec(line)
188
- if (edge !== null) {
189
- const left = nodeFrom(edge[1])
190
- const right = nodeFrom(edge[4])
191
- if (left === undefined || right === undefined) {
192
- refused.push(where + '`' + line + '` does not name two nodes')
193
- return
194
- }
195
- ensure(builder, left.id, left.label)
196
- ensure(builder, right.id, right.label)
197
- push({
198
- from: left.id,
199
- to: right.id,
200
- label: (edge[3] ?? '').trim(),
201
- ...(edge[2] === '-.->' || edge[2] === '==>' ? { dashed: true } : {}),
202
- })
203
- return
204
- }
205
-
206
- const single = nodeFrom(line)
207
- if (single !== undefined) {
208
- ensure(builder, single.id, single.label)
209
- return
210
- }
211
-
212
- refused.push(where + '`' + line + '` is not something this renderer draws')
213
- })
214
-
215
- if (mode === 'none') {
216
- return { diagram: emptyDiagram(title, 'no mermaid diagram found'), refused }
217
- }
218
-
219
- if (builder.nodes.size === 0) {
220
- return { diagram: emptyDiagram(title, 'no nodes found'), refused }
221
- }
222
-
223
- const diagram: Diagram = {
224
- kind: mode === 'state' ? 'machine' : 'graph',
225
- title,
226
- ...(startId === undefined ? {} : { init: startId }),
227
- nodes: [...builder.nodes.values()],
228
- edges: builder.edges,
229
- ...(builder.groups.length === 0 ? {} : { groups: builder.groups }),
230
- caption:
231
- mode + ', ' + String(builder.nodes.size) + ' box' + (builder.nodes.size === 1 ? '' : 'es') + ', ' +
232
- String(builder.edges.length) + ' arrow' + (builder.edges.length === 1 ? '' : 's') +
233
- (refused.length === 0 ? '' : ', ' + String(refused.length) + ' line(s) refused'),
234
- }
235
- return { diagram, refused }
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
+ }