@diagrammo/dgmo 0.7.0 → 0.7.2
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/dist/cli.cjs +180 -180
- package/dist/index.cjs +415 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +415 -144
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/c4/parser.ts +3 -2
- package/src/d3.ts +7 -9
- package/src/er/parser.ts +5 -3
- package/src/gantt/calculator.ts +51 -11
- package/src/gantt/parser.ts +31 -20
- package/src/gantt/renderer.ts +524 -132
- package/src/gantt/types.ts +4 -0
- package/src/org/parser.ts +7 -5
- package/src/sequence/parser.ts +10 -9
- package/src/sitemap/parser.ts +5 -3
- package/src/utils/parsing.ts +23 -12
package/src/gantt/types.ts
CHANGED
|
@@ -93,6 +93,7 @@ export interface GanttEra {
|
|
|
93
93
|
endDate: string;
|
|
94
94
|
label: string;
|
|
95
95
|
color: string | null;
|
|
96
|
+
lineNumber: number;
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
export interface GanttMarker {
|
|
@@ -114,6 +115,9 @@ export interface GanttOptions {
|
|
|
114
115
|
dependencies: boolean;
|
|
115
116
|
sort: 'default' | 'tag';
|
|
116
117
|
defaultSwimlaneGroup: string | null; // tag group name from `sort: tag:Team`
|
|
118
|
+
/** Line numbers for option/block keywords — maps key to source line */
|
|
119
|
+
optionLineNumbers: Record<string, number>;
|
|
120
|
+
holidaysLineNumber: number | null;
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
// ── Parsed Result ───────────────────────────────────────────
|
package/src/org/parser.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
measureIndent,
|
|
8
8
|
extractColor,
|
|
9
9
|
parsePipeMetadata,
|
|
10
|
+
MULTIPLE_PIPE_WARNING,
|
|
10
11
|
CHART_TYPE_RE,
|
|
11
12
|
TITLE_RE,
|
|
12
13
|
OPTION_RE,
|
|
@@ -280,14 +281,14 @@ export function parseOrg(
|
|
|
280
281
|
// Otherwise it's an orphan metadata error
|
|
281
282
|
if (indent === 0) {
|
|
282
283
|
// Treat as a node label (e.g., "Dr. Smith: Surgeon" is a valid name)
|
|
283
|
-
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
284
|
+
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
284
285
|
attachNode(node, indent, indentStack, result);
|
|
285
286
|
} else {
|
|
286
287
|
pushError(lineNumber, 'Metadata has no parent node');
|
|
287
288
|
}
|
|
288
289
|
} else {
|
|
289
290
|
// It's a node label — possibly with single-line pipe-delimited metadata
|
|
290
|
-
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
291
|
+
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
291
292
|
attachNode(node, indent, indentStack, result);
|
|
292
293
|
}
|
|
293
294
|
}
|
|
@@ -326,15 +327,16 @@ function parseNodeLabel(
|
|
|
326
327
|
lineNumber: number,
|
|
327
328
|
palette: PaletteColors | undefined,
|
|
328
329
|
counter: number,
|
|
329
|
-
aliasMap: Map<string, string> = new Map()
|
|
330
|
+
aliasMap: Map<string, string> = new Map(),
|
|
331
|
+
warnFn?: (line: number, msg: string) => void,
|
|
330
332
|
): OrgNode {
|
|
331
|
-
// Check for single-line compact metadata: "Alice Park | role: Senior
|
|
333
|
+
// Check for single-line compact metadata: "Alice Park | role: Senior, location: NY"
|
|
332
334
|
const segments = trimmed.split('|').map((s) => s.trim());
|
|
333
335
|
|
|
334
336
|
let rawLabel = segments[0];
|
|
335
337
|
const { label, color } = extractColor(rawLabel, palette);
|
|
336
338
|
|
|
337
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
339
|
+
const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : undefined);
|
|
338
340
|
|
|
339
341
|
return {
|
|
340
342
|
id: `node-${counter}`,
|
package/src/sequence/parser.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { inferParticipantType } from './participant-inference';
|
|
|
6
6
|
import type { DgmoError } from '../diagnostics';
|
|
7
7
|
import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
|
|
8
8
|
import { parseArrow } from '../utils/arrows';
|
|
9
|
-
import { measureIndent, extractColor, parsePipeMetadata } from '../utils/parsing';
|
|
9
|
+
import { measureIndent, extractColor, parsePipeMetadata, MULTIPLE_PIPE_WARNING } from '../utils/parsing';
|
|
10
10
|
import type { TagGroup } from '../utils/tag-groups';
|
|
11
11
|
import { matchTagBlockHeading, validateTagValues } from '../utils/tag-groups';
|
|
12
12
|
|
|
@@ -237,12 +237,13 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
237
237
|
const aliasMap = new Map<string, string>();
|
|
238
238
|
|
|
239
239
|
/** Split pipe metadata from a line: "core | k: v" → { core, meta } */
|
|
240
|
-
const splitPipe = (text: string): { core: string; meta?: Record<string, string> } => {
|
|
240
|
+
const splitPipe = (text: string, ln?: number): { core: string; meta?: Record<string, string> } => {
|
|
241
241
|
const idx = text.indexOf('|');
|
|
242
242
|
if (idx < 0) return { core: text };
|
|
243
243
|
const core = text.substring(0, idx).trimEnd();
|
|
244
244
|
const segments = text.substring(idx).split('|');
|
|
245
|
-
const
|
|
245
|
+
const warnFn = ln != null ? () => pushWarning(ln, MULTIPLE_PIPE_WARNING) : undefined;
|
|
246
|
+
const meta = parsePipeMetadata(segments, aliasMap, warnFn);
|
|
246
247
|
return Object.keys(meta).length > 0 ? { core, meta } : { core };
|
|
247
248
|
};
|
|
248
249
|
|
|
@@ -287,7 +288,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
287
288
|
if (gpipeIdx >= 0) {
|
|
288
289
|
const nameAndColor = groupName.substring(0, gpipeIdx).trimEnd();
|
|
289
290
|
const segments = groupName.substring(gpipeIdx).split('|');
|
|
290
|
-
const meta = parsePipeMetadata(segments, aliasMap);
|
|
291
|
+
const meta = parsePipeMetadata(segments, aliasMap, () => pushWarning(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
291
292
|
if (Object.keys(meta).length > 0) groupMeta = meta;
|
|
292
293
|
// Re-extract color from name part
|
|
293
294
|
const colorSuffix = nameAndColor.match(/^(.+?)\(([^)]+)\)$/);
|
|
@@ -444,7 +445,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
444
445
|
}
|
|
445
446
|
|
|
446
447
|
// Parse "Name is a type [aka Alias]" declarations (always top-level)
|
|
447
|
-
const { core: isACore, meta: isAMeta } = splitPipe(trimmed);
|
|
448
|
+
const { core: isACore, meta: isAMeta } = splitPipe(trimmed, lineNumber);
|
|
448
449
|
const isAMatch = isACore.match(IS_A_PATTERN);
|
|
449
450
|
if (isAMatch) {
|
|
450
451
|
contentStarted = true;
|
|
@@ -491,7 +492,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
491
492
|
}
|
|
492
493
|
|
|
493
494
|
// Parse standalone "Name position N" (no "is a" type)
|
|
494
|
-
const { core: posCore, meta: posMeta } = splitPipe(trimmed);
|
|
495
|
+
const { core: posCore, meta: posMeta } = splitPipe(trimmed, lineNumber);
|
|
495
496
|
const posOnlyMatch = posCore.match(POSITION_ONLY_PATTERN);
|
|
496
497
|
if (posOnlyMatch) {
|
|
497
498
|
contentStarted = true;
|
|
@@ -523,7 +524,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
523
524
|
|
|
524
525
|
// Colored participant declaration — "Name(color)" at any level
|
|
525
526
|
// Color syntax is deprecated — emit warning and register without color
|
|
526
|
-
const { core: colorCore, meta: colorMeta } = splitPipe(trimmed);
|
|
527
|
+
const { core: colorCore, meta: colorMeta } = splitPipe(trimmed, lineNumber);
|
|
527
528
|
const coloredMatch = colorCore.match(COLORED_PARTICIPANT_PATTERN);
|
|
528
529
|
if (coloredMatch && !ARROW_PATTERN.test(colorCore)) {
|
|
529
530
|
const id = coloredMatch[1];
|
|
@@ -554,7 +555,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
554
555
|
// Bare participant name — either inside an active group (indented) or top-level declaration
|
|
555
556
|
// Supports pipe metadata: " API | c: Gateway" or "Tapin2 | l:Park"
|
|
556
557
|
{
|
|
557
|
-
const { core: bareCore, meta: bareMeta } = splitPipe(trimmed);
|
|
558
|
+
const { core: bareCore, meta: bareMeta } = splitPipe(trimmed, lineNumber);
|
|
558
559
|
const inGroup = activeGroup && measureIndent(raw) > 0;
|
|
559
560
|
if (/^\S+$/.test(bareCore) && !ARROW_PATTERN.test(bareCore) && (inGroup || !contentStarted || bareMeta)) {
|
|
560
561
|
contentStarted = true;
|
|
@@ -600,7 +601,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
600
601
|
}
|
|
601
602
|
|
|
602
603
|
// Split pipe metadata before arrow parsing (arrows use $ anchor)
|
|
603
|
-
const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed);
|
|
604
|
+
const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed, lineNumber);
|
|
604
605
|
|
|
605
606
|
// Parse message lines first — arrows take priority over keywords
|
|
606
607
|
// Reject "async" keyword prefix — use ~> instead
|
package/src/sitemap/parser.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
measureIndent,
|
|
12
12
|
extractColor,
|
|
13
13
|
parsePipeMetadata,
|
|
14
|
+
MULTIPLE_PIPE_WARNING,
|
|
14
15
|
CHART_TYPE_RE,
|
|
15
16
|
TITLE_RE,
|
|
16
17
|
OPTION_RE,
|
|
@@ -360,7 +361,7 @@ export function parseSitemap(
|
|
|
360
361
|
} else if (metadataMatch && indentStack.length === 0) {
|
|
361
362
|
// Could be a node label containing ':'
|
|
362
363
|
if (indent === 0) {
|
|
363
|
-
const node = parseNodeLabel(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
364
|
+
const node = parseNodeLabel(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
364
365
|
attachNode(node, indent, indentStack, result);
|
|
365
366
|
labelToNode.set(node.label.toLowerCase(), node);
|
|
366
367
|
} else {
|
|
@@ -368,7 +369,7 @@ export function parseSitemap(
|
|
|
368
369
|
}
|
|
369
370
|
} else {
|
|
370
371
|
// Node label — possibly with pipe-delimited metadata
|
|
371
|
-
const node = parseNodeLabel(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
372
|
+
const node = parseNodeLabel(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
372
373
|
attachNode(node, indent, indentStack, result);
|
|
373
374
|
labelToNode.set(node.label.toLowerCase(), node);
|
|
374
375
|
}
|
|
@@ -430,11 +431,12 @@ function parseNodeLabel(
|
|
|
430
431
|
palette: PaletteColors | undefined,
|
|
431
432
|
counter: number,
|
|
432
433
|
aliasMap: Map<string, string> = new Map(),
|
|
434
|
+
warnFn?: (line: number, msg: string) => void,
|
|
433
435
|
): SitemapNode {
|
|
434
436
|
const segments = trimmed.split('|').map((s) => s.trim());
|
|
435
437
|
const rawLabel = segments[0];
|
|
436
438
|
const { label, color } = extractColor(rawLabel, palette);
|
|
437
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
439
|
+
const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : undefined);
|
|
438
440
|
|
|
439
441
|
return {
|
|
440
442
|
id: `node-${counter}`,
|
package/src/utils/parsing.ts
CHANGED
|
@@ -118,23 +118,34 @@ export function parseSeriesNames(
|
|
|
118
118
|
return { series, names, nameColors, newIndex };
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
/**
|
|
121
|
+
/** Warning message for multiple pipes on a single line. */
|
|
122
|
+
export const MULTIPLE_PIPE_WARNING =
|
|
123
|
+
'Use a single "|" to start metadata, then separate items with commas.';
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Parse metadata from segments after the first (name) segment.
|
|
127
|
+
* A single `|` separates the label from metadata; items after the pipe are comma-delimited.
|
|
128
|
+
* Multiple pipes are treated as commas for backward compatibility but trigger a warning.
|
|
129
|
+
*/
|
|
122
130
|
export function parsePipeMetadata(
|
|
123
131
|
segments: string[],
|
|
124
132
|
aliasMap: Map<string, string> = new Map(),
|
|
133
|
+
warnMultiplePipes?: () => void,
|
|
125
134
|
): Record<string, string> {
|
|
135
|
+
if (segments.length > 2 && warnMultiplePipes) {
|
|
136
|
+
warnMultiplePipes();
|
|
137
|
+
}
|
|
126
138
|
const metadata: Record<string, string> = {};
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
139
|
+
const raw = segments.slice(1).join(',');
|
|
140
|
+
for (const part of raw.split(',')) {
|
|
141
|
+
const trimmedPart = part.trim();
|
|
142
|
+
if (!trimmedPart) continue;
|
|
143
|
+
const colonIdx = trimmedPart.indexOf(':');
|
|
144
|
+
if (colonIdx > 0) {
|
|
145
|
+
const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
|
|
146
|
+
const key = aliasMap.get(rawKey) ?? rawKey;
|
|
147
|
+
const value = trimmedPart.substring(colonIdx + 1).trim();
|
|
148
|
+
metadata[key] = value;
|
|
138
149
|
}
|
|
139
150
|
}
|
|
140
151
|
return metadata;
|