@diagrammo/dgmo 0.2.28 → 0.3.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/dist/cli.cjs +361 -921
- package/dist/index.cjs +40 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +40 -47
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +16 -14
- package/package.json +6 -6
- package/src/class/layout.ts +17 -12
- package/src/class/parser.ts +20 -42
- package/src/class/renderer.ts +44 -46
|
@@ -441,7 +441,10 @@ Animal
|
|
|
441
441
|
+ speak(): void
|
|
442
442
|
|
|
443
443
|
Dog extends Animal
|
|
444
|
+
+ breed: string
|
|
445
|
+
|
|
444
446
|
Cat extends Animal
|
|
447
|
+
+ indoor: boolean
|
|
445
448
|
```
|
|
446
449
|
|
|
447
450
|
Full example:
|
|
@@ -453,38 +456,37 @@ title: Type Hierarchy
|
|
|
453
456
|
Printable [interface]
|
|
454
457
|
+ print(): void
|
|
455
458
|
|
|
456
|
-
Shape [abstract]
|
|
459
|
+
Shape implements Printable [abstract]
|
|
457
460
|
# x: number
|
|
458
461
|
# y: number
|
|
459
462
|
+ area(): number
|
|
460
|
-
|
|
463
|
+
count: number {static}
|
|
461
464
|
|
|
462
|
-
Circle
|
|
465
|
+
Circle extends Shape
|
|
463
466
|
- radius: number
|
|
464
467
|
+ area(): number
|
|
465
468
|
|
|
466
|
-
Rectangle
|
|
469
|
+
Rectangle extends Shape
|
|
467
470
|
- width: number
|
|
468
471
|
- height: number
|
|
469
472
|
|
|
470
|
-
Circle extends Shape
|
|
471
|
-
Rectangle extends Shape
|
|
472
|
-
Shape implements Printable
|
|
473
473
|
Shape *-- Circle : contains
|
|
474
474
|
```
|
|
475
475
|
|
|
476
476
|
**Class modifiers**: `[abstract]`, `[interface]`, `[enum]`
|
|
477
477
|
|
|
478
|
+
**Inheritance**: `ClassName extends Parent` or `ClassName implements Interface` — declared inline in the class header. Members are indented below.
|
|
479
|
+
|
|
478
480
|
**Member visibility**: `+` public, `#` protected, `-` private. Static: `{static}`.
|
|
479
481
|
|
|
480
|
-
**Relationships** (
|
|
481
|
-
- Inheritance: `A
|
|
482
|
-
- Implementation: `A
|
|
483
|
-
- Composition: `A
|
|
484
|
-
- Aggregation: `A
|
|
485
|
-
- Dependency: `A
|
|
482
|
+
**Relationships** (arrow syntax):
|
|
483
|
+
- Inheritance: `A --|> B`
|
|
484
|
+
- Implementation: `A ..|> B`
|
|
485
|
+
- Composition: `A *-- B`
|
|
486
|
+
- Aggregation: `A o-- B`
|
|
487
|
+
- Dependency: `A ..> B`
|
|
486
488
|
- Association: `A -> B`
|
|
487
|
-
- Optional label: `A
|
|
489
|
+
- Optional label: `A *-- B : description`
|
|
488
490
|
|
|
489
491
|
### er
|
|
490
492
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diagrammo/dgmo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "DGMO diagram markup language — parser, renderer, and color system",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
"d3-scale": "^4.0.2",
|
|
51
51
|
"d3-selection": "^3.0.0",
|
|
52
52
|
"d3-shape": "^3.2.0",
|
|
53
|
-
"echarts": "^
|
|
53
|
+
"echarts": "^6.0.0",
|
|
54
54
|
"lz-string": "^1.5.0"
|
|
55
55
|
},
|
|
56
56
|
"optionalDependencies": {
|
|
57
|
-
"jsdom": "^
|
|
57
|
+
"jsdom": "^28.1.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/d3-array": "^3.2.1",
|
|
@@ -63,11 +63,11 @@
|
|
|
63
63
|
"@types/d3-scale": "^4.0.8",
|
|
64
64
|
"@types/d3-selection": "^3.0.11",
|
|
65
65
|
"@types/d3-shape": "^3.1.7",
|
|
66
|
-
"@types/dagre": "^0.7.
|
|
67
|
-
"@types/jsdom": "^
|
|
66
|
+
"@types/dagre": "^0.7.54",
|
|
67
|
+
"@types/jsdom": "^28.0.0",
|
|
68
68
|
"jscpd": "^4.0.8",
|
|
69
69
|
"tsup": "^8.5.1",
|
|
70
70
|
"typescript": "^5.7.3",
|
|
71
|
-
"vitest": "^
|
|
71
|
+
"vitest": "^4.0.18"
|
|
72
72
|
}
|
|
73
73
|
}
|
package/src/class/layout.ts
CHANGED
|
@@ -87,7 +87,7 @@ function computeNodeDimensions(node: ClassNode): {
|
|
|
87
87
|
const headerHeight = HEADER_BASE + (node.modifier ? MODIFIER_BADGE : 0);
|
|
88
88
|
|
|
89
89
|
// Fields compartment
|
|
90
|
-
let fieldsHeight
|
|
90
|
+
let fieldsHeight: number;
|
|
91
91
|
if (isEnum) {
|
|
92
92
|
// Enum values go in fields compartment
|
|
93
93
|
const enumValues = node.members; // all members are enum values
|
|
@@ -96,6 +96,8 @@ function computeNodeDimensions(node: ClassNode): {
|
|
|
96
96
|
COMPARTMENT_PADDING_Y * 2 +
|
|
97
97
|
enumValues.length * MEMBER_LINE_HEIGHT +
|
|
98
98
|
SEPARATOR_HEIGHT;
|
|
99
|
+
} else {
|
|
100
|
+
fieldsHeight = SEPARATOR_HEIGHT + COMPARTMENT_PADDING_Y;
|
|
99
101
|
}
|
|
100
102
|
} else {
|
|
101
103
|
if (fields.length > 0) {
|
|
@@ -103,24 +105,27 @@ function computeNodeDimensions(node: ClassNode): {
|
|
|
103
105
|
COMPARTMENT_PADDING_Y * 2 +
|
|
104
106
|
fields.length * MEMBER_LINE_HEIGHT +
|
|
105
107
|
SEPARATOR_HEIGHT;
|
|
108
|
+
} else {
|
|
109
|
+
// UML: always show attributes compartment
|
|
110
|
+
fieldsHeight = SEPARATOR_HEIGHT + COMPARTMENT_PADDING_Y;
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
// Methods compartment (not for enums)
|
|
110
115
|
let methodsHeight = 0;
|
|
111
|
-
if (!isEnum
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
if (!isEnum) {
|
|
117
|
+
if (methods.length > 0) {
|
|
118
|
+
methodsHeight =
|
|
119
|
+
COMPARTMENT_PADDING_Y * 2 +
|
|
120
|
+
methods.length * MEMBER_LINE_HEIGHT +
|
|
121
|
+
SEPARATOR_HEIGHT;
|
|
122
|
+
} else {
|
|
123
|
+
// UML: always show methods compartment
|
|
124
|
+
methodsHeight = SEPARATOR_HEIGHT + COMPARTMENT_PADDING_Y;
|
|
125
|
+
}
|
|
116
126
|
}
|
|
117
127
|
|
|
118
|
-
|
|
119
|
-
const height =
|
|
120
|
-
headerHeight +
|
|
121
|
-
fieldsHeight +
|
|
122
|
-
methodsHeight +
|
|
123
|
-
(fieldsHeight === 0 && methodsHeight === 0 ? 4 : 0);
|
|
128
|
+
const height = headerHeight + fieldsHeight + methodsHeight;
|
|
124
129
|
|
|
125
130
|
return { width, height, headerHeight, fieldsHeight, methodsHeight };
|
|
126
131
|
}
|
package/src/class/parser.ts
CHANGED
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
ParsedClassDiagram,
|
|
7
7
|
ClassNode,
|
|
8
8
|
ClassMember,
|
|
9
|
-
ClassRelationship,
|
|
10
9
|
ClassModifier,
|
|
11
10
|
MemberVisibility,
|
|
12
11
|
RelationshipType,
|
|
@@ -24,14 +23,9 @@ function classId(name: string): string {
|
|
|
24
23
|
// Regex patterns
|
|
25
24
|
// ============================================================
|
|
26
25
|
|
|
27
|
-
// Class declaration: ClassName [modifier] (color)
|
|
26
|
+
// Class declaration: ClassName [extends|implements ParentClass] [modifier] (color)
|
|
28
27
|
const CLASS_DECL_RE =
|
|
29
|
-
/^([A-Z][A-Za-z0-9_]*)(?:\s+\[(abstract|interface|enum)\])?(?:\s+\(([^)]+)\))?\s*$/;
|
|
30
|
-
|
|
31
|
-
// Relationship — keyword syntax:
|
|
32
|
-
// ClassName extends|implements|contains|has|uses TargetClass : label
|
|
33
|
-
const REL_KEYWORD_RE =
|
|
34
|
-
/^([A-Z][A-Za-z0-9_]*)\s+(extends|implements|contains|has|uses)\s+([A-Z][A-Za-z0-9_]*)(?:\s*:\s*(.+))?$/;
|
|
28
|
+
/^([A-Z][A-Za-z0-9_]*)(?:\s+(extends|implements)\s+([A-Z][A-Za-z0-9_]*))?(?:\s+\[(abstract|interface|enum)\])?(?:\s+\(([^)]+)\))?\s*$/;
|
|
35
29
|
|
|
36
30
|
// Relationship — arrow syntax:
|
|
37
31
|
// ClassName --|> TargetClass : label
|
|
@@ -45,14 +39,6 @@ const STATIC_SUFFIX_RE = /\{static\}\s*$/;
|
|
|
45
39
|
const METHOD_RE = /^(.+?)\(([^)]*)\)(?:\s*:\s*(.+))?$/;
|
|
46
40
|
const FIELD_RE = /^(.+?)\s*:\s*(.+)$/;
|
|
47
41
|
|
|
48
|
-
const KEYWORD_TO_TYPE: Record<string, RelationshipType> = {
|
|
49
|
-
extends: 'extends',
|
|
50
|
-
implements: 'implements',
|
|
51
|
-
contains: 'composes',
|
|
52
|
-
has: 'aggregates',
|
|
53
|
-
uses: 'depends',
|
|
54
|
-
};
|
|
55
|
-
|
|
56
42
|
const ARROW_TO_TYPE: Record<string, RelationshipType> = {
|
|
57
43
|
'--|>': 'extends',
|
|
58
44
|
'..|>': 'implements',
|
|
@@ -257,28 +243,6 @@ export function parseClassDiagram(
|
|
|
257
243
|
currentClass = null;
|
|
258
244
|
contentStarted = true;
|
|
259
245
|
|
|
260
|
-
// Try relationship — keyword syntax
|
|
261
|
-
const relKeyword = trimmed.match(REL_KEYWORD_RE);
|
|
262
|
-
if (relKeyword) {
|
|
263
|
-
const sourceName = relKeyword[1];
|
|
264
|
-
const keyword = relKeyword[2].toLowerCase();
|
|
265
|
-
const targetName = relKeyword[3];
|
|
266
|
-
const label = relKeyword[4]?.trim();
|
|
267
|
-
|
|
268
|
-
// Ensure both classes exist
|
|
269
|
-
getOrCreateClass(sourceName, lineNumber);
|
|
270
|
-
getOrCreateClass(targetName, lineNumber);
|
|
271
|
-
|
|
272
|
-
result.relationships.push({
|
|
273
|
-
source: classId(sourceName),
|
|
274
|
-
target: classId(targetName),
|
|
275
|
-
type: KEYWORD_TO_TYPE[keyword],
|
|
276
|
-
...(label && { label }),
|
|
277
|
-
lineNumber,
|
|
278
|
-
});
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
246
|
// Try relationship — arrow syntax
|
|
283
247
|
const relArrow = trimmed.match(REL_ARROW_RE);
|
|
284
248
|
if (relArrow) {
|
|
@@ -305,8 +269,10 @@ export function parseClassDiagram(
|
|
|
305
269
|
const classDecl = trimmed.match(CLASS_DECL_RE);
|
|
306
270
|
if (classDecl) {
|
|
307
271
|
const name = classDecl[1];
|
|
308
|
-
const
|
|
309
|
-
const
|
|
272
|
+
const relKeyword = classDecl[2] as 'extends' | 'implements' | undefined;
|
|
273
|
+
const parentName = classDecl[3];
|
|
274
|
+
const modifier = classDecl[4] as ClassModifier | undefined;
|
|
275
|
+
const colorName = classDecl[5]?.trim();
|
|
310
276
|
const color = colorName ? resolveColor(colorName, palette) : undefined;
|
|
311
277
|
|
|
312
278
|
const node = getOrCreateClass(name, lineNumber);
|
|
@@ -315,6 +281,17 @@ export function parseClassDiagram(
|
|
|
315
281
|
// Update line number to the declaration line (may have been created by relationship)
|
|
316
282
|
node.lineNumber = lineNumber;
|
|
317
283
|
|
|
284
|
+
// Inline extends/implements creates a relationship
|
|
285
|
+
if (relKeyword && parentName) {
|
|
286
|
+
getOrCreateClass(parentName, lineNumber);
|
|
287
|
+
result.relationships.push({
|
|
288
|
+
source: classId(name),
|
|
289
|
+
target: classId(parentName),
|
|
290
|
+
type: relKeyword as RelationshipType,
|
|
291
|
+
lineNumber,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
318
295
|
currentClass = node;
|
|
319
296
|
continue;
|
|
320
297
|
}
|
|
@@ -376,9 +353,10 @@ export function looksLikeClassDiagram(content: string): boolean {
|
|
|
376
353
|
hasModifier = true;
|
|
377
354
|
hasClassDecl = true;
|
|
378
355
|
}
|
|
379
|
-
// Check for
|
|
380
|
-
if (
|
|
356
|
+
// Check for inline extends/implements in class declaration
|
|
357
|
+
if (/^[A-Z][A-Za-z0-9_]*\s+(extends|implements)\s+[A-Z]/.test(trimmed)) {
|
|
381
358
|
hasRelationship = true;
|
|
359
|
+
hasClassDecl = true;
|
|
382
360
|
}
|
|
383
361
|
// Check for relationship arrows
|
|
384
362
|
if (REL_ARROW_RE.test(trimmed)) {
|
package/src/class/renderer.ts
CHANGED
|
@@ -406,43 +406,42 @@ export function renderClassDiagram(
|
|
|
406
406
|
const methods = node.members.filter((m) => m.isMethod);
|
|
407
407
|
|
|
408
408
|
if (isEnum) {
|
|
409
|
-
// Enum:
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
memberY += MEMBER_LINE_HEIGHT;
|
|
431
|
-
}
|
|
409
|
+
// Enum: single values compartment
|
|
410
|
+
// Separator
|
|
411
|
+
nodeG.append('line')
|
|
412
|
+
.attr('x1', -w / 2)
|
|
413
|
+
.attr('y1', yPos)
|
|
414
|
+
.attr('x2', w / 2)
|
|
415
|
+
.attr('y2', yPos)
|
|
416
|
+
.attr('stroke', stroke)
|
|
417
|
+
.attr('stroke-width', 0.5)
|
|
418
|
+
.attr('stroke-opacity', 0.5);
|
|
419
|
+
|
|
420
|
+
let memberY = yPos + COMPARTMENT_PADDING_Y;
|
|
421
|
+
for (const member of node.members) {
|
|
422
|
+
nodeG.append('text')
|
|
423
|
+
.attr('x', -w / 2 + MEMBER_PADDING_X)
|
|
424
|
+
.attr('y', memberY + MEMBER_LINE_HEIGHT / 2)
|
|
425
|
+
.attr('dominant-baseline', 'central')
|
|
426
|
+
.attr('fill', palette.text)
|
|
427
|
+
.attr('font-size', MEMBER_FONT_SIZE)
|
|
428
|
+
.text(member.name);
|
|
429
|
+
memberY += MEMBER_LINE_HEIGHT;
|
|
432
430
|
}
|
|
433
431
|
} else {
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
432
|
+
// UML 3-compartment layout: always show both separators
|
|
433
|
+
|
|
434
|
+
// Fields separator
|
|
435
|
+
nodeG.append('line')
|
|
436
|
+
.attr('x1', -w / 2)
|
|
437
|
+
.attr('y1', yPos)
|
|
438
|
+
.attr('x2', w / 2)
|
|
439
|
+
.attr('y2', yPos)
|
|
440
|
+
.attr('stroke', stroke)
|
|
441
|
+
.attr('stroke-width', 0.5)
|
|
442
|
+
.attr('stroke-opacity', 0.5);
|
|
445
443
|
|
|
444
|
+
if (fields.length > 0) {
|
|
446
445
|
let memberY = yPos + COMPARTMENT_PADDING_Y;
|
|
447
446
|
for (const field of fields) {
|
|
448
447
|
const vis = visibilitySymbol(field.visibility);
|
|
@@ -463,21 +462,20 @@ export function renderClassDiagram(
|
|
|
463
462
|
|
|
464
463
|
memberY += MEMBER_LINE_HEIGHT;
|
|
465
464
|
}
|
|
466
|
-
yPos += node.fieldsHeight;
|
|
467
465
|
}
|
|
466
|
+
yPos += node.fieldsHeight;
|
|
467
|
+
|
|
468
|
+
// Methods separator
|
|
469
|
+
nodeG.append('line')
|
|
470
|
+
.attr('x1', -w / 2)
|
|
471
|
+
.attr('y1', yPos)
|
|
472
|
+
.attr('x2', w / 2)
|
|
473
|
+
.attr('y2', yPos)
|
|
474
|
+
.attr('stroke', stroke)
|
|
475
|
+
.attr('stroke-width', 0.5)
|
|
476
|
+
.attr('stroke-opacity', 0.5);
|
|
468
477
|
|
|
469
|
-
// Methods compartment
|
|
470
478
|
if (methods.length > 0) {
|
|
471
|
-
// Separator
|
|
472
|
-
nodeG.append('line')
|
|
473
|
-
.attr('x1', -w / 2)
|
|
474
|
-
.attr('y1', yPos)
|
|
475
|
-
.attr('x2', w / 2)
|
|
476
|
-
.attr('y2', yPos)
|
|
477
|
-
.attr('stroke', stroke)
|
|
478
|
-
.attr('stroke-width', 0.5)
|
|
479
|
-
.attr('stroke-opacity', 0.5);
|
|
480
|
-
|
|
481
479
|
let memberY = yPos + COMPARTMENT_PADDING_Y;
|
|
482
480
|
for (const method of methods) {
|
|
483
481
|
const vis = visibilitySymbol(method.visibility);
|