@diagrammo/dgmo 0.8.2 → 0.8.4
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/.claude/commands/dgmo-diagram-this.md +60 -0
- package/.claude/commands/dgmo-document-project.md +128 -0
- package/.claude/commands/dgmo.md +185 -50
- package/.cursorrules +32 -37
- package/.github/copilot-instructions.md +35 -44
- package/.windsurfrules +32 -37
- package/README.md +4 -4
- package/dist/cli.cjs +189 -194
- package/dist/editor.cjs +336 -0
- package/dist/editor.cjs.map +1 -0
- package/dist/editor.d.cts +27 -0
- package/dist/editor.d.ts +27 -0
- package/dist/editor.js +305 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.cjs +3699 -1564
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +3699 -1564
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +822 -1060
- package/gallery/fixtures/arc.dgmo +18 -0
- package/gallery/fixtures/area.dgmo +19 -0
- package/gallery/fixtures/bar-stacked.dgmo +10 -0
- package/gallery/fixtures/bar.dgmo +10 -0
- package/gallery/fixtures/c4-full.dgmo +52 -0
- package/gallery/fixtures/c4.dgmo +17 -0
- package/gallery/fixtures/chord.dgmo +12 -0
- package/gallery/fixtures/class-basic.dgmo +14 -0
- package/gallery/fixtures/class-full.dgmo +43 -0
- package/gallery/fixtures/doughnut.dgmo +8 -0
- package/gallery/fixtures/flowchart-basic.dgmo +3 -0
- package/gallery/fixtures/flowchart-colors.dgmo +5 -0
- package/gallery/fixtures/flowchart-complex.dgmo +17 -0
- package/gallery/fixtures/flowchart-decision.dgmo +5 -0
- package/gallery/fixtures/flowchart-full.dgmo +13 -0
- package/gallery/fixtures/flowchart-groups.dgmo +10 -0
- package/gallery/fixtures/flowchart-loop.dgmo +7 -0
- package/gallery/fixtures/flowchart-nested.dgmo +7 -0
- package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
- package/gallery/fixtures/function.dgmo +8 -0
- package/gallery/fixtures/funnel.dgmo +7 -0
- package/gallery/fixtures/gantt-full.dgmo +49 -0
- package/gallery/fixtures/gantt.dgmo +42 -0
- package/gallery/fixtures/heatmap.dgmo +8 -0
- package/gallery/fixtures/infra-full.dgmo +78 -0
- package/gallery/fixtures/infra-overload.dgmo +25 -0
- package/gallery/fixtures/infra.dgmo +47 -0
- package/gallery/fixtures/initiative-status-full.dgmo +46 -0
- package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
- package/gallery/fixtures/initiative-status.dgmo +9 -0
- package/gallery/fixtures/line.dgmo +19 -0
- package/gallery/fixtures/multi-line.dgmo +11 -0
- package/gallery/fixtures/org-basic.dgmo +16 -0
- package/gallery/fixtures/org-full.dgmo +69 -0
- package/gallery/fixtures/org-teams.dgmo +25 -0
- package/gallery/fixtures/pie.dgmo +9 -0
- package/gallery/fixtures/polar-area.dgmo +8 -0
- package/gallery/fixtures/quadrant.dgmo +18 -0
- package/gallery/fixtures/radar.dgmo +8 -0
- package/gallery/fixtures/sankey.dgmo +31 -0
- package/gallery/fixtures/scatter.dgmo +21 -0
- package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
- package/gallery/fixtures/sequence-tags.dgmo +41 -0
- package/gallery/fixtures/sequence.dgmo +35 -0
- package/gallery/fixtures/sitemap-basic.dgmo +12 -0
- package/gallery/fixtures/sitemap-full.dgmo +156 -0
- package/gallery/fixtures/slope.dgmo +8 -0
- package/gallery/fixtures/spr-eras.dgmo +62 -0
- package/gallery/fixtures/state.dgmo +30 -0
- package/gallery/fixtures/timeline-intraday.dgmo +14 -0
- package/gallery/fixtures/timeline.dgmo +32 -0
- package/gallery/fixtures/venn.dgmo +10 -0
- package/gallery/fixtures/wordcloud.dgmo +24 -0
- package/package.json +51 -2
- package/src/c4/layout.ts +372 -90
- package/src/c4/parser.ts +113 -62
- package/src/chart.ts +149 -64
- package/src/class/parser.ts +84 -28
- package/src/class/renderer.ts +2 -2
- package/src/cli.ts +179 -77
- package/src/completion.ts +381 -182
- package/src/d3.ts +1026 -428
- package/src/dgmo-mermaid.ts +16 -13
- package/src/dgmo-router.ts +70 -24
- package/src/echarts.ts +682 -169
- package/src/editor/dgmo.grammar +69 -0
- package/src/editor/dgmo.grammar.d.ts +2 -0
- package/src/editor/dgmo.grammar.js +18 -0
- package/src/editor/dgmo.grammar.terms.d.ts +5 -0
- package/src/editor/dgmo.grammar.terms.js +35 -0
- package/src/editor/highlight.ts +36 -0
- package/src/editor/index.ts +28 -0
- package/src/editor/keywords.ts +220 -0
- package/src/editor/tokens.ts +30 -0
- package/src/er/parser.ts +55 -29
- package/src/er/renderer.ts +112 -53
- package/src/gantt/calculator.ts +91 -29
- package/src/gantt/parser.ts +291 -97
- package/src/gantt/renderer.ts +1120 -350
- package/src/graph/flowchart-parser.ts +48 -75
- package/src/graph/state-parser.ts +54 -27
- package/src/infra/parser.ts +161 -177
- package/src/infra/renderer.ts +723 -271
- package/src/infra/types.ts +0 -1
- package/src/initiative-status/parser.ts +144 -56
- package/src/kanban/parser.ts +27 -19
- package/src/org/layout.ts +111 -44
- package/src/org/parser.ts +71 -27
- package/src/org/resolver.ts +3 -3
- package/src/palettes/index.ts +3 -2
- package/src/render.ts +1 -2
- package/src/sequence/parser.ts +209 -100
- package/src/sitemap/parser.ts +73 -44
- package/src/utils/arrows.ts +2 -22
- package/src/utils/duration.ts +39 -21
- package/src/utils/legend-constants.ts +0 -2
- package/src/utils/parsing.ts +82 -72
- package/src/utils/tag-groups.ts +4 -41
- package/src/infra/serialize.ts +0 -67
|
@@ -4,22 +4,12 @@ import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
|
|
|
4
4
|
import {
|
|
5
5
|
measureIndent,
|
|
6
6
|
extractColor,
|
|
7
|
-
normalizeDirection,
|
|
8
7
|
inferArrowColor,
|
|
9
8
|
parseFirstLine,
|
|
10
9
|
OPTION_NOCOLON_RE,
|
|
11
|
-
GROUP_HASH_RE,
|
|
12
|
-
DOUBLE_HASH_RE,
|
|
13
10
|
ALL_CHART_TYPES,
|
|
14
11
|
} from '../utils/parsing';
|
|
15
|
-
import type {
|
|
16
|
-
ParsedGraph,
|
|
17
|
-
GraphNode,
|
|
18
|
-
GraphEdge,
|
|
19
|
-
GraphGroup,
|
|
20
|
-
GraphShape,
|
|
21
|
-
GraphDirection,
|
|
22
|
-
} from './types';
|
|
12
|
+
import type { ParsedGraph, GraphNode, GraphEdge, GraphShape } from './types';
|
|
23
13
|
|
|
24
14
|
// ============================================================
|
|
25
15
|
// Helpers
|
|
@@ -40,10 +30,7 @@ interface NodeRef {
|
|
|
40
30
|
* Try to parse a node reference from a text fragment.
|
|
41
31
|
* Order matters: subroutine & document before process.
|
|
42
32
|
*/
|
|
43
|
-
function parseNodeRef(
|
|
44
|
-
text: string,
|
|
45
|
-
palette?: PaletteColors
|
|
46
|
-
): NodeRef | null {
|
|
33
|
+
function parseNodeRef(text: string, palette?: PaletteColors): NodeRef | null {
|
|
47
34
|
const t = text.trim();
|
|
48
35
|
if (!t) return null;
|
|
49
36
|
|
|
@@ -51,7 +38,12 @@ function parseNodeRef(
|
|
|
51
38
|
let m = t.match(/^\[\[([^\]]+)\]\]$/);
|
|
52
39
|
if (m) {
|
|
53
40
|
const { label, color } = extractColor(m[1].trim(), palette);
|
|
54
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
id: nodeId('subroutine', label),
|
|
43
|
+
label,
|
|
44
|
+
shape: 'subroutine',
|
|
45
|
+
color,
|
|
46
|
+
};
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
// Document: [Label~]
|
|
@@ -103,7 +95,12 @@ function splitArrows(line: string): string[] {
|
|
|
103
95
|
const segments: string[] = [];
|
|
104
96
|
let lastIndex = 0;
|
|
105
97
|
// Simpler approach: find all `->` positions, then determine if there's a label prefix
|
|
106
|
-
const arrowPositions: {
|
|
98
|
+
const arrowPositions: {
|
|
99
|
+
start: number;
|
|
100
|
+
end: number;
|
|
101
|
+
label?: string;
|
|
102
|
+
color?: string;
|
|
103
|
+
}[] = [];
|
|
107
104
|
|
|
108
105
|
// Find all -> occurrences
|
|
109
106
|
let searchFrom = 0;
|
|
@@ -128,10 +125,14 @@ function splitArrows(line: string): string[] {
|
|
|
128
125
|
scanBack--;
|
|
129
126
|
}
|
|
130
127
|
// Check if this `-` could be the start of the arrow
|
|
131
|
-
if (
|
|
128
|
+
if (
|
|
129
|
+
line[scanBack] === '-' &&
|
|
130
|
+
(scanBack === 0 || /\s/.test(line[scanBack - 1]))
|
|
131
|
+
) {
|
|
132
132
|
// Content between opening `-` and `->` (strip trailing `-` that is part of `->`)
|
|
133
133
|
let arrowContent = line.substring(scanBack + 1, idx);
|
|
134
|
-
if (arrowContent.endsWith('-'))
|
|
134
|
+
if (arrowContent.endsWith('-'))
|
|
135
|
+
arrowContent = arrowContent.slice(0, -1);
|
|
135
136
|
// Parse label and color from arrow content
|
|
136
137
|
const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
|
|
137
138
|
if (colorMatch) {
|
|
@@ -163,7 +164,8 @@ function splitArrows(line: string): string[] {
|
|
|
163
164
|
}
|
|
164
165
|
// Arrow marker
|
|
165
166
|
let arrowToken = '->';
|
|
166
|
-
if (arrow.label && arrow.color)
|
|
167
|
+
if (arrow.label && arrow.color)
|
|
168
|
+
arrowToken = `-${arrow.label}(${arrow.color})->`;
|
|
167
169
|
else if (arrow.label) arrowToken = `-${arrow.label}->`;
|
|
168
170
|
else if (arrow.color) arrowToken = `-(${arrow.color})->`;
|
|
169
171
|
segments.push(arrowToken);
|
|
@@ -194,7 +196,9 @@ function parseArrowToken(token: string, palette?: PaletteColors): ArrowInfo {
|
|
|
194
196
|
const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
|
|
195
197
|
if (m) {
|
|
196
198
|
const label = m[1]?.trim() || undefined;
|
|
197
|
-
let color = m[2]
|
|
199
|
+
let color = m[2]
|
|
200
|
+
? (resolveColor(m[2].trim(), palette) ?? undefined)
|
|
201
|
+
: undefined;
|
|
198
202
|
if (label && !color) {
|
|
199
203
|
color = inferArrowColor(label);
|
|
200
204
|
}
|
|
@@ -238,11 +242,6 @@ export function parseFlowchart(
|
|
|
238
242
|
let contentStarted = false;
|
|
239
243
|
let firstLineParsed = false;
|
|
240
244
|
|
|
241
|
-
// Group support
|
|
242
|
-
let currentGroup: GraphGroup | null = null;
|
|
243
|
-
let groupIndent = -1;
|
|
244
|
-
const groups: GraphGroup[] = [];
|
|
245
|
-
|
|
246
245
|
function getOrCreateNode(ref: NodeRef, lineNumber: number): GraphNode {
|
|
247
246
|
const existing = nodeMap.get(ref.id);
|
|
248
247
|
if (existing) return existing;
|
|
@@ -253,15 +252,10 @@ export function parseFlowchart(
|
|
|
253
252
|
shape: ref.shape,
|
|
254
253
|
lineNumber,
|
|
255
254
|
...(ref.color && { color: ref.color }),
|
|
256
|
-
...(currentGroup && { group: currentGroup.id }),
|
|
257
255
|
};
|
|
258
256
|
nodeMap.set(ref.id, node);
|
|
259
257
|
result.nodes.push(node);
|
|
260
258
|
|
|
261
|
-
if (currentGroup && !currentGroup.nodeIds.includes(ref.id)) {
|
|
262
|
-
currentGroup.nodeIds.push(ref.id);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
259
|
return node;
|
|
266
260
|
}
|
|
267
261
|
|
|
@@ -418,45 +412,19 @@ export function parseFlowchart(
|
|
|
418
412
|
}
|
|
419
413
|
}
|
|
420
414
|
|
|
421
|
-
// ## group headings — emit helpful error
|
|
422
|
-
if (DOUBLE_HASH_RE.test(trimmed)) {
|
|
423
|
-
result.diagnostics.push(
|
|
424
|
-
makeDgmoError(lineNumber, 'Use `#` for groups \u2014 nesting is done with indentation.', 'error')
|
|
425
|
-
);
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// # GroupName — alternate group notation
|
|
430
|
-
const hashGroupMatch = trimmed.match(GROUP_HASH_RE);
|
|
431
|
-
if (hashGroupMatch) {
|
|
432
|
-
const { label, color } = extractColor(hashGroupMatch[1].trim(), palette);
|
|
433
|
-
currentGroup = {
|
|
434
|
-
id: `group:${label.toLowerCase()}`,
|
|
435
|
-
label,
|
|
436
|
-
nodeIds: [],
|
|
437
|
-
lineNumber,
|
|
438
|
-
...(color && { color }),
|
|
439
|
-
};
|
|
440
|
-
groupIndent = indent;
|
|
441
|
-
groups.push(currentGroup);
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
415
|
// Options (space-separated, before content)
|
|
446
416
|
if (!contentStarted) {
|
|
417
|
+
// Bare boolean: direction-lr
|
|
418
|
+
if (/^direction-lr$/i.test(trimmed)) {
|
|
419
|
+
result.direction = 'LR';
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
|
|
447
423
|
const optMatch = trimmed.match(OPTION_NOCOLON_RE);
|
|
448
424
|
if (optMatch && !trimmed.includes('->')) {
|
|
449
425
|
const key = optMatch[1].toLowerCase();
|
|
450
426
|
const value = optMatch[2].trim();
|
|
451
427
|
|
|
452
|
-
if (key === 'direction' || key === 'orientation') {
|
|
453
|
-
const dir = normalizeDirection(value);
|
|
454
|
-
if (dir) {
|
|
455
|
-
result.direction = dir;
|
|
456
|
-
}
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
428
|
// Boolean: no-color = color off
|
|
461
429
|
if (key === 'no-color') {
|
|
462
430
|
result.options['color'] = 'off';
|
|
@@ -469,21 +437,16 @@ export function parseFlowchart(
|
|
|
469
437
|
}
|
|
470
438
|
}
|
|
471
439
|
|
|
472
|
-
// Close current group when indent returns to or below the group level
|
|
473
|
-
if (currentGroup && indent <= groupIndent) {
|
|
474
|
-
currentGroup = null;
|
|
475
|
-
groupIndent = -1;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
440
|
// Content line (nodes and edges)
|
|
479
441
|
processContentLine(trimmed, lineNumber, indent);
|
|
480
442
|
}
|
|
481
443
|
|
|
482
|
-
if (groups.length > 0) result.groups = groups;
|
|
483
|
-
|
|
484
444
|
// Validation: no nodes found
|
|
485
445
|
if (result.nodes.length === 0 && !result.error) {
|
|
486
|
-
const diag = makeDgmoError(
|
|
446
|
+
const diag = makeDgmoError(
|
|
447
|
+
1,
|
|
448
|
+
'No nodes found. Add flowchart content with shape syntax like [Process] or (Start).'
|
|
449
|
+
);
|
|
487
450
|
result.diagnostics.push(diag);
|
|
488
451
|
result.error = formatDgmoError(diag);
|
|
489
452
|
}
|
|
@@ -497,7 +460,13 @@ export function parseFlowchart(
|
|
|
497
460
|
}
|
|
498
461
|
for (const node of result.nodes) {
|
|
499
462
|
if (!connectedIds.has(node.id)) {
|
|
500
|
-
result.diagnostics.push(
|
|
463
|
+
result.diagnostics.push(
|
|
464
|
+
makeDgmoError(
|
|
465
|
+
node.lineNumber,
|
|
466
|
+
`Node "${node.label}" is not connected to any other node`,
|
|
467
|
+
'warning'
|
|
468
|
+
)
|
|
469
|
+
);
|
|
501
470
|
}
|
|
502
471
|
}
|
|
503
472
|
}
|
|
@@ -533,7 +502,7 @@ export function looksLikeFlowchart(content: string): boolean {
|
|
|
533
502
|
// Look for patterns like `[X] ->` or `-> [X]` or `(X) ->` etc.
|
|
534
503
|
const shapeNearArrow =
|
|
535
504
|
/[\])][ \t]*-.*->/.test(content) || // shape ] or ) followed by arrow
|
|
536
|
-
/->[ \t]*[
|
|
505
|
+
/->[ \t]*[[(</]/.test(content); // arrow followed by shape opener
|
|
537
506
|
|
|
538
507
|
return shapeNearArrow;
|
|
539
508
|
}
|
|
@@ -557,7 +526,11 @@ export function extractSymbols(docText: string): DiagramSymbols {
|
|
|
557
526
|
for (const rawLine of docText.split('\n')) {
|
|
558
527
|
const line = rawLine.trim();
|
|
559
528
|
// Skip old-style colon metadata and new-style space-separated options
|
|
560
|
-
if (
|
|
529
|
+
if (
|
|
530
|
+
inMetadata &&
|
|
531
|
+
(/^[a-z-]+\s*:/i.test(line) || /^[a-z-]+\s+\S/i.test(line))
|
|
532
|
+
)
|
|
533
|
+
continue;
|
|
561
534
|
inMetadata = false;
|
|
562
535
|
if (line.length === 0 || /^\s/.test(rawLine)) continue;
|
|
563
536
|
const m = NODE_ID_RE.exec(line);
|
|
@@ -4,17 +4,11 @@ import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
|
|
|
4
4
|
import {
|
|
5
5
|
measureIndent,
|
|
6
6
|
extractColor,
|
|
7
|
-
normalizeDirection,
|
|
8
7
|
parseFirstLine,
|
|
9
8
|
OPTION_NOCOLON_RE,
|
|
10
9
|
ALL_CHART_TYPES,
|
|
11
10
|
} from '../utils/parsing';
|
|
12
|
-
import type {
|
|
13
|
-
ParsedGraph,
|
|
14
|
-
GraphNode,
|
|
15
|
-
GraphGroup,
|
|
16
|
-
GraphDirection,
|
|
17
|
-
} from './types';
|
|
11
|
+
import type { ParsedGraph, GraphNode, GraphGroup } from './types';
|
|
18
12
|
|
|
19
13
|
// ============================================================
|
|
20
14
|
// Constants
|
|
@@ -37,7 +31,12 @@ const GROUP_BRACKET_RE = /^\[([^\]]+)\](?:\(([^)]+)\))?\s*$/;
|
|
|
37
31
|
*/
|
|
38
32
|
function splitArrows(line: string): string[] {
|
|
39
33
|
const segments: string[] = [];
|
|
40
|
-
const arrowPositions: {
|
|
34
|
+
const arrowPositions: {
|
|
35
|
+
start: number;
|
|
36
|
+
end: number;
|
|
37
|
+
label?: string;
|
|
38
|
+
color?: string;
|
|
39
|
+
}[] = [];
|
|
41
40
|
|
|
42
41
|
let searchFrom = 0;
|
|
43
42
|
while (searchFrom < line.length) {
|
|
@@ -53,9 +52,13 @@ function splitArrows(line: string): string[] {
|
|
|
53
52
|
while (scanBack > 0 && line[scanBack] !== '-') {
|
|
54
53
|
scanBack--;
|
|
55
54
|
}
|
|
56
|
-
if (
|
|
55
|
+
if (
|
|
56
|
+
line[scanBack] === '-' &&
|
|
57
|
+
(scanBack === 0 || /\s/.test(line[scanBack - 1]))
|
|
58
|
+
) {
|
|
57
59
|
let arrowContent = line.substring(scanBack + 1, idx);
|
|
58
|
-
if (arrowContent.endsWith('-'))
|
|
60
|
+
if (arrowContent.endsWith('-'))
|
|
61
|
+
arrowContent = arrowContent.slice(0, -1);
|
|
59
62
|
const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
|
|
60
63
|
if (colorMatch) {
|
|
61
64
|
color = colorMatch[1].trim();
|
|
@@ -82,7 +85,8 @@ function splitArrows(line: string): string[] {
|
|
|
82
85
|
if (beforeText || i === 0) segments.push(beforeText);
|
|
83
86
|
|
|
84
87
|
let arrowToken = '->';
|
|
85
|
-
if (arrow.label && arrow.color)
|
|
88
|
+
if (arrow.label && arrow.color)
|
|
89
|
+
arrowToken = `-${arrow.label}(${arrow.color})->`;
|
|
86
90
|
else if (arrow.label) arrowToken = `-${arrow.label}->`;
|
|
87
91
|
else if (arrow.color) arrowToken = `-(${arrow.color})->`;
|
|
88
92
|
segments.push(arrowToken);
|
|
@@ -102,11 +106,14 @@ interface ArrowInfo {
|
|
|
102
106
|
function parseArrowToken(token: string, palette?: PaletteColors): ArrowInfo {
|
|
103
107
|
if (token === '->') return {};
|
|
104
108
|
const colorOnly = token.match(/^-\(([^)]+)\)->$/);
|
|
105
|
-
if (colorOnly)
|
|
109
|
+
if (colorOnly)
|
|
110
|
+
return { color: resolveColor(colorOnly[1].trim(), palette) ?? undefined };
|
|
106
111
|
const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
|
|
107
112
|
if (m) {
|
|
108
113
|
const label = m[1]?.trim() || undefined;
|
|
109
|
-
const color = m[2]
|
|
114
|
+
const color = m[2]
|
|
115
|
+
? (resolveColor(m[2].trim(), palette) ?? undefined)
|
|
116
|
+
: undefined;
|
|
110
117
|
return { label, color };
|
|
111
118
|
}
|
|
112
119
|
return {};
|
|
@@ -123,13 +130,20 @@ interface NodeRef {
|
|
|
123
130
|
color?: string;
|
|
124
131
|
}
|
|
125
132
|
|
|
126
|
-
function parseStateNodeRef(
|
|
133
|
+
function parseStateNodeRef(
|
|
134
|
+
text: string,
|
|
135
|
+
palette?: PaletteColors
|
|
136
|
+
): NodeRef | null {
|
|
127
137
|
const t = text.trim();
|
|
128
138
|
if (!t) return null;
|
|
129
139
|
|
|
130
140
|
// Pseudostate: [*]
|
|
131
141
|
if (t === '[*]') {
|
|
132
|
-
return {
|
|
142
|
+
return {
|
|
143
|
+
id: PSEUDOSTATE_ID,
|
|
144
|
+
label: PSEUDOSTATE_LABEL,
|
|
145
|
+
shape: 'pseudostate',
|
|
146
|
+
};
|
|
133
147
|
}
|
|
134
148
|
|
|
135
149
|
// State: bare text with optional (color) suffix
|
|
@@ -154,7 +168,7 @@ export function parseState(
|
|
|
154
168
|
const lines = content.split('\n');
|
|
155
169
|
const result: ParsedGraph = {
|
|
156
170
|
type: 'state',
|
|
157
|
-
direction: '
|
|
171
|
+
direction: 'LR',
|
|
158
172
|
nodes: [],
|
|
159
173
|
edges: [],
|
|
160
174
|
options: {},
|
|
@@ -268,19 +282,17 @@ export function parseState(
|
|
|
268
282
|
|
|
269
283
|
// Options (space-separated, before content)
|
|
270
284
|
if (!contentStarted) {
|
|
285
|
+
// Bare boolean: direction-tb
|
|
286
|
+
if (/^direction-tb$/i.test(trimmed)) {
|
|
287
|
+
result.direction = 'TB';
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
271
291
|
const optMatch = trimmed.match(OPTION_NOCOLON_RE);
|
|
272
292
|
if (optMatch && !trimmed.includes('->')) {
|
|
273
293
|
const key = optMatch[1].toLowerCase();
|
|
274
294
|
const value = optMatch[2].trim();
|
|
275
295
|
|
|
276
|
-
if (key === 'direction' || key === 'orientation') {
|
|
277
|
-
const dir = normalizeDirection(value);
|
|
278
|
-
if (dir) {
|
|
279
|
-
result.direction = dir;
|
|
280
|
-
}
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
296
|
// Boolean: no-color = color off
|
|
285
297
|
if (key === 'no-color') {
|
|
286
298
|
result.options['color'] = 'off';
|
|
@@ -353,7 +365,13 @@ export function parseState(
|
|
|
353
365
|
// Use explicit source if available, else implicit from indent
|
|
354
366
|
const sourceId = lastNodeId ?? implicitSourceId;
|
|
355
367
|
if (sourceId) {
|
|
356
|
-
addEdge(
|
|
368
|
+
addEdge(
|
|
369
|
+
sourceId,
|
|
370
|
+
node.id,
|
|
371
|
+
lineNumber,
|
|
372
|
+
pendingArrow.label,
|
|
373
|
+
pendingArrow.color
|
|
374
|
+
);
|
|
357
375
|
}
|
|
358
376
|
pendingArrow = null;
|
|
359
377
|
}
|
|
@@ -370,7 +388,10 @@ export function parseState(
|
|
|
370
388
|
|
|
371
389
|
// Validation: no nodes found
|
|
372
390
|
if (result.nodes.length === 0 && !result.error) {
|
|
373
|
-
const diag = makeDgmoError(
|
|
391
|
+
const diag = makeDgmoError(
|
|
392
|
+
1,
|
|
393
|
+
'No states found. Add state transitions like: Idle -> Active'
|
|
394
|
+
);
|
|
374
395
|
result.diagnostics.push(diag);
|
|
375
396
|
result.error = formatDgmoError(diag);
|
|
376
397
|
}
|
|
@@ -384,7 +405,13 @@ export function parseState(
|
|
|
384
405
|
}
|
|
385
406
|
for (const node of result.nodes) {
|
|
386
407
|
if (!connectedIds.has(node.id)) {
|
|
387
|
-
result.diagnostics.push(
|
|
408
|
+
result.diagnostics.push(
|
|
409
|
+
makeDgmoError(
|
|
410
|
+
node.lineNumber,
|
|
411
|
+
`State "${node.label}" is not connected to any other state`,
|
|
412
|
+
'warning'
|
|
413
|
+
)
|
|
414
|
+
);
|
|
388
415
|
}
|
|
389
416
|
}
|
|
390
417
|
}
|