@diagrammo/dgmo 0.15.1 → 0.16.0
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.md +9 -9
- package/dist/advanced.cjs +479 -454
- package/dist/advanced.d.cts +34 -35
- package/dist/advanced.d.ts +34 -35
- package/dist/advanced.js +479 -453
- package/dist/auto.cjs +374 -352
- package/dist/auto.js +103 -103
- package/dist/auto.mjs +374 -352
- package/dist/cli.cjs +140 -140
- package/dist/editor.cjs +8 -9
- package/dist/editor.js +8 -9
- package/dist/highlight.cjs +8 -9
- package/dist/highlight.js +8 -9
- package/dist/index.cjs +365 -342
- package/dist/index.js +365 -342
- package/dist/internal.cjs +479 -454
- package/dist/internal.d.cts +34 -35
- package/dist/internal.d.ts +34 -35
- package/dist/internal.js +479 -453
- package/dist/pert.d.cts +2 -2
- package/dist/pert.d.ts +2 -2
- package/docs/language-reference.md +83 -66
- package/gallery/fixtures/area.dgmo +3 -3
- package/gallery/fixtures/bar-stacked.dgmo +5 -5
- package/gallery/fixtures/boxes-and-lines.dgmo +2 -2
- package/gallery/fixtures/c4-full.dgmo +8 -8
- package/gallery/fixtures/class-full.dgmo +2 -2
- package/gallery/fixtures/doughnut.dgmo +6 -6
- package/gallery/fixtures/flowchart-colors.dgmo +3 -3
- package/gallery/fixtures/function.dgmo +3 -3
- package/gallery/fixtures/gantt-full.dgmo +9 -9
- package/gallery/fixtures/gantt.dgmo +7 -7
- package/gallery/fixtures/infra-full.dgmo +6 -6
- package/gallery/fixtures/infra.dgmo +2 -2
- package/gallery/fixtures/kanban.dgmo +9 -9
- package/gallery/fixtures/line.dgmo +2 -2
- package/gallery/fixtures/multi-line.dgmo +3 -3
- package/gallery/fixtures/org-full.dgmo +6 -6
- package/gallery/fixtures/quadrant.dgmo +2 -2
- package/gallery/fixtures/sankey.dgmo +9 -9
- package/gallery/fixtures/scatter.dgmo +3 -3
- package/gallery/fixtures/sequence-tags-protocols.dgmo +8 -8
- package/gallery/fixtures/sequence-tags.dgmo +7 -7
- package/gallery/fixtures/sitemap-full.dgmo +7 -7
- package/gallery/fixtures/slope.dgmo +5 -5
- package/gallery/fixtures/spr-eras.dgmo +9 -9
- package/gallery/fixtures/timeline.dgmo +3 -3
- package/gallery/fixtures/venn.dgmo +3 -3
- package/package.json +1 -1
- package/src/advanced.ts +0 -1
- package/src/boxes-and-lines/renderer.ts +5 -1
- package/src/c4/parser.ts +1 -1
- package/src/c4/renderer.ts +15 -8
- package/src/chart.ts +18 -9
- package/src/class/parser.ts +7 -6
- package/src/class/renderer.ts +17 -6
- package/src/cli.ts +6 -6
- package/src/completion.ts +13 -3
- package/src/cycle/parser.ts +14 -0
- package/src/cycle/renderer.ts +6 -3
- package/src/d3.ts +86 -46
- package/src/echarts.ts +26 -9
- package/src/editor/dgmo.grammar +1 -3
- package/src/editor/dgmo.grammar.js +8 -8
- package/src/editor/dgmo.grammar.terms.js +11 -12
- package/src/editor/highlight-api.ts +0 -1
- package/src/editor/highlight.ts +0 -1
- package/src/er/parser.ts +18 -11
- package/src/er/renderer.ts +19 -7
- package/src/gantt/parser.ts +1 -1
- package/src/gantt/renderer.ts +7 -4
- package/src/graph/flowchart-parser.ts +18 -84
- package/src/graph/flowchart-renderer.ts +3 -8
- package/src/graph/layout.ts +0 -2
- package/src/graph/state-parser.ts +17 -62
- package/src/graph/state-renderer.ts +3 -8
- package/src/infra/parser.ts +21 -11
- package/src/infra/renderer.ts +7 -4
- package/src/journey-map/parser.ts +10 -3
- package/src/journey-map/renderer.ts +3 -1
- package/src/kanban/parser.ts +10 -6
- package/src/kanban/renderer.ts +3 -1
- package/src/mindmap/parser.ts +2 -2
- package/src/mindmap/renderer.ts +2 -1
- package/src/org/parser.ts +2 -2
- package/src/org/renderer.ts +4 -3
- package/src/pert/parser.ts +7 -7
- package/src/pert/renderer.ts +7 -2
- package/src/pert/types.ts +1 -1
- package/src/pyramid/parser.ts +12 -0
- package/src/raci/parser.ts +40 -10
- package/src/raci/renderer.ts +2 -1
- package/src/raci/types.ts +4 -3
- package/src/ring/parser.ts +12 -0
- package/src/sequence/parser.ts +15 -9
- package/src/sequence/renderer.ts +1 -1
- package/src/sitemap/layout.ts +0 -2
- package/src/sitemap/parser.ts +11 -37
- package/src/sitemap/renderer.ts +13 -13
- package/src/sitemap/types.ts +0 -1
- package/src/tech-radar/renderer.ts +5 -3
- package/src/tech-radar/types.ts +2 -0
- package/src/utils/arrows.ts +3 -28
- package/src/utils/legend-d3.ts +12 -6
- package/src/utils/legend-layout.ts +1 -1
- package/src/utils/legend-types.ts +1 -1
- package/src/utils/parsing.ts +64 -35
- package/src/utils/tag-groups.ts +98 -18
- package/src/wireframe/parser.ts +2 -2
package/src/er/parser.ts
CHANGED
|
@@ -46,15 +46,15 @@ function tableId(name: string): string {
|
|
|
46
46
|
// Regex patterns
|
|
47
47
|
// ============================================================
|
|
48
48
|
|
|
49
|
-
// Table declaration: name
|
|
50
|
-
// Multi-word names allowed; quote `"name with reserved
|
|
51
|
-
// contains pipe / paren / colon. Captures:
|
|
49
|
+
// Table declaration: `name`, `name color` (trailing-token §1.5), or
|
|
50
|
+
// `name | key: value`. Multi-word names allowed; quote `"name with reserved
|
|
51
|
+
// chars"` if the name contains pipe / paren / colon. Captures:
|
|
52
52
|
// 1: quoted-name content (without surrounding quotes), or undefined
|
|
53
53
|
// 2: bare-name (trimmed at call site), or undefined
|
|
54
|
-
// 3: color (
|
|
54
|
+
// 3: trailing-token color (recognized palette word), or undefined
|
|
55
55
|
// 4: pipe metadata (without leading `|`), or undefined
|
|
56
56
|
const TABLE_DECL_RE =
|
|
57
|
-
/^(?:"([^"]+)"|([a-zA-Z_][^|":(]*?))(?:\s
|
|
57
|
+
/^(?:"([^"]+)"|([a-zA-Z_][^|":(]*?))(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?(?:\s*\|(.+))?$/;
|
|
58
58
|
|
|
59
59
|
// Column: name [type] [constraints...] — space-separated, no colon, no brackets
|
|
60
60
|
// First token is always the name. Second token is the type if it's not a constraint keyword.
|
|
@@ -350,7 +350,7 @@ export function parseERDiagram(
|
|
|
350
350
|
result.diagnostics.push(
|
|
351
351
|
makeDgmoError(
|
|
352
352
|
lineNumber,
|
|
353
|
-
`Expected 'Value
|
|
353
|
+
`Expected 'Value color' in tag group '${currentTagGroup.name}'`,
|
|
354
354
|
'warning'
|
|
355
355
|
)
|
|
356
356
|
);
|
|
@@ -494,7 +494,7 @@ export function parseERDiagram(
|
|
|
494
494
|
if (result.tables.length === 0 && !result.error) {
|
|
495
495
|
const diag = makeDgmoError(
|
|
496
496
|
1,
|
|
497
|
-
'No tables found. Add table declarations like "users" or "orders
|
|
497
|
+
'No tables found. Add table declarations like "users" or "orders blue".'
|
|
498
498
|
);
|
|
499
499
|
result.diagnostics.push(diag);
|
|
500
500
|
result.error = formatDgmoError(diag);
|
|
@@ -632,15 +632,22 @@ export function extractSymbols(docText: string): DiagramSymbols {
|
|
|
632
632
|
for (const rawLine of docText.split('\n')) {
|
|
633
633
|
const line = rawLine.trim();
|
|
634
634
|
if (inMetadata && /^er(\s|$)/i.test(line)) continue;
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
635
|
+
// Under §1.5 trailing-token, `Users blue` matches OPTION_NOCOLON_RE
|
|
636
|
+
// (key=Users, value=blue) but is actually a table with a color.
|
|
637
|
+
// Detect tables FIRST so they aren't swallowed by the option fallback.
|
|
638
638
|
if (/^\s/.test(rawLine)) continue; // indented = column definition, not table
|
|
639
|
+
if (line.length === 0) continue;
|
|
639
640
|
const m = TABLE_DECL_RE.exec(line);
|
|
640
641
|
if (m) {
|
|
641
642
|
const name = (m[1] ?? m[2] ?? '').trim();
|
|
642
|
-
if (name)
|
|
643
|
+
if (name) {
|
|
644
|
+
inMetadata = false;
|
|
645
|
+
entities.push(name);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
643
648
|
}
|
|
649
|
+
if (inMetadata && OPTION_NOCOLON_RE.test(line)) continue; // option line
|
|
650
|
+
inMetadata = false;
|
|
644
651
|
}
|
|
645
652
|
return {
|
|
646
653
|
kind: 'er',
|
package/src/er/renderer.ts
CHANGED
|
@@ -225,7 +225,8 @@ export function renderERDiagram(
|
|
|
225
225
|
exportDims?: { width?: number; height?: number },
|
|
226
226
|
activeTagGroup?: string | null,
|
|
227
227
|
/** When false, semantic role colors are suppressed and entities use a neutral color. */
|
|
228
|
-
semanticColorsActive?: boolean
|
|
228
|
+
semanticColorsActive?: boolean,
|
|
229
|
+
exportMode?: boolean
|
|
229
230
|
): void {
|
|
230
231
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
231
232
|
|
|
@@ -560,7 +561,7 @@ export function renderERDiagram(
|
|
|
560
561
|
const legendConfig: LegendConfig = {
|
|
561
562
|
groups: parsed.tagGroups,
|
|
562
563
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
563
|
-
mode: '
|
|
564
|
+
mode: exportMode ? 'export' : 'preview',
|
|
564
565
|
};
|
|
565
566
|
const legendState: LegendState = { activeGroup: activeTagGroup ?? null };
|
|
566
567
|
const legendG = svg
|
|
@@ -602,7 +603,7 @@ export function renderERDiagram(
|
|
|
602
603
|
const legendConfig: LegendConfig = {
|
|
603
604
|
groups: semanticGroups,
|
|
604
605
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
605
|
-
mode: '
|
|
606
|
+
mode: exportMode ? 'export' : 'preview',
|
|
606
607
|
};
|
|
607
608
|
const legendState: LegendState = {
|
|
608
609
|
activeGroup: semanticActive ? 'Role' : null,
|
|
@@ -653,10 +654,21 @@ export function renderERDiagramForExport(
|
|
|
653
654
|
document.body.appendChild(container);
|
|
654
655
|
|
|
655
656
|
try {
|
|
656
|
-
renderERDiagram(
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
657
|
+
renderERDiagram(
|
|
658
|
+
container,
|
|
659
|
+
parsed,
|
|
660
|
+
layout,
|
|
661
|
+
palette,
|
|
662
|
+
isDark,
|
|
663
|
+
undefined,
|
|
664
|
+
{
|
|
665
|
+
width: exportWidth,
|
|
666
|
+
height: exportHeight,
|
|
667
|
+
},
|
|
668
|
+
undefined,
|
|
669
|
+
undefined,
|
|
670
|
+
true
|
|
671
|
+
);
|
|
660
672
|
|
|
661
673
|
const svgEl = container.querySelector('svg');
|
|
662
674
|
if (!svgEl) return '';
|
package/src/gantt/parser.ts
CHANGED
|
@@ -380,7 +380,7 @@ export function parseGantt(
|
|
|
380
380
|
currentTagGroup = null;
|
|
381
381
|
// fall through to process this line normally
|
|
382
382
|
} else {
|
|
383
|
-
// Parse tag entry: `Value
|
|
383
|
+
// Parse tag entry: `Value color` or `Value`
|
|
384
384
|
// First entry is the default unless another is marked `default`
|
|
385
385
|
if (COMMENT_RE.test(line)) continue;
|
|
386
386
|
const { text: cleanEntry, isDefault } = stripDefaultModifier(line);
|
package/src/gantt/renderer.ts
CHANGED
|
@@ -208,6 +208,7 @@ export interface GanttInteractiveOptions {
|
|
|
208
208
|
collapsedLanes?: Set<string>;
|
|
209
209
|
onToggleLane?: (laneName: string) => void;
|
|
210
210
|
viewMode?: boolean;
|
|
211
|
+
exportMode?: boolean;
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
// ── Main Renderer ───────────────────────────────────────────
|
|
@@ -439,7 +440,8 @@ export function renderGantt(
|
|
|
439
440
|
).attr('display', active ? null : 'none');
|
|
440
441
|
}
|
|
441
442
|
drawLegend();
|
|
442
|
-
}
|
|
443
|
+
},
|
|
444
|
+
options?.exportMode ?? false
|
|
443
445
|
);
|
|
444
446
|
}
|
|
445
447
|
}
|
|
@@ -1983,7 +1985,8 @@ function renderTagLegend(
|
|
|
1983
1985
|
controlsExpanded = false,
|
|
1984
1986
|
hasDependencies = false,
|
|
1985
1987
|
dependenciesActive = false,
|
|
1986
|
-
onControlsToggle?: (toggleId: string, active: boolean) => void
|
|
1988
|
+
onControlsToggle?: (toggleId: string, active: boolean) => void,
|
|
1989
|
+
exportMode = false
|
|
1987
1990
|
): void {
|
|
1988
1991
|
// Build visible groups: active group expanded + swimlane group as compact pill
|
|
1989
1992
|
let visibleGroups: TagGroup[];
|
|
@@ -2117,7 +2120,7 @@ function renderTagLegend(
|
|
|
2117
2120
|
placement: 'top-center' as const,
|
|
2118
2121
|
titleRelation: 'below-title' as const,
|
|
2119
2122
|
},
|
|
2120
|
-
mode: '
|
|
2123
|
+
mode: exportMode ? 'export' : 'preview',
|
|
2121
2124
|
capsulePillAddonWidth: iconReserve,
|
|
2122
2125
|
controlsGroup:
|
|
2123
2126
|
controlsToggles.length > 0 ? { toggles: controlsToggles } : undefined,
|
|
@@ -2263,7 +2266,7 @@ function renderTagLegend(
|
|
|
2263
2266
|
placement: 'top-center' as const,
|
|
2264
2267
|
titleRelation: 'below-title' as const,
|
|
2265
2268
|
},
|
|
2266
|
-
mode: '
|
|
2269
|
+
mode: exportMode ? 'export' : 'preview',
|
|
2267
2270
|
controlsGroup: { toggles: controlsToggles },
|
|
2268
2271
|
};
|
|
2269
2272
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { resolveColorWithDiagnostic } from '../colors';
|
|
2
1
|
import type { DgmoError } from '../diagnostics';
|
|
3
2
|
import type { PaletteColors } from '../palettes';
|
|
4
3
|
import {
|
|
@@ -8,10 +7,9 @@ import {
|
|
|
8
7
|
NAME_DIAGNOSTIC_CODES,
|
|
9
8
|
nameMergedMessage,
|
|
10
9
|
} from '../diagnostics';
|
|
11
|
-
import { parseInArrowLabel
|
|
10
|
+
import { parseInArrowLabel } from '../utils/arrows';
|
|
12
11
|
import {
|
|
13
12
|
measureIndent,
|
|
14
|
-
inferArrowColor,
|
|
15
13
|
parseFirstLine,
|
|
16
14
|
OPTION_NOCOLON_RE,
|
|
17
15
|
ALL_CHART_TYPES,
|
|
@@ -90,9 +88,8 @@ function parseNodeRef(text: string): NodeRef | null {
|
|
|
90
88
|
|
|
91
89
|
/**
|
|
92
90
|
* Split a line into segments around arrow tokens.
|
|
93
|
-
* Arrows: `->`, `-label->`,
|
|
94
|
-
*
|
|
95
|
-
* token is the maximal run of `-+>`).
|
|
91
|
+
* Arrows: `->`, `-label->`, and long-dash variants like `-->`, `--->`,
|
|
92
|
+
* `--foo--->` (TD-9 longest-match: the arrow token is the maximal run of `-+>`).
|
|
96
93
|
*
|
|
97
94
|
* Returns alternating: [nodeText, arrowText, nodeText, arrowText, nodeText, ...]
|
|
98
95
|
* Where arrowText is the synthesized full arrow token like `-yes->` or `->`
|
|
@@ -105,7 +102,6 @@ function splitArrows(line: string): string[] {
|
|
|
105
102
|
start: number;
|
|
106
103
|
end: number;
|
|
107
104
|
label?: string;
|
|
108
|
-
color?: string;
|
|
109
105
|
}[] = [];
|
|
110
106
|
|
|
111
107
|
// Find all arrow tokens. A token is a maximal run of `-+>` (one-or-more
|
|
@@ -132,7 +128,6 @@ function splitArrows(line: string): string[] {
|
|
|
132
128
|
// the label; the full arrow token runs from opening through `>`.
|
|
133
129
|
let arrowStart: number;
|
|
134
130
|
let label: string | undefined;
|
|
135
|
-
let color: string | undefined;
|
|
136
131
|
|
|
137
132
|
let openingStart = -1;
|
|
138
133
|
for (let i = scanFloor; i < runStart; i++) {
|
|
@@ -151,16 +146,9 @@ function splitArrows(line: string): string[] {
|
|
|
151
146
|
while (openingEnd < runStart && line[openingEnd] === '-') openingEnd++;
|
|
152
147
|
|
|
153
148
|
// Label content = everything between opening run and the arrow run.
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
157
|
-
color = colorMatch[1].trim();
|
|
158
|
-
const labelPart = arrowContent.substring(0, colorMatch.index!).trim();
|
|
159
|
-
if (labelPart) label = labelPart;
|
|
160
|
-
} else {
|
|
161
|
-
const labelPart = arrowContent.trim();
|
|
162
|
-
if (labelPart) label = labelPart;
|
|
163
|
-
}
|
|
149
|
+
// Edges have no color slot (§1.7); parens stay literal.
|
|
150
|
+
const labelPart = line.substring(openingEnd, runStart).trim();
|
|
151
|
+
if (labelPart) label = labelPart;
|
|
164
152
|
arrowStart = openingStart;
|
|
165
153
|
} else {
|
|
166
154
|
// No opening dash run found. All absorbed leftward dashes belong to
|
|
@@ -168,7 +156,7 @@ function splitArrows(line: string): string[] {
|
|
|
168
156
|
arrowStart = runStart;
|
|
169
157
|
}
|
|
170
158
|
|
|
171
|
-
arrowPositions.push({ start: arrowStart, end: arrowEnd, label
|
|
159
|
+
arrowPositions.push({ start: arrowStart, end: arrowEnd, label });
|
|
172
160
|
searchFrom = arrowEnd;
|
|
173
161
|
scanFloor = arrowEnd;
|
|
174
162
|
}
|
|
@@ -179,12 +167,9 @@ function splitArrows(line: string): string[] {
|
|
|
179
167
|
|
|
180
168
|
// Build segments.
|
|
181
169
|
//
|
|
182
|
-
// NOTE: the synthesized arrow token is always the short form (
|
|
183
|
-
// `-label
|
|
184
|
-
//
|
|
185
|
-
// dash-length-sensitive edge styling (e.g. Mermaid-style "long arrow"
|
|
186
|
-
// emphasis), thread `arrow.end - arrow.start - label?.length - color?.length`
|
|
187
|
-
// through to ArrowInfo so downstream renderers can honor it.
|
|
170
|
+
// NOTE: the synthesized arrow token is always the short form (`->` or
|
|
171
|
+
// `-label->`). The actual dash run-length (`-->`, `--->`, `---->`) seen
|
|
172
|
+
// in the source is collapsed here.
|
|
188
173
|
let lastIndex = 0;
|
|
189
174
|
for (let i = 0; i < arrowPositions.length; i++) {
|
|
190
175
|
const arrow = arrowPositions[i];
|
|
@@ -192,12 +177,7 @@ function splitArrows(line: string): string[] {
|
|
|
192
177
|
if (beforeText || i === 0) {
|
|
193
178
|
segments.push(beforeText);
|
|
194
179
|
}
|
|
195
|
-
|
|
196
|
-
let arrowToken = '->';
|
|
197
|
-
if (arrow.label && arrow.color)
|
|
198
|
-
arrowToken = `-${arrow.label}(${arrow.color})->`;
|
|
199
|
-
else if (arrow.label) arrowToken = `-${arrow.label}->`;
|
|
200
|
-
else if (arrow.color) arrowToken = `-(${arrow.color})->`;
|
|
180
|
+
const arrowToken = arrow.label ? `-${arrow.label}->` : '->';
|
|
201
181
|
segments.push(arrowToken);
|
|
202
182
|
lastIndex = arrow.end;
|
|
203
183
|
}
|
|
@@ -212,61 +192,23 @@ function splitArrows(line: string): string[] {
|
|
|
212
192
|
|
|
213
193
|
interface ArrowInfo {
|
|
214
194
|
label?: string;
|
|
215
|
-
color?: string;
|
|
216
195
|
}
|
|
217
196
|
|
|
218
197
|
function parseArrowToken(
|
|
219
198
|
token: string,
|
|
220
|
-
|
|
199
|
+
_palette: PaletteColors | undefined,
|
|
221
200
|
lineNumber: number,
|
|
222
201
|
diagnostics: DgmoError[]
|
|
223
202
|
): ArrowInfo {
|
|
224
203
|
if (token === '->') return {};
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
const bareParen = token.match(/^-(\([A-Za-z]+\))->$/);
|
|
229
|
-
if (bareParen) {
|
|
230
|
-
const colorName = matchColorParens(bareParen[1]);
|
|
231
|
-
if (colorName) {
|
|
232
|
-
return {
|
|
233
|
-
color: resolveColorWithDiagnostic(
|
|
234
|
-
colorName,
|
|
235
|
-
lineNumber,
|
|
236
|
-
diagnostics,
|
|
237
|
-
palette
|
|
238
|
-
),
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
// Unrecognized color name → whole `(X)` is the label (fall through).
|
|
242
|
-
}
|
|
243
|
-
// -label(color)-> or -label->
|
|
244
|
-
const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
|
|
204
|
+
// Edges have no color slot (spec §1.7 "Edge color is not a feature"). The
|
|
205
|
+
// whole content between `-` and `->` is the label, including parens.
|
|
206
|
+
const m = token.match(/^-(.+?)->$/);
|
|
245
207
|
if (m) {
|
|
246
208
|
const rawLabel = m[1] ?? '';
|
|
247
|
-
// Route label through TD-13/TD-14 validator.
|
|
248
209
|
const labelResult = parseInArrowLabel(rawLabel, lineNumber);
|
|
249
210
|
diagnostics.push(...labelResult.diagnostics);
|
|
250
|
-
|
|
251
|
-
let color = m[2]
|
|
252
|
-
? resolveColorWithDiagnostic(
|
|
253
|
-
m[2].trim(),
|
|
254
|
-
lineNumber,
|
|
255
|
-
diagnostics,
|
|
256
|
-
palette
|
|
257
|
-
)
|
|
258
|
-
: undefined;
|
|
259
|
-
if (label && !color) {
|
|
260
|
-
const inferred = inferArrowColor(label);
|
|
261
|
-
if (inferred)
|
|
262
|
-
color = resolveColorWithDiagnostic(
|
|
263
|
-
inferred,
|
|
264
|
-
lineNumber,
|
|
265
|
-
diagnostics,
|
|
266
|
-
palette
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
return { label, color };
|
|
211
|
+
return { label: labelResult.label };
|
|
270
212
|
}
|
|
271
213
|
return {};
|
|
272
214
|
}
|
|
@@ -362,15 +304,13 @@ export function parseFlowchart(
|
|
|
362
304
|
sourceId: string,
|
|
363
305
|
targetId: string,
|
|
364
306
|
lineNumber: number,
|
|
365
|
-
label?: string
|
|
366
|
-
color?: string
|
|
307
|
+
label?: string
|
|
367
308
|
): void {
|
|
368
309
|
const edge: GraphEdge = {
|
|
369
310
|
source: sourceId,
|
|
370
311
|
target: targetId,
|
|
371
312
|
lineNumber,
|
|
372
313
|
...(label && { label }),
|
|
373
|
-
...(color && { color }),
|
|
374
314
|
};
|
|
375
315
|
result.edges.push(edge);
|
|
376
316
|
}
|
|
@@ -471,13 +411,7 @@ export function parseFlowchart(
|
|
|
471
411
|
if (pendingArrow !== null) {
|
|
472
412
|
const sourceId = lastNodeId ?? implicitSourceId;
|
|
473
413
|
if (sourceId) {
|
|
474
|
-
addEdge(
|
|
475
|
-
sourceId,
|
|
476
|
-
node.id,
|
|
477
|
-
lineNumber,
|
|
478
|
-
pendingArrow.label,
|
|
479
|
-
pendingArrow.color
|
|
480
|
-
);
|
|
414
|
+
addEdge(sourceId, node.id, lineNumber, pendingArrow.label);
|
|
481
415
|
}
|
|
482
416
|
pendingArrow = null;
|
|
483
417
|
} else if (lastNodeId === null && implicitSourceId === null) {
|
|
@@ -447,11 +447,8 @@ export function renderFlowchart(
|
|
|
447
447
|
.attr('points', `0,0 ${ARROWHEAD_W},${ARROWHEAD_H / 2} 0,${ARROWHEAD_H}`)
|
|
448
448
|
.attr('fill', palette.textMuted);
|
|
449
449
|
|
|
450
|
-
//
|
|
450
|
+
// Edges have no color slot (§1.7); keep empty set for marker iteration.
|
|
451
451
|
const edgeColors = new Set<string>();
|
|
452
|
-
for (const edge of layout.edges) {
|
|
453
|
-
if (edge.color) edgeColors.add(edge.color);
|
|
454
|
-
}
|
|
455
452
|
for (const color of edgeColors) {
|
|
456
453
|
const id = `fc-arrow-${color.replace('#', '')}`;
|
|
457
454
|
defs
|
|
@@ -569,10 +566,8 @@ export function renderFlowchart(
|
|
|
569
566
|
.attr('class', 'fc-edge-group')
|
|
570
567
|
.attr('data-line-number', String(edge.lineNumber));
|
|
571
568
|
|
|
572
|
-
const edgeColor =
|
|
573
|
-
const markerId =
|
|
574
|
-
? `fc-arrow-${edge.color.replace('#', '')}`
|
|
575
|
-
: 'fc-arrow';
|
|
569
|
+
const edgeColor = palette.textMuted;
|
|
570
|
+
const markerId = 'fc-arrow';
|
|
576
571
|
|
|
577
572
|
const pathD = lineGenerator(edge.points);
|
|
578
573
|
if (pathD) {
|
package/src/graph/layout.ts
CHANGED
|
@@ -25,7 +25,6 @@ export interface LayoutEdge {
|
|
|
25
25
|
target: string;
|
|
26
26
|
points: { x: number; y: number }[];
|
|
27
27
|
label?: string;
|
|
28
|
-
color?: string;
|
|
29
28
|
lineNumber: number;
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -179,7 +178,6 @@ export function layoutGraph(
|
|
|
179
178
|
target: edge.target,
|
|
180
179
|
points: edgeData?.points ?? [],
|
|
181
180
|
label: edge.label,
|
|
182
|
-
color: edge.color,
|
|
183
181
|
lineNumber: edge.lineNumber,
|
|
184
182
|
};
|
|
185
183
|
});
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
NAME_DIAGNOSTIC_CODES,
|
|
9
9
|
nameMergedMessage,
|
|
10
10
|
} from '../diagnostics';
|
|
11
|
-
import { parseInArrowLabel
|
|
11
|
+
import { parseInArrowLabel } from '../utils/arrows';
|
|
12
12
|
import {
|
|
13
13
|
measureIndent,
|
|
14
14
|
parseFirstLine,
|
|
@@ -26,7 +26,10 @@ import type { ParsedGraph, GraphNode, GraphGroup } from './types';
|
|
|
26
26
|
const PSEUDOSTATE_ID = 'pseudostate:[*]';
|
|
27
27
|
const PSEUDOSTATE_LABEL = '[*]';
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// `[Group]` or `[Group] color` (universal §1.5 trailing-token).
|
|
30
|
+
// Color (group 2) must be a recognized lowercase palette word.
|
|
31
|
+
const GROUP_BRACKET_RE =
|
|
32
|
+
/^\[([^\]]+)\](?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?\s*$/;
|
|
30
33
|
|
|
31
34
|
// ============================================================
|
|
32
35
|
// Arrow splitter
|
|
@@ -36,7 +39,7 @@ const GROUP_BRACKET_RE = /^\[([^\]]+)\](?:\(([^)]+)\))?\s*$/;
|
|
|
36
39
|
* Split a line on `->` arrows, returning alternating segments:
|
|
37
40
|
* [nodeText, arrowToken, nodeText, ...]
|
|
38
41
|
*
|
|
39
|
-
* Arrows: `->`, `-label
|
|
42
|
+
* Arrows: `->`, `-label->`. Edges have no color slot (spec §1.7).
|
|
40
43
|
*/
|
|
41
44
|
function splitArrows(line: string): string[] {
|
|
42
45
|
// Mirrors flowchart-parser.ts splitArrows. TD-9 longest-match: arrow token
|
|
@@ -46,7 +49,6 @@ function splitArrows(line: string): string[] {
|
|
|
46
49
|
start: number;
|
|
47
50
|
end: number;
|
|
48
51
|
label?: string;
|
|
49
|
-
color?: string;
|
|
50
52
|
}[] = [];
|
|
51
53
|
|
|
52
54
|
let searchFrom = 0;
|
|
@@ -61,7 +63,6 @@ function splitArrows(line: string): string[] {
|
|
|
61
63
|
|
|
62
64
|
let arrowStart: number;
|
|
63
65
|
let label: string | undefined;
|
|
64
|
-
let color: string | undefined;
|
|
65
66
|
|
|
66
67
|
let openingStart = -1;
|
|
67
68
|
for (let i = scanFloor; i < runStart; i++) {
|
|
@@ -78,22 +79,14 @@ function splitArrows(line: string): string[] {
|
|
|
78
79
|
let openingEnd = openingStart;
|
|
79
80
|
while (openingEnd < runStart && line[openingEnd] === '-') openingEnd++;
|
|
80
81
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
if (colorMatch) {
|
|
84
|
-
color = colorMatch[1].trim();
|
|
85
|
-
const labelPart = arrowContent.substring(0, colorMatch.index!).trim();
|
|
86
|
-
if (labelPart) label = labelPart;
|
|
87
|
-
} else {
|
|
88
|
-
const labelPart = arrowContent.trim();
|
|
89
|
-
if (labelPart) label = labelPart;
|
|
90
|
-
}
|
|
82
|
+
const labelPart = line.substring(openingEnd, runStart).trim();
|
|
83
|
+
if (labelPart) label = labelPart;
|
|
91
84
|
arrowStart = openingStart;
|
|
92
85
|
} else {
|
|
93
86
|
arrowStart = runStart;
|
|
94
87
|
}
|
|
95
88
|
|
|
96
|
-
arrowPositions.push({ start: arrowStart, end: arrowEnd, label
|
|
89
|
+
arrowPositions.push({ start: arrowStart, end: arrowEnd, label });
|
|
97
90
|
searchFrom = arrowEnd;
|
|
98
91
|
scanFloor = arrowEnd;
|
|
99
92
|
}
|
|
@@ -106,11 +99,7 @@ function splitArrows(line: string): string[] {
|
|
|
106
99
|
const beforeText = line.substring(lastIndex, arrow.start).trim();
|
|
107
100
|
if (beforeText || i === 0) segments.push(beforeText);
|
|
108
101
|
|
|
109
|
-
|
|
110
|
-
if (arrow.label && arrow.color)
|
|
111
|
-
arrowToken = `-${arrow.label}(${arrow.color})->`;
|
|
112
|
-
else if (arrow.label) arrowToken = `-${arrow.label}->`;
|
|
113
|
-
else if (arrow.color) arrowToken = `-(${arrow.color})->`;
|
|
102
|
+
const arrowToken = arrow.label ? `-${arrow.label}->` : '->';
|
|
114
103
|
segments.push(arrowToken);
|
|
115
104
|
lastIndex = arrow.end;
|
|
116
105
|
}
|
|
@@ -122,49 +111,23 @@ function splitArrows(line: string): string[] {
|
|
|
122
111
|
|
|
123
112
|
interface ArrowInfo {
|
|
124
113
|
label?: string;
|
|
125
|
-
color?: string;
|
|
126
114
|
}
|
|
127
115
|
|
|
128
116
|
function parseArrowToken(
|
|
129
117
|
token: string,
|
|
130
|
-
|
|
118
|
+
_palette: PaletteColors | undefined,
|
|
131
119
|
lineNumber: number,
|
|
132
120
|
diagnostics: DgmoError[]
|
|
133
121
|
): ArrowInfo {
|
|
134
122
|
if (token === '->') return {};
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
const bareParen = token.match(/^-(\([A-Za-z]+\))->$/);
|
|
139
|
-
if (bareParen) {
|
|
140
|
-
const colorName = matchColorParens(bareParen[1]);
|
|
141
|
-
if (colorName) {
|
|
142
|
-
return {
|
|
143
|
-
color: resolveColorWithDiagnostic(
|
|
144
|
-
colorName,
|
|
145
|
-
lineNumber,
|
|
146
|
-
diagnostics,
|
|
147
|
-
palette
|
|
148
|
-
),
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
// fall through — whole `(X)` becomes label
|
|
152
|
-
}
|
|
153
|
-
const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
|
|
123
|
+
// Edges have no color slot (§1.7); arrow content between `-` and `->`
|
|
124
|
+
// is pure label text.
|
|
125
|
+
const m = token.match(/^-(.+?)->$/);
|
|
154
126
|
if (m) {
|
|
155
127
|
const rawLabel = m[1] ?? '';
|
|
156
128
|
const labelResult = parseInArrowLabel(rawLabel, lineNumber);
|
|
157
129
|
diagnostics.push(...labelResult.diagnostics);
|
|
158
|
-
|
|
159
|
-
const color = m[2]
|
|
160
|
-
? resolveColorWithDiagnostic(
|
|
161
|
-
m[2].trim(),
|
|
162
|
-
lineNumber,
|
|
163
|
-
diagnostics,
|
|
164
|
-
palette
|
|
165
|
-
)
|
|
166
|
-
: undefined;
|
|
167
|
-
return { label, color };
|
|
130
|
+
return { label: labelResult.label };
|
|
168
131
|
}
|
|
169
132
|
return {};
|
|
170
133
|
}
|
|
@@ -292,15 +255,13 @@ export function parseState(
|
|
|
292
255
|
sourceId: string,
|
|
293
256
|
targetId: string,
|
|
294
257
|
lineNumber: number,
|
|
295
|
-
label?: string
|
|
296
|
-
color?: string
|
|
258
|
+
label?: string
|
|
297
259
|
): void {
|
|
298
260
|
result.edges.push({
|
|
299
261
|
source: sourceId,
|
|
300
262
|
target: targetId,
|
|
301
263
|
lineNumber,
|
|
302
264
|
...(label && { label }),
|
|
303
|
-
...(color && { color }),
|
|
304
265
|
});
|
|
305
266
|
}
|
|
306
267
|
|
|
@@ -494,13 +455,7 @@ export function parseState(
|
|
|
494
455
|
// Use explicit source if available, else implicit from indent
|
|
495
456
|
const sourceId = lastNodeId ?? implicitSourceId;
|
|
496
457
|
if (sourceId) {
|
|
497
|
-
addEdge(
|
|
498
|
-
sourceId,
|
|
499
|
-
node.id,
|
|
500
|
-
lineNumber,
|
|
501
|
-
pendingArrow.label,
|
|
502
|
-
pendingArrow.color
|
|
503
|
-
);
|
|
458
|
+
addEdge(sourceId, node.id, lineNumber, pendingArrow.label);
|
|
504
459
|
}
|
|
505
460
|
pendingArrow = null;
|
|
506
461
|
}
|
|
@@ -144,11 +144,8 @@ export function renderState(
|
|
|
144
144
|
.attr('points', `0,0 ${ARROWHEAD_W},${ARROWHEAD_H / 2} 0,${ARROWHEAD_H}`)
|
|
145
145
|
.attr('fill', palette.textMuted);
|
|
146
146
|
|
|
147
|
-
//
|
|
147
|
+
// Edges have no color slot (§1.7); keep empty set for marker iteration.
|
|
148
148
|
const edgeColors = new Set<string>();
|
|
149
|
-
for (const edge of layout.edges) {
|
|
150
|
-
if (edge.color) edgeColors.add(edge.color);
|
|
151
|
-
}
|
|
152
149
|
for (const color of edgeColors) {
|
|
153
150
|
const id = `st-arrow-${color.replace('#', '')}`;
|
|
154
151
|
defs
|
|
@@ -345,10 +342,8 @@ export function renderState(
|
|
|
345
342
|
.attr('class', 'st-edge-group')
|
|
346
343
|
.attr('data-line-number', String(edge.lineNumber));
|
|
347
344
|
|
|
348
|
-
const edgeColor =
|
|
349
|
-
const markerId =
|
|
350
|
-
? `st-arrow-${edge.color.replace('#', '')}`
|
|
351
|
-
: 'st-arrow';
|
|
345
|
+
const edgeColor = palette.textMuted;
|
|
346
|
+
const markerId = 'st-arrow';
|
|
352
347
|
|
|
353
348
|
if (edge.source === edge.target) {
|
|
354
349
|
// Self-loop
|
package/src/infra/parser.ts
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
nameMergedMessage,
|
|
15
15
|
} from '../diagnostics';
|
|
16
16
|
import { tryStripDescriptionKeyword } from '../utils/description-helpers';
|
|
17
|
-
import { resolveColorWithDiagnostic } from '../colors';
|
|
18
17
|
import { parseInArrowLabel } from '../utils/arrows';
|
|
19
18
|
import {
|
|
20
19
|
measureIndent,
|
|
@@ -22,6 +21,7 @@ import {
|
|
|
22
21
|
OPTION_NOCOLON_RE,
|
|
23
22
|
tryParseSharedOption,
|
|
24
23
|
} from '../utils/parsing';
|
|
24
|
+
import { isRecognizedColorName } from '../colors';
|
|
25
25
|
import { normalizeName, displayName } from '../utils/name-normalize';
|
|
26
26
|
import {
|
|
27
27
|
matchTagBlockHeading,
|
|
@@ -62,9 +62,11 @@ const DEPRECATED_FANOUT_RE = /\bx(\d+)\s*$/;
|
|
|
62
62
|
const GROUP_RE =
|
|
63
63
|
/^\[([^\]]+)\]\s*(?:as\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*)?(?:\|\s*(.+))?$/;
|
|
64
64
|
|
|
65
|
-
// Tag value: Name
|
|
66
|
-
//
|
|
67
|
-
|
|
65
|
+
// Tag value: `Name` or `Name color` (trailing-token color form). Color is
|
|
66
|
+
// extracted via the shared `extractColor` helper at use-site (see
|
|
67
|
+
// `dgmo/src/utils/parsing.ts:extractColor`), not via this regex. This regex
|
|
68
|
+
// just confirms the line shape is a valid tag value (no reserved sigils).
|
|
69
|
+
const TAG_VALUE_RE = /^(\w[\w\s]+?)\s*$/;
|
|
68
70
|
|
|
69
71
|
// Component line. Accepts either a quoted name ("name with | : reserved chars")
|
|
70
72
|
// or a bare name (multi-word allowed; must start with letter/underscore so digit-
|
|
@@ -445,14 +447,22 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
445
447
|
// Tag value inside tag group — first value is the default unless another is marked `default`
|
|
446
448
|
if (currentTagGroup && indent > 0) {
|
|
447
449
|
const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
450
|
+
// Trailing-token color (universal rule, §1.5): peel off a lowercase
|
|
451
|
+
// recognized color word from the end of the line. Downstream stores
|
|
452
|
+
// the raw color NAME (not the palette hex) so the renderer can resolve
|
|
453
|
+
// against whichever theme/palette is active at render time.
|
|
454
|
+
const lastSpaceIdx = cleanEntry.lastIndexOf(' ');
|
|
455
|
+
let valueName = cleanEntry;
|
|
456
|
+
let rawColor: string | undefined;
|
|
457
|
+
if (lastSpaceIdx > 0) {
|
|
458
|
+
const trailing = cleanEntry.substring(lastSpaceIdx + 1);
|
|
459
|
+
if (isRecognizedColorName(trailing)) {
|
|
460
|
+
rawColor = trailing;
|
|
461
|
+
valueName = cleanEntry.substring(0, lastSpaceIdx).trimEnd();
|
|
455
462
|
}
|
|
463
|
+
}
|
|
464
|
+
const tvMatch = valueName.match(TAG_VALUE_RE);
|
|
465
|
+
if (tvMatch || /^\w+$/.test(valueName)) {
|
|
456
466
|
currentTagGroup.values.push({
|
|
457
467
|
name: valueName,
|
|
458
468
|
color: rawColor,
|