@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
package/src/utils/parsing.ts
CHANGED
|
@@ -11,14 +11,41 @@ import type { PaletteColors } from '../palettes';
|
|
|
11
11
|
/** Complete set of recognized chart type identifiers. */
|
|
12
12
|
export const ALL_CHART_TYPES = new Set([
|
|
13
13
|
// data charts
|
|
14
|
-
'bar',
|
|
15
|
-
'
|
|
16
|
-
'
|
|
14
|
+
'bar',
|
|
15
|
+
'line',
|
|
16
|
+
'pie',
|
|
17
|
+
'doughnut',
|
|
18
|
+
'area',
|
|
19
|
+
'polar-area',
|
|
20
|
+
'radar',
|
|
21
|
+
'bar-stacked',
|
|
22
|
+
'multi-line',
|
|
23
|
+
'scatter',
|
|
24
|
+
'sankey',
|
|
25
|
+
'chord',
|
|
26
|
+
'function',
|
|
27
|
+
'heatmap',
|
|
28
|
+
'funnel',
|
|
17
29
|
// visualizations
|
|
18
|
-
'slope',
|
|
30
|
+
'slope',
|
|
31
|
+
'wordcloud',
|
|
32
|
+
'arc',
|
|
33
|
+
'timeline',
|
|
34
|
+
'venn',
|
|
35
|
+
'quadrant',
|
|
19
36
|
// diagrams
|
|
20
|
-
'sequence',
|
|
21
|
-
'
|
|
37
|
+
'sequence',
|
|
38
|
+
'flowchart',
|
|
39
|
+
'class',
|
|
40
|
+
'er',
|
|
41
|
+
'org',
|
|
42
|
+
'kanban',
|
|
43
|
+
'c4',
|
|
44
|
+
'initiative-status',
|
|
45
|
+
'state',
|
|
46
|
+
'sitemap',
|
|
47
|
+
'infra',
|
|
48
|
+
'gantt',
|
|
22
49
|
]);
|
|
23
50
|
|
|
24
51
|
/** Measure leading whitespace of a line, normalizing tabs to 4 spaces. */
|
|
@@ -38,7 +65,7 @@ export const COLOR_SUFFIX_RE = /\(([^)]+)\)\s*$/;
|
|
|
38
65
|
/** Extract an optional trailing color suffix from a label, resolving via palette. */
|
|
39
66
|
export function extractColor(
|
|
40
67
|
label: string,
|
|
41
|
-
palette?: PaletteColors
|
|
68
|
+
palette?: PaletteColors
|
|
42
69
|
): { label: string; color?: string } {
|
|
43
70
|
const m = label.match(COLOR_SUFFIX_RE);
|
|
44
71
|
if (!m) return { label };
|
|
@@ -49,24 +76,9 @@ export function extractColor(
|
|
|
49
76
|
};
|
|
50
77
|
}
|
|
51
78
|
|
|
52
|
-
/** @deprecated Matches `chart: <type>` header lines. Remove after all parsers migrate. */
|
|
53
|
-
export const CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
|
|
54
|
-
|
|
55
|
-
/** @deprecated Matches `title: <text>` header lines. Remove after all parsers migrate. */
|
|
56
|
-
export const TITLE_RE = /^title\s*:\s*(.+)/i;
|
|
57
|
-
|
|
58
|
-
/** @deprecated Matches `option: value` header lines. Remove after all parsers migrate. */
|
|
59
|
-
export const OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
|
|
60
|
-
|
|
61
79
|
/** Matches `option value` header lines (space-separated, no colon). */
|
|
62
80
|
export const OPTION_NOCOLON_RE = /^([a-z][a-z0-9-]*)\s+(.+)$/i;
|
|
63
81
|
|
|
64
|
-
/** Matches `# GroupName` lines — alternate group notation. */
|
|
65
|
-
export const GROUP_HASH_RE = /^#\s+(.+)$/;
|
|
66
|
-
|
|
67
|
-
/** Matches `## ...` lines — parse error with helpful hint. */
|
|
68
|
-
export const DOUBLE_HASH_RE = /^##\s/;
|
|
69
|
-
|
|
70
82
|
// ── New shared utilities ─────────────────────────────────────
|
|
71
83
|
|
|
72
84
|
/**
|
|
@@ -76,29 +88,12 @@ export const DOUBLE_HASH_RE = /^##\s/;
|
|
|
76
88
|
* Returns `null` if the first token is not a recognized chart type.
|
|
77
89
|
*/
|
|
78
90
|
export function parseFirstLine(
|
|
79
|
-
line: string
|
|
91
|
+
line: string
|
|
80
92
|
): { chartType: string; title: string | undefined } | null {
|
|
81
93
|
const trimmed = line.trim();
|
|
82
94
|
if (!trimmed || trimmed.startsWith('//')) return null;
|
|
83
95
|
|
|
84
|
-
//
|
|
85
|
-
const oldMatch = trimmed.match(CHART_TYPE_RE);
|
|
86
|
-
if (oldMatch) {
|
|
87
|
-
const parts = oldMatch[1].trim();
|
|
88
|
-
// Could be `chart: gantt My Title` — first token is type
|
|
89
|
-
const spaceIdx = parts.indexOf(' ');
|
|
90
|
-
if (spaceIdx === -1) {
|
|
91
|
-
const ct = parts.toLowerCase();
|
|
92
|
-
return ALL_CHART_TYPES.has(ct) ? { chartType: ct, title: undefined } : null;
|
|
93
|
-
}
|
|
94
|
-
const ct = parts.substring(0, spaceIdx).toLowerCase();
|
|
95
|
-
if (ALL_CHART_TYPES.has(ct)) {
|
|
96
|
-
return { chartType: ct, title: parts.substring(spaceIdx + 1).trim() || undefined };
|
|
97
|
-
}
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// New-style: first token is chart type, rest is title
|
|
96
|
+
// First token is chart type, rest is title
|
|
102
97
|
const spaceIdx = trimmed.indexOf(' ');
|
|
103
98
|
if (spaceIdx === -1) {
|
|
104
99
|
const ct = trimmed.toLowerCase();
|
|
@@ -106,7 +101,10 @@ export function parseFirstLine(
|
|
|
106
101
|
}
|
|
107
102
|
const firstToken = trimmed.substring(0, spaceIdx).toLowerCase();
|
|
108
103
|
if (!ALL_CHART_TYPES.has(firstToken)) return null;
|
|
109
|
-
return {
|
|
104
|
+
return {
|
|
105
|
+
chartType: firstToken,
|
|
106
|
+
title: trimmed.substring(spaceIdx + 1).trim() || undefined,
|
|
107
|
+
};
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
/** Result of `prescanOptions()` — options collected from a two-pass scan. */
|
|
@@ -137,7 +135,7 @@ export interface PrescanResult {
|
|
|
137
135
|
export function prescanOptions(
|
|
138
136
|
lines: string[],
|
|
139
137
|
knownOptions: Set<string>,
|
|
140
|
-
knownBooleans: Set<string> = new Set()
|
|
138
|
+
knownBooleans: Set<string> = new Set()
|
|
141
139
|
): PrescanResult {
|
|
142
140
|
const options: Record<string, string> = {};
|
|
143
141
|
const booleans = new Set<string>();
|
|
@@ -152,12 +150,15 @@ export function prescanOptions(
|
|
|
152
150
|
|
|
153
151
|
// Strip inline comments
|
|
154
152
|
const commentIdx = trimmed.indexOf(' //');
|
|
155
|
-
const effective =
|
|
153
|
+
const effective =
|
|
154
|
+
commentIdx >= 0 ? trimmed.substring(0, commentIdx).trim() : trimmed;
|
|
156
155
|
if (!effective) continue;
|
|
157
156
|
|
|
158
157
|
// Extract first token
|
|
159
158
|
const spaceIdx = effective.indexOf(' ');
|
|
160
|
-
const firstToken = (
|
|
159
|
+
const firstToken = (
|
|
160
|
+
spaceIdx === -1 ? effective : effective.substring(0, spaceIdx)
|
|
161
|
+
).toLowerCase();
|
|
161
162
|
|
|
162
163
|
// Check for bare boolean (presence = on)
|
|
163
164
|
if (spaceIdx === -1 && knownBooleans.has(firstToken)) {
|
|
@@ -210,8 +211,10 @@ export function normalizeGroupedNumber(token: string): string | null {
|
|
|
210
211
|
*/
|
|
211
212
|
export function stripQuotes(token: string): string {
|
|
212
213
|
if (token.length >= 2) {
|
|
213
|
-
if (
|
|
214
|
-
|
|
214
|
+
if (
|
|
215
|
+
(token[0] === '"' && token[token.length - 1] === '"') ||
|
|
216
|
+
(token[0] === "'" && token[token.length - 1] === "'")
|
|
217
|
+
) {
|
|
215
218
|
return token.substring(1, token.length - 1);
|
|
216
219
|
}
|
|
217
220
|
}
|
|
@@ -228,7 +231,10 @@ export function tokenizeQuoteAware(input: string): string[] {
|
|
|
228
231
|
let i = 0;
|
|
229
232
|
while (i < input.length) {
|
|
230
233
|
// Skip whitespace
|
|
231
|
-
if (input[i] === ' ' || input[i] === '\t') {
|
|
234
|
+
if (input[i] === ' ' || input[i] === '\t') {
|
|
235
|
+
i++;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
232
238
|
|
|
233
239
|
// Quoted token
|
|
234
240
|
if (input[i] === '"' || input[i] === "'") {
|
|
@@ -261,7 +267,7 @@ export function tokenizeQuoteAware(input: string): string[] {
|
|
|
261
267
|
*/
|
|
262
268
|
export function collectIndentedValues(
|
|
263
269
|
lines: string[],
|
|
264
|
-
startIndex: number
|
|
270
|
+
startIndex: number
|
|
265
271
|
): { values: string[]; lineNumbers: number[]; newIndex: number } {
|
|
266
272
|
const values: string[] = [];
|
|
267
273
|
const lineNumbers: number[] = [];
|
|
@@ -293,7 +299,7 @@ export function parseSeriesNames(
|
|
|
293
299
|
value: string,
|
|
294
300
|
lines: string[],
|
|
295
301
|
lineIndex: number,
|
|
296
|
-
palette?: PaletteColors
|
|
302
|
+
palette?: PaletteColors
|
|
297
303
|
): {
|
|
298
304
|
series: string;
|
|
299
305
|
names: string[];
|
|
@@ -304,10 +310,13 @@ export function parseSeriesNames(
|
|
|
304
310
|
let rawNames: string[];
|
|
305
311
|
let series: string;
|
|
306
312
|
let newIndex = lineIndex;
|
|
307
|
-
let nameLineNumbers: number[] = [];
|
|
313
|
+
let nameLineNumbers: number[] = []; // eslint-disable-line no-useless-assignment
|
|
308
314
|
if (value) {
|
|
309
315
|
series = value;
|
|
310
|
-
rawNames = value
|
|
316
|
+
rawNames = value
|
|
317
|
+
.split(',')
|
|
318
|
+
.map((s) => s.trim())
|
|
319
|
+
.filter(Boolean);
|
|
311
320
|
// Inline series names all share the same line number
|
|
312
321
|
nameLineNumbers = rawNames.map(() => lineIndex + 1);
|
|
313
322
|
} else {
|
|
@@ -330,18 +339,6 @@ export function parseSeriesNames(
|
|
|
330
339
|
return { series, names, nameColors, nameLineNumbers, newIndex };
|
|
331
340
|
}
|
|
332
341
|
|
|
333
|
-
/**
|
|
334
|
-
* Normalize a direction/orientation value to canonical form ('LR' | 'TB').
|
|
335
|
-
* Accepts 'lr', 'tb', 'horizontal', 'vertical' (case-insensitive).
|
|
336
|
-
* Returns null if the value is not recognized.
|
|
337
|
-
*/
|
|
338
|
-
export function normalizeDirection(value: string): 'LR' | 'TB' | null {
|
|
339
|
-
const v = value.trim().toLowerCase();
|
|
340
|
-
if (v === 'lr' || v === 'horizontal') return 'LR';
|
|
341
|
-
if (v === 'tb' || v === 'vertical') return 'TB';
|
|
342
|
-
return null;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
342
|
/**
|
|
346
343
|
* Infer arrow color from label text.
|
|
347
344
|
* Returns a named palette color or undefined if no inference applies.
|
|
@@ -350,30 +347,43 @@ export function normalizeDirection(value: string): 'LR' | 'TB' | null {
|
|
|
350
347
|
export function inferArrowColor(label: string): string | undefined {
|
|
351
348
|
const lower = label.toLowerCase();
|
|
352
349
|
// Green: positive/affirmative
|
|
353
|
-
if (
|
|
350
|
+
if (
|
|
351
|
+
lower === 'yes' ||
|
|
352
|
+
lower === 'success' ||
|
|
353
|
+
lower === 'ok' ||
|
|
354
|
+
lower === 'true'
|
|
355
|
+
)
|
|
356
|
+
return 'green';
|
|
354
357
|
// Red: negative/failure
|
|
355
|
-
if (
|
|
358
|
+
if (
|
|
359
|
+
lower === 'no' ||
|
|
360
|
+
lower === 'fail' ||
|
|
361
|
+
lower === 'error' ||
|
|
362
|
+
lower === 'false'
|
|
363
|
+
)
|
|
364
|
+
return 'red';
|
|
356
365
|
// Orange: uncertain/warning
|
|
357
366
|
if (lower === 'maybe' || lower === 'warning') return 'orange';
|
|
358
367
|
return undefined;
|
|
359
368
|
}
|
|
360
369
|
|
|
361
|
-
/**
|
|
362
|
-
export const
|
|
370
|
+
/** Error message for multiple pipes on a single line. */
|
|
371
|
+
export const MULTIPLE_PIPE_ERROR =
|
|
363
372
|
'Use a single "|" to start metadata, then separate items with commas.';
|
|
364
373
|
|
|
365
374
|
/**
|
|
366
375
|
* Parse metadata from segments after the first (name) segment.
|
|
367
376
|
* A single `|` separates the label from metadata; items after the pipe are comma-delimited.
|
|
368
|
-
* Multiple pipes
|
|
377
|
+
* Multiple pipes produce an error.
|
|
369
378
|
*/
|
|
370
379
|
export function parsePipeMetadata(
|
|
371
380
|
segments: string[],
|
|
372
381
|
aliasMap: Map<string, string> = new Map(),
|
|
373
|
-
|
|
382
|
+
errorMultiplePipes?: () => void
|
|
374
383
|
): Record<string, string> {
|
|
375
|
-
if (segments.length > 2
|
|
376
|
-
|
|
384
|
+
if (segments.length > 2) {
|
|
385
|
+
if (errorMultiplePipes) errorMultiplePipes();
|
|
386
|
+
return {};
|
|
377
387
|
}
|
|
378
388
|
const metadata: Record<string, string> = {};
|
|
379
389
|
const raw = segments.slice(1).join(',');
|
package/src/utils/tag-groups.ts
CHANGED
|
@@ -26,25 +26,15 @@ export interface TagBlockMatch {
|
|
|
26
26
|
name: string;
|
|
27
27
|
alias: string | undefined;
|
|
28
28
|
colorHint: string | undefined;
|
|
29
|
-
/** true when the heading used `## …` (deprecated) */
|
|
30
|
-
deprecated: boolean;
|
|
31
29
|
/** Inline tag values parsed from single-line form (e.g., `tag Priority p High(red), Low(blue)`) */
|
|
32
30
|
inlineValues?: string[];
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
// ── Regexes ─────────────────────────────────────────────────
|
|
36
34
|
|
|
37
|
-
/**
|
|
38
|
-
export const TAG_BLOCK_RE =
|
|
39
|
-
/^tag:\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/i;
|
|
40
|
-
|
|
41
|
-
/** New canonical syntax: line starting with `tag` keyword (no colon). */
|
|
35
|
+
/** Canonical syntax: line starting with `tag` keyword (no colon). */
|
|
42
36
|
export const TAG_BLOCK_NOCOLON_RE = /^tag\s+/i;
|
|
43
37
|
|
|
44
|
-
/** @deprecated Legacy syntax: `## GroupName [alias X] [(color)]` */
|
|
45
|
-
export const GROUP_HEADING_RE =
|
|
46
|
-
/^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
|
|
47
|
-
|
|
48
38
|
// ── Alias Inference ─────────────────────────────────────────
|
|
49
39
|
|
|
50
40
|
/** Returns true if the token looks like an alias: 1-4 lowercase ASCII characters. */
|
|
@@ -54,9 +44,9 @@ function isAliasToken(token: string): boolean {
|
|
|
54
44
|
|
|
55
45
|
// ── Matchers ────────────────────────────────────────────────
|
|
56
46
|
|
|
57
|
-
/** Returns true if `trimmed` is a tag block heading
|
|
47
|
+
/** Returns true if `trimmed` is a tag block heading. */
|
|
58
48
|
export function isTagBlockHeading(trimmed: string): boolean {
|
|
59
|
-
return TAG_BLOCK_NOCOLON_RE.test(trimmed)
|
|
49
|
+
return TAG_BLOCK_NOCOLON_RE.test(trimmed);
|
|
60
50
|
}
|
|
61
51
|
|
|
62
52
|
/**
|
|
@@ -173,7 +163,6 @@ export function parseTagDeclaration(line: string): TagBlockMatch | null {
|
|
|
173
163
|
name,
|
|
174
164
|
alias,
|
|
175
165
|
colorHint,
|
|
176
|
-
deprecated: false,
|
|
177
166
|
inlineValues: inlineValues && inlineValues.length > 0 ? inlineValues : undefined,
|
|
178
167
|
};
|
|
179
168
|
}
|
|
@@ -310,31 +299,5 @@ export function injectDefaultTagMetadata(
|
|
|
310
299
|
// ── Matchers ────────────────────────────────────────────────
|
|
311
300
|
|
|
312
301
|
export function matchTagBlockHeading(trimmed: string): TagBlockMatch | null {
|
|
313
|
-
|
|
314
|
-
const nocolonResult = parseTagDeclaration(trimmed);
|
|
315
|
-
if (nocolonResult) return nocolonResult;
|
|
316
|
-
|
|
317
|
-
// Try old colon syntax: `tag: GroupName [alias X] [(color)]`
|
|
318
|
-
const tagMatch = trimmed.match(TAG_BLOCK_RE);
|
|
319
|
-
if (tagMatch) {
|
|
320
|
-
return {
|
|
321
|
-
name: tagMatch[1].trim(),
|
|
322
|
-
alias: tagMatch[2] || undefined,
|
|
323
|
-
colorHint: tagMatch[3] || undefined,
|
|
324
|
-
deprecated: false,
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Fall back to legacy ## syntax
|
|
329
|
-
const groupMatch = trimmed.match(GROUP_HEADING_RE);
|
|
330
|
-
if (groupMatch) {
|
|
331
|
-
return {
|
|
332
|
-
name: groupMatch[1].trim(),
|
|
333
|
-
alias: groupMatch[2] || undefined,
|
|
334
|
-
colorHint: groupMatch[3] || undefined,
|
|
335
|
-
deprecated: true,
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return null;
|
|
302
|
+
return parseTagDeclaration(trimmed);
|
|
340
303
|
}
|
package/src/infra/serialize.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Infra Scenario Serializer
|
|
3
|
-
// ============================================================
|
|
4
|
-
//
|
|
5
|
-
// Converts interactive overrides into a `scenario:` DSL block.
|
|
6
|
-
// Only includes properties that differ from the base diagram.
|
|
7
|
-
|
|
8
|
-
import type { ParsedInfra, InfraComputeParams } from './types';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Serialize interactive overrides as a DSL `scenario:` block.
|
|
12
|
-
* Returns an empty string if nothing differs from the base diagram.
|
|
13
|
-
*/
|
|
14
|
-
export function serializeScenario(name: string, parsed: ParsedInfra, overrides: InfraComputeParams): string {
|
|
15
|
-
const lines: string[] = [];
|
|
16
|
-
|
|
17
|
-
// Edge RPS override
|
|
18
|
-
const edgeNode = parsed.nodes.find((n) => n.isEdge);
|
|
19
|
-
if (edgeNode && overrides.rps != null) {
|
|
20
|
-
const baseRps = edgeNode.properties.find((p) => p.key === 'rps');
|
|
21
|
-
const baseVal = baseRps ? (typeof baseRps.value === 'number' ? baseRps.value : parseFloat(String(baseRps.value)) || 0) : 0;
|
|
22
|
-
if (overrides.rps !== baseVal) {
|
|
23
|
-
lines.push(` ${edgeNode.id}`);
|
|
24
|
-
lines.push(` rps: ${overrides.rps}`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Instance overrides and property overrides per node
|
|
29
|
-
const instanceOv = overrides.instanceOverrides ?? {};
|
|
30
|
-
const propOv = overrides.propertyOverrides ?? {};
|
|
31
|
-
|
|
32
|
-
for (const node of parsed.nodes) {
|
|
33
|
-
if (node.isEdge) continue;
|
|
34
|
-
|
|
35
|
-
const nodeLines: string[] = [];
|
|
36
|
-
|
|
37
|
-
// Instance override
|
|
38
|
-
if (instanceOv[node.id] != null) {
|
|
39
|
-
const baseProp = node.properties.find((p) => p.key === 'instances');
|
|
40
|
-
const baseVal = baseProp ? (typeof baseProp.value === 'number' ? baseProp.value : parseFloat(String(baseProp.value)) || 1) : 1;
|
|
41
|
-
if (instanceOv[node.id] !== baseVal) {
|
|
42
|
-
nodeLines.push(` instances: ${instanceOv[node.id]}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Property overrides
|
|
47
|
-
const nodePropOv = propOv[node.id];
|
|
48
|
-
if (nodePropOv) {
|
|
49
|
-
for (const [key, val] of Object.entries(nodePropOv)) {
|
|
50
|
-
const baseProp = node.properties.find((p) => p.key === key);
|
|
51
|
-
const baseVal = baseProp ? (typeof baseProp.value === 'number' ? baseProp.value : parseFloat(String(baseProp.value)) || 0) : 0;
|
|
52
|
-
if (val !== baseVal) {
|
|
53
|
-
nodeLines.push(` ${key}: ${val}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (nodeLines.length > 0) {
|
|
59
|
-
lines.push(` ${node.id}`);
|
|
60
|
-
lines.push(...nodeLines);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (lines.length === 0) return '';
|
|
65
|
-
|
|
66
|
-
return `scenario: ${name}\n${lines.join('\n')}\n`;
|
|
67
|
-
}
|