dsh-model-everything 0.4.1 → 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.
- package/README.en-US.md +75 -19
- package/README.md +377 -345
- package/cordis.patch.yml +9 -0
- package/diagram/package.json +5 -0
- package/lib/client.js +3494 -0
- package/lib/diagram-diff-tool.js +90 -0
- package/lib/diagram-render-tool.js +102 -0
- package/lib/diagram-review-tool.js +99 -0
- package/lib/diagram.js +43 -0
- package/lib/gate.js +14 -12
- package/lib/presentation.js +10 -0
- package/lib/render/budget.js +99 -0
- package/lib/render/channels.js +243 -0
- package/lib/render/collapse.js +155 -0
- 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/diff.js +138 -0
- package/lib/render/dummy.js +43 -0
- package/lib/render/from-mermaid.js +235 -0
- package/lib/render/from-plantuml.js +430 -0
- package/lib/render/index.js +42 -0
- package/lib/render/layer.js +91 -0
- package/lib/render/layout.js +68 -0
- package/lib/render/model.js +17 -0
- package/lib/render/network-simplex.js +272 -0
- package/lib/render/order.js +122 -0
- package/lib/render/paint.js +891 -0
- package/lib/render/palette.js +127 -0
- package/lib/render/place.js +482 -0
- package/lib/render/route.js +225 -0
- package/lib/render/svg.js +229 -0
- package/lib/render/symbol.js +108 -0
- package/lib/render/text.js +74 -0
- package/lib/render/viewmodel.js +126 -0
- package/lib/render/wrap.js +87 -0
- package/lib/render/write.js +14 -0
- package/lib/scan.js +248 -8
- package/lib/structure-tool.js +2 -2
- package/lib/types/client.d.ts +83 -0
- package/lib/types/diagram-diff-tool.d.ts +8 -0
- package/lib/types/diagram-render-tool.d.ts +8 -0
- package/lib/types/diagram-review-tool.d.ts +8 -0
- package/lib/types/diagram.d.ts +34 -0
- package/lib/types/presentation.d.ts +7 -0
- package/lib/types/render/budget.d.ts +55 -0
- package/lib/types/render/channels.d.ts +63 -0
- package/lib/types/render/collapse.d.ts +23 -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/diff.d.ts +44 -0
- package/lib/types/render/dummy.d.ts +32 -0
- package/lib/types/render/from-mermaid.d.ts +27 -0
- package/lib/types/render/from-plantuml.d.ts +33 -0
- package/lib/types/render/index.d.ts +38 -0
- package/lib/types/render/layer.d.ts +28 -0
- package/lib/types/render/layout.d.ts +54 -0
- package/lib/types/render/model.d.ts +73 -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 +127 -0
- package/lib/types/render/palette.d.ts +41 -0
- package/lib/types/render/place.d.ts +55 -0
- package/lib/types/render/route.d.ts +77 -0
- package/lib/types/render/svg.d.ts +19 -0
- package/lib/types/render/symbol.d.ts +73 -0
- package/lib/types/render/text.d.ts +38 -0
- package/lib/types/render/viewmodel.d.ts +20 -0
- package/lib/types/render/wrap.d.ts +27 -0
- package/lib/types/render/write.d.ts +14 -0
- package/lib/types/scan.d.ts +10 -2
- package/lib/types/structure.d.ts +1 -0
- package/package.json +148 -132
- package/skills/model-everything/SKILL.md +454 -454
- package/skills/model-everything/references/architecture-domains.md +137 -0
- package/skills/model-everything/references/architecture-views.md +119 -0
- package/skills/model-everything-diagram/SKILL.md +84 -0
- package/src/client.ts +307 -0
- package/src/diagram-diff-tool.ts +94 -0
- package/src/diagram-render-tool.ts +108 -0
- package/src/diagram-review-tool.ts +109 -0
- package/src/diagram.ts +61 -0
- package/src/gate.ts +14 -12
- package/src/presentation.ts +12 -0
- package/src/render/budget.ts +135 -0
- package/src/render/channels.ts +309 -0
- package/src/render/collapse.ts +150 -0
- 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/diff.ts +162 -0
- package/src/render/dummy.ts +65 -0
- package/src/render/from-mermaid.ts +261 -0
- package/src/render/from-plantuml.ts +469 -0
- package/src/render/index.ts +73 -0
- package/src/render/layer.ts +98 -0
- package/src/render/layout.ts +101 -0
- package/src/render/model.ts +86 -0
- package/src/render/network-simplex.ts +290 -0
- package/src/render/order.ts +139 -0
- package/src/render/paint.ts +1006 -0
- package/src/render/palette.ts +164 -0
- package/src/render/place.ts +527 -0
- package/src/render/route.ts +269 -0
- package/src/render/svg.ts +270 -0
- package/src/render/symbol.ts +137 -0
- package/src/render/text.ts +86 -0
- package/src/render/viewmodel.ts +136 -0
- package/src/render/wrap.ts +103 -0
- package/src/render/write.ts +17 -0
- package/src/scan.ts +807 -564
- package/src/structure-tool.ts +2 -2
- package/src/structure.ts +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dummy nodes: the placeholders a long edge stands on.
|
|
3
|
+
*
|
|
4
|
+
* An edge from layer 0 to layer 3 has to cross layers 1 and 2. The router can go around whatever is in the way, but a
|
|
5
|
+
* drawing reads better if the edge owns a lane through those layers: then ordering can keep other edges off it, and
|
|
6
|
+
* coordinate assignment can give the whole lane one x. A dummy node is that lane: one per layer the edge crosses.
|
|
7
|
+
*
|
|
8
|
+
* A lane is one variable, not one variable per layer. The instances keep their own id (`#d<edge>_<layer>`) so the
|
|
9
|
+
* ordering stage can sort them inside their layer, and they all point at one lane id (`#e<edge>`) so coordinate
|
|
10
|
+
* assignment cannot give the same edge three different x values.
|
|
11
|
+
*
|
|
12
|
+
* Pure: the layers, the chains, and the map between the two.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { LayoutEdge } from './layout.js'
|
|
16
|
+
import type { Layering } from './layer.js'
|
|
17
|
+
|
|
18
|
+
/** One edge, as the id it passes through in each layer it crosses. */
|
|
19
|
+
export interface Chain {
|
|
20
|
+
/** The diagram edge's index. */
|
|
21
|
+
index: number
|
|
22
|
+
/** The ids from the layout source to the layout target: the endpoints plus one dummy per crossed layer. */
|
|
23
|
+
path: string[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Augmented {
|
|
27
|
+
/** The layers with the dummies inserted; each dummy sits in the layer it crosses. */
|
|
28
|
+
layers: string[][]
|
|
29
|
+
chains: Chain[]
|
|
30
|
+
/** A dummy instance's lane id: the variable coordinate assignment shares between a long edge's dummies. */
|
|
31
|
+
chainOf: Map<string, string>
|
|
32
|
+
/** The lane id of every edge that needed dummies. */
|
|
33
|
+
laneOf: Map<number, string>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function addDummies(layering: Layering, edges: LayoutEdge[]): Augmented {
|
|
37
|
+
const layers = layering.layers.map((layer) => [...layer])
|
|
38
|
+
const chains: Chain[] = []
|
|
39
|
+
const chainOf = new Map<string, string>()
|
|
40
|
+
const laneOf = new Map<number, string>()
|
|
41
|
+
for (const edge of edges) {
|
|
42
|
+
if (edge.from === edge.to) {
|
|
43
|
+
chains.push({ index: edge.index, path: [edge.from, edge.to] })
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
const from = layering.layerOf.get(edge.from)
|
|
47
|
+
const to = layering.layerOf.get(edge.to)
|
|
48
|
+
if (from === undefined || to === undefined || to <= from) {
|
|
49
|
+
chains.push({ index: edge.index, path: [edge.from, edge.to] })
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
const path = [edge.from]
|
|
53
|
+
for (let cursor = from + 1; cursor < to; cursor += 1) {
|
|
54
|
+
const instance = '#d' + String(edge.index) + '_' + String(cursor)
|
|
55
|
+
const lane = '#e' + String(edge.index)
|
|
56
|
+
chainOf.set(instance, lane)
|
|
57
|
+
laneOf.set(edge.index, lane)
|
|
58
|
+
layers[cursor].push(instance)
|
|
59
|
+
path.push(instance)
|
|
60
|
+
}
|
|
61
|
+
path.push(edge.to)
|
|
62
|
+
chains.push({ index: edge.index, path })
|
|
63
|
+
}
|
|
64
|
+
return { layers, chains, chainOf, laneOf }
|
|
65
|
+
}
|
|
@@ -0,0 +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, 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
|
+
}
|