@polagram/core 0.0.3 → 0.0.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/dist/index.d.ts +104 -6
- package/dist/polagram-core.js +2689 -2157
- package/dist/polagram-core.umd.cjs +20 -14
- package/dist/src/api.d.ts +12 -3
- package/dist/src/api.js +26 -3
- package/dist/src/config/schema.d.ts +16 -0
- package/dist/src/config/schema.js +5 -1
- package/dist/src/generator/generators/plantuml.d.ts +17 -0
- package/dist/src/generator/generators/plantuml.js +131 -0
- package/dist/src/generator/generators/plantuml.test.d.ts +1 -0
- package/dist/src/generator/generators/plantuml.test.js +143 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/parser/base/lexer.d.ts +3 -3
- package/dist/src/parser/base/parser.d.ts +9 -9
- package/dist/src/parser/base/token.d.ts +18 -0
- package/dist/src/parser/base/token.js +1 -0
- package/dist/src/parser/base/tokens.d.ts +8 -0
- package/dist/src/parser/base/tokens.js +1 -0
- package/dist/src/parser/format-detector.d.ts +55 -0
- package/dist/src/parser/format-detector.js +98 -0
- package/dist/src/parser/index.d.ts +1 -0
- package/dist/src/parser/index.js +4 -0
- package/dist/src/parser/languages/mermaid/lexer.d.ts +1 -1
- package/dist/src/parser/languages/mermaid/parser.d.ts +2 -1
- package/dist/src/parser/languages/plantuml/index.d.ts +4 -0
- package/dist/src/parser/languages/plantuml/index.js +11 -0
- package/dist/src/parser/languages/plantuml/lexer.d.ts +15 -0
- package/dist/src/parser/languages/plantuml/lexer.js +143 -0
- package/dist/src/parser/languages/plantuml/parser.d.ts +23 -0
- package/dist/src/parser/languages/plantuml/parser.js +481 -0
- package/dist/src/parser/languages/plantuml/parser.test.d.ts +1 -0
- package/dist/src/parser/languages/plantuml/parser.test.js +236 -0
- package/dist/src/parser/languages/plantuml/tokens.d.ts +9 -0
- package/dist/src/parser/languages/plantuml/tokens.js +1 -0
- package/dist/src/transformer/orchestration/engine.test.js +12 -1
- package/dist/src/transformer/selector/matcher.test.js +17 -0
- package/dist/src/transformer/traverse/walker.test.js +67 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -9
package/dist/src/api.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MermaidGeneratorVisitor } from './generator/generators/mermaid';
|
|
2
|
+
import { PlantUMLGeneratorVisitor } from './generator/generators/plantuml';
|
|
2
3
|
import { ParserFactory } from './parser';
|
|
3
4
|
import { TransformationEngine } from './transformer/orchestration/engine';
|
|
4
5
|
/**
|
|
@@ -17,19 +18,19 @@ export class Polagram {
|
|
|
17
18
|
/**
|
|
18
19
|
* Initialize a new Polagram transformation pipeline.
|
|
19
20
|
* @param code Source diagram code
|
|
20
|
-
* @param format Input format (
|
|
21
|
+
* @param format Input format ('mermaid' or 'plantuml')
|
|
21
22
|
*/
|
|
22
23
|
static init(code, format = 'mermaid') {
|
|
23
24
|
const parser = ParserFactory.getParser(format);
|
|
24
25
|
const ast = parser.parse(code);
|
|
25
|
-
return new PolagramBuilder(ast);
|
|
26
|
+
return new PolagramBuilder(ast, format);
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
30
|
* Builder class for chaining transformations.
|
|
30
31
|
*/
|
|
31
32
|
export class PolagramBuilder {
|
|
32
|
-
constructor(ast) {
|
|
33
|
+
constructor(ast, sourceFormat = 'mermaid') {
|
|
33
34
|
Object.defineProperty(this, "ast", {
|
|
34
35
|
enumerable: true,
|
|
35
36
|
configurable: true,
|
|
@@ -42,7 +43,14 @@ export class PolagramBuilder {
|
|
|
42
43
|
writable: true,
|
|
43
44
|
value: []
|
|
44
45
|
});
|
|
46
|
+
Object.defineProperty(this, "sourceFormat", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value: void 0
|
|
51
|
+
});
|
|
45
52
|
this.ast = ast;
|
|
53
|
+
this.sourceFormat = sourceFormat;
|
|
46
54
|
}
|
|
47
55
|
// -- Entity Filtering --
|
|
48
56
|
/**
|
|
@@ -122,6 +130,15 @@ export class PolagramBuilder {
|
|
|
122
130
|
const generator = new MermaidGeneratorVisitor();
|
|
123
131
|
return generator.generate(transformedAst);
|
|
124
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Generate PlantUML code from the transformed AST.
|
|
135
|
+
*/
|
|
136
|
+
toPlantUML() {
|
|
137
|
+
const engine = new TransformationEngine();
|
|
138
|
+
const transformedAst = engine.transform(this.ast, this.layers);
|
|
139
|
+
const generator = new PlantUMLGeneratorVisitor();
|
|
140
|
+
return generator.generate(transformedAst);
|
|
141
|
+
}
|
|
125
142
|
/**
|
|
126
143
|
* Get the transformed AST (for advanced use cases).
|
|
127
144
|
*/
|
|
@@ -129,6 +146,12 @@ export class PolagramBuilder {
|
|
|
129
146
|
const engine = new TransformationEngine();
|
|
130
147
|
return engine.transform(this.ast, this.layers);
|
|
131
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Get the source format detected/specified during init.
|
|
151
|
+
*/
|
|
152
|
+
getSourceFormat() {
|
|
153
|
+
return this.sourceFormat;
|
|
154
|
+
}
|
|
132
155
|
// -- Helper Methods --
|
|
133
156
|
normalizeParticipantSelector(selector) {
|
|
134
157
|
if (this.isTextMatcher(selector)) {
|
|
@@ -109,6 +109,14 @@ declare const TargetConfigSchema: z.ZodObject<{
|
|
|
109
109
|
}, z.core.$strip>], "kind">;
|
|
110
110
|
}, z.core.$strip>>;
|
|
111
111
|
}, z.core.$strip>>;
|
|
112
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
113
|
+
mermaid: "mermaid";
|
|
114
|
+
plantuml: "plantuml";
|
|
115
|
+
}>>;
|
|
116
|
+
outputFormat: z.ZodOptional<z.ZodEnum<{
|
|
117
|
+
mermaid: "mermaid";
|
|
118
|
+
plantuml: "plantuml";
|
|
119
|
+
}>>;
|
|
112
120
|
}, z.core.$strip>;
|
|
113
121
|
export declare const PolagramConfigSchema: z.ZodObject<{
|
|
114
122
|
version: z.ZodNumber;
|
|
@@ -169,6 +177,14 @@ export declare const PolagramConfigSchema: z.ZodObject<{
|
|
|
169
177
|
}, z.core.$strip>], "kind">;
|
|
170
178
|
}, z.core.$strip>>;
|
|
171
179
|
}, z.core.$strip>>;
|
|
180
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
181
|
+
mermaid: "mermaid";
|
|
182
|
+
plantuml: "plantuml";
|
|
183
|
+
}>>;
|
|
184
|
+
outputFormat: z.ZodOptional<z.ZodEnum<{
|
|
185
|
+
mermaid: "mermaid";
|
|
186
|
+
plantuml: "plantuml";
|
|
187
|
+
}>>;
|
|
172
188
|
}, z.core.$strip>>;
|
|
173
189
|
}, z.core.$strip>;
|
|
174
190
|
export type PolagramConfig = z.infer<typeof PolagramConfigSchema>;
|
|
@@ -50,12 +50,16 @@ const LensSchema = z.object({
|
|
|
50
50
|
suffix: z.string().optional(), // Defaults to .name in logic
|
|
51
51
|
layers: z.array(LayerSchema)
|
|
52
52
|
});
|
|
53
|
+
// -- Format --
|
|
54
|
+
const DiagramFormatSchema = z.enum(['mermaid', 'plantuml']);
|
|
53
55
|
// -- Config --
|
|
54
56
|
const TargetConfigSchema = z.object({
|
|
55
57
|
input: z.array(z.string()),
|
|
56
58
|
outputDir: z.string(),
|
|
57
59
|
ignore: z.array(z.string()).optional(),
|
|
58
|
-
lenses: z.array(LensSchema)
|
|
60
|
+
lenses: z.array(LensSchema),
|
|
61
|
+
format: DiagramFormatSchema.optional(), // Input format (auto-detected if omitted)
|
|
62
|
+
outputFormat: DiagramFormatSchema.optional() // Output format (same as input if omitted)
|
|
59
63
|
});
|
|
60
64
|
export const PolagramConfigSchema = z.object({
|
|
61
65
|
version: z.number(),
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ActivationNode, DividerNode, FragmentNode, MessageNode, NoteNode, Participant, ParticipantGroup, PolagramRoot, ReferenceNode, SpacerNode } from '../../ast';
|
|
2
|
+
import { PolagramVisitor } from '../interface';
|
|
3
|
+
export declare class PlantUMLGeneratorVisitor implements PolagramVisitor {
|
|
4
|
+
generate(root: PolagramRoot): string;
|
|
5
|
+
visitRoot(node: PolagramRoot): string;
|
|
6
|
+
visitParticipant(node: Participant): string;
|
|
7
|
+
visitGroup(node: ParticipantGroup, context?: Map<string, Participant>): string;
|
|
8
|
+
visitMessage(node: MessageNode): string;
|
|
9
|
+
visitFragment(node: FragmentNode): string;
|
|
10
|
+
private visitEvent;
|
|
11
|
+
visitNote(node: NoteNode): string;
|
|
12
|
+
visitActivation(node: ActivationNode): string;
|
|
13
|
+
visitParticipantGroup(_node: ParticipantGroup): string;
|
|
14
|
+
visitDivider(_node: DividerNode): string;
|
|
15
|
+
visitSpacer(_node: SpacerNode): string;
|
|
16
|
+
visitReference(_node: ReferenceNode): string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export class PlantUMLGeneratorVisitor {
|
|
2
|
+
generate(root) {
|
|
3
|
+
return this.visitRoot(root);
|
|
4
|
+
}
|
|
5
|
+
visitRoot(node) {
|
|
6
|
+
const lines = ['@startuml'];
|
|
7
|
+
if (node.meta.title) {
|
|
8
|
+
lines.push(`title ${node.meta.title}`);
|
|
9
|
+
}
|
|
10
|
+
const participantsMap = new Map(node.participants.map(p => [p.id, p]));
|
|
11
|
+
const groupedParticipantIds = new Set();
|
|
12
|
+
// Groups
|
|
13
|
+
for (const group of node.groups) {
|
|
14
|
+
lines.push(this.visitGroup(group, participantsMap));
|
|
15
|
+
group.participantIds.forEach(id => groupedParticipantIds.add(id));
|
|
16
|
+
}
|
|
17
|
+
// Ungrouped Participants
|
|
18
|
+
for (const participant of node.participants) {
|
|
19
|
+
if (!groupedParticipantIds.has(participant.id)) {
|
|
20
|
+
lines.push(this.visitParticipant(participant));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Events
|
|
24
|
+
for (const event of node.events) {
|
|
25
|
+
lines.push(this.visitEvent(event));
|
|
26
|
+
}
|
|
27
|
+
lines.push('@enduml');
|
|
28
|
+
return lines.join('\n');
|
|
29
|
+
}
|
|
30
|
+
visitParticipant(node) {
|
|
31
|
+
// If Name == ID, use concise format: "type ID"
|
|
32
|
+
// Otherwise use explicit format: "type "Name" as ID"
|
|
33
|
+
if (node.name === node.id) {
|
|
34
|
+
return `${node.type} ${node.id}`;
|
|
35
|
+
}
|
|
36
|
+
return `${node.type} "${node.name}" as ${node.id}`;
|
|
37
|
+
}
|
|
38
|
+
// Adjusted signature to pass context
|
|
39
|
+
visitGroup(node, context) {
|
|
40
|
+
const parts = [];
|
|
41
|
+
const color = node.style?.backgroundColor ? ` ${node.style.backgroundColor}` : '';
|
|
42
|
+
const title = node.name ? ` "${node.name}"` : '';
|
|
43
|
+
parts.push(`box${title}${color}`);
|
|
44
|
+
if (context) {
|
|
45
|
+
for (const pid of node.participantIds) {
|
|
46
|
+
const p = context.get(pid);
|
|
47
|
+
if (p) {
|
|
48
|
+
parts.push(this.visitParticipant(p));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
parts.push('end box');
|
|
53
|
+
return parts.join('\n');
|
|
54
|
+
}
|
|
55
|
+
visitMessage(node) {
|
|
56
|
+
const from = node.from || '[*]'; // Handle lost/found if needed
|
|
57
|
+
const to = node.to || '[*]';
|
|
58
|
+
let arrow = '->';
|
|
59
|
+
if (node.type === 'reply')
|
|
60
|
+
arrow = '-->';
|
|
61
|
+
else if (node.type === 'async')
|
|
62
|
+
arrow = '->>'; // PlantUML doesn't strictly distinguish async arrow head generally, but ->> is fine
|
|
63
|
+
// style override
|
|
64
|
+
if (node.style.line === 'dotted') {
|
|
65
|
+
// If reply, already --> (dotted). If sync but dotted: A -[dotted]-> B ?
|
|
66
|
+
// PlantUML: A ..> B
|
|
67
|
+
// But --> is usually standard reply.
|
|
68
|
+
}
|
|
69
|
+
return `${from} ${arrow} ${to}: ${node.text}`;
|
|
70
|
+
}
|
|
71
|
+
visitFragment(node) {
|
|
72
|
+
const lines = [];
|
|
73
|
+
const op = node.operator;
|
|
74
|
+
node.branches.forEach((branch, index) => {
|
|
75
|
+
if (index === 0) {
|
|
76
|
+
lines.push(`${op} ${branch.condition || ''}`.trim());
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
lines.push(`else ${branch.condition || ''}`.trim());
|
|
80
|
+
}
|
|
81
|
+
for (const event of branch.events) {
|
|
82
|
+
// We need to recursively visit events (messages, nested fragments, etc.)
|
|
83
|
+
// But we can't easily call visitX because we don't know the type statically here or need a centralized dispatcher that returns string.
|
|
84
|
+
// But wait, visitRoot has a dispatcher. We should extract it to `visitEvent`.
|
|
85
|
+
// For now, let's duplicate the relevant dispatch logic or creating a helper.
|
|
86
|
+
lines.push(this.visitEvent(event));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
lines.push('end');
|
|
90
|
+
return lines.join('\n');
|
|
91
|
+
}
|
|
92
|
+
visitEvent(event) {
|
|
93
|
+
switch (event.kind) {
|
|
94
|
+
case 'message': return this.visitMessage(event);
|
|
95
|
+
case 'fragment': return this.visitFragment(event);
|
|
96
|
+
case 'note': return this.visitNote(event);
|
|
97
|
+
case 'activation': return this.visitActivation(event);
|
|
98
|
+
case 'divider': return this.visitDivider(event);
|
|
99
|
+
case 'spacer': return this.visitSpacer(event);
|
|
100
|
+
case 'ref': return this.visitReference(event);
|
|
101
|
+
default: return '';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
visitNote(node) {
|
|
105
|
+
const position = node.position || 'over';
|
|
106
|
+
const participants = node.participantIds.join(', ');
|
|
107
|
+
// note left of A: Text
|
|
108
|
+
// note over A, B: Text
|
|
109
|
+
// For 'over', we don't say 'of' in standard examples? "note over Alice", "note over Alice, Bob"
|
|
110
|
+
// But 'left of', 'right of' use 'of'.
|
|
111
|
+
const positionStr = (position === 'left' || position === 'right') ? `${position} of` : position;
|
|
112
|
+
return `note ${positionStr} ${participants}: ${node.text}`;
|
|
113
|
+
}
|
|
114
|
+
visitActivation(node) {
|
|
115
|
+
// activate A
|
|
116
|
+
// deactivate A
|
|
117
|
+
return `${node.action} ${node.participantId}`;
|
|
118
|
+
}
|
|
119
|
+
visitParticipantGroup(_node) {
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
visitDivider(_node) {
|
|
123
|
+
return '';
|
|
124
|
+
}
|
|
125
|
+
visitSpacer(_node) {
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
visitReference(_node) {
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { PlantUMLGeneratorVisitor } from './plantuml';
|
|
3
|
+
describe('PlantUML Generator', () => {
|
|
4
|
+
const generator = new PlantUMLGeneratorVisitor();
|
|
5
|
+
it('should generate basic document structure', () => {
|
|
6
|
+
const ast = {
|
|
7
|
+
kind: 'root',
|
|
8
|
+
meta: { version: '1.0.0', source: 'plantuml', title: 'My Diagram' },
|
|
9
|
+
participants: [],
|
|
10
|
+
groups: [],
|
|
11
|
+
events: []
|
|
12
|
+
};
|
|
13
|
+
const code = generator.generate(ast);
|
|
14
|
+
expect(code).toContain('@startuml');
|
|
15
|
+
expect(code).toContain('title My Diagram');
|
|
16
|
+
expect(code).toContain('@enduml');
|
|
17
|
+
});
|
|
18
|
+
it('should generate participants', () => {
|
|
19
|
+
const ast = {
|
|
20
|
+
kind: 'root',
|
|
21
|
+
meta: { version: '1.0.0', source: 'plantuml' },
|
|
22
|
+
participants: [
|
|
23
|
+
{ id: 'User', name: 'User', type: 'actor' },
|
|
24
|
+
{ id: 'DB', name: 'DB', type: 'database' },
|
|
25
|
+
{ id: 'Svc', name: 'Service', type: 'participant' }
|
|
26
|
+
],
|
|
27
|
+
groups: [],
|
|
28
|
+
events: []
|
|
29
|
+
};
|
|
30
|
+
const code = generator.generate(ast);
|
|
31
|
+
expect(code).toContain('actor User');
|
|
32
|
+
expect(code).toContain('database DB');
|
|
33
|
+
expect(code).toContain('participant "Service" as Svc');
|
|
34
|
+
});
|
|
35
|
+
it('should generate messages', () => {
|
|
36
|
+
const ast = {
|
|
37
|
+
kind: 'root',
|
|
38
|
+
meta: { version: '1.0.0', source: 'plantuml' },
|
|
39
|
+
participants: [
|
|
40
|
+
{ id: 'A', name: 'A', type: 'participant' },
|
|
41
|
+
{ id: 'B', name: 'B', type: 'participant' }
|
|
42
|
+
],
|
|
43
|
+
groups: [],
|
|
44
|
+
events: [
|
|
45
|
+
{ kind: 'message', id: 'm1', from: 'A', to: 'B', text: 'Sync', type: 'sync', style: { line: 'solid', head: 'arrow' } },
|
|
46
|
+
{ kind: 'message', id: 'm2', from: 'B', to: 'A', text: 'Reply', type: 'reply', style: { line: 'dotted', head: 'arrow' } },
|
|
47
|
+
{ kind: 'message', id: 'm3', from: 'A', to: 'A', text: 'Self', type: 'sync', style: { line: 'solid', head: 'arrow' } }
|
|
48
|
+
]
|
|
49
|
+
};
|
|
50
|
+
const code = generator.generate(ast);
|
|
51
|
+
expect(code).toContain('A -> B: Sync');
|
|
52
|
+
expect(code).toContain('B --> A: Reply');
|
|
53
|
+
expect(code).toContain('A -> A: Self');
|
|
54
|
+
});
|
|
55
|
+
it('should generate fragments', () => {
|
|
56
|
+
const ast = {
|
|
57
|
+
kind: 'root',
|
|
58
|
+
meta: { version: '1.0.0', source: 'plantuml' },
|
|
59
|
+
participants: [],
|
|
60
|
+
groups: [],
|
|
61
|
+
events: [
|
|
62
|
+
{
|
|
63
|
+
kind: 'fragment',
|
|
64
|
+
id: 'f1',
|
|
65
|
+
operator: 'alt',
|
|
66
|
+
branches: [
|
|
67
|
+
{ id: 'b1', condition: 'success', events: [
|
|
68
|
+
{ kind: 'message', id: 'm1', from: 'A', to: 'B', text: 'OK', type: 'sync', style: { line: 'solid', head: 'arrow' } }
|
|
69
|
+
] },
|
|
70
|
+
{ id: 'b2', condition: 'failure', events: [
|
|
71
|
+
{ kind: 'message', id: 'm2', from: 'A', to: 'B', text: 'Fail', type: 'sync', style: { line: 'solid', head: 'arrow' } }
|
|
72
|
+
] }
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
};
|
|
77
|
+
const code = generator.generate(ast);
|
|
78
|
+
expect(code).toContain('alt success');
|
|
79
|
+
expect(code).toContain('A -> B: OK');
|
|
80
|
+
expect(code).toContain('else failure');
|
|
81
|
+
expect(code).toContain('A -> B: Fail');
|
|
82
|
+
expect(code).toContain('end');
|
|
83
|
+
});
|
|
84
|
+
it('should generate notes', () => {
|
|
85
|
+
const ast = {
|
|
86
|
+
kind: 'root',
|
|
87
|
+
meta: { version: '1.0.0', source: 'plantuml' },
|
|
88
|
+
participants: [
|
|
89
|
+
{ id: 'A', name: 'A', type: 'participant' }
|
|
90
|
+
],
|
|
91
|
+
groups: [],
|
|
92
|
+
events: [
|
|
93
|
+
{ kind: 'note', id: 'n1', text: 'My Note', position: 'right', participantIds: ['A'] },
|
|
94
|
+
{ kind: 'note', id: 'n2', text: 'Over Note', position: 'over', participantIds: ['A'] }
|
|
95
|
+
]
|
|
96
|
+
};
|
|
97
|
+
const code = generator.generate(ast);
|
|
98
|
+
expect(code).toContain('note right of A: My Note');
|
|
99
|
+
expect(code).toContain('note over A: Over Note');
|
|
100
|
+
});
|
|
101
|
+
it('should generate lifecycle events', () => {
|
|
102
|
+
const ast = {
|
|
103
|
+
kind: 'root',
|
|
104
|
+
meta: { version: '1.0.0', source: 'plantuml' },
|
|
105
|
+
participants: [
|
|
106
|
+
{ id: 'A', name: 'A', type: 'participant' }
|
|
107
|
+
],
|
|
108
|
+
groups: [],
|
|
109
|
+
events: [
|
|
110
|
+
{ kind: 'activation', participantId: 'A', action: 'activate' },
|
|
111
|
+
// Some message presumably
|
|
112
|
+
{ kind: 'activation', participantId: 'A', action: 'deactivate' }
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
const code = generator.generate(ast);
|
|
116
|
+
expect(code).toContain('activate A');
|
|
117
|
+
expect(code).toContain('deactivate A');
|
|
118
|
+
});
|
|
119
|
+
it('should generate groups', () => {
|
|
120
|
+
const ast = {
|
|
121
|
+
kind: 'root',
|
|
122
|
+
meta: { version: '1.0.0', source: 'plantuml' },
|
|
123
|
+
participants: [
|
|
124
|
+
{ id: 'A', name: 'A', type: 'participant' },
|
|
125
|
+
{ id: 'B', name: 'B', type: 'participant' },
|
|
126
|
+
{ id: 'C', name: 'C', type: 'participant' }
|
|
127
|
+
],
|
|
128
|
+
groups: [
|
|
129
|
+
{ kind: 'group', id: 'g1', name: 'Box1', type: 'box', participantIds: ['A', 'B'], style: { backgroundColor: '#LightBlue' } }
|
|
130
|
+
],
|
|
131
|
+
events: []
|
|
132
|
+
};
|
|
133
|
+
const code = generator.generate(ast);
|
|
134
|
+
// Expect Box1 to contain A and B
|
|
135
|
+
// C should be outside
|
|
136
|
+
// Exact order depends on implementation, but box should be there
|
|
137
|
+
expect(code).toContain('box "Box1" #LightBlue');
|
|
138
|
+
expect(code).toContain('participant A');
|
|
139
|
+
expect(code).toContain('participant B');
|
|
140
|
+
expect(code).toContain('end box');
|
|
141
|
+
expect(code).toContain('participant C');
|
|
142
|
+
});
|
|
143
|
+
});
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export * from './ast';
|
|
2
2
|
export { ParserFactory } from './parser';
|
|
3
|
+
export { FormatDetector } from './parser/format-detector';
|
|
4
|
+
export type { DiagramFormat } from './parser/format-detector';
|
|
3
5
|
export type { DiagramParser } from './parser/interface';
|
|
4
6
|
export { Traverser } from './generator/base/walker';
|
|
5
7
|
export type { PolagramVisitor } from './generator/interface';
|
|
8
|
+
export * from './config/schema';
|
|
6
9
|
export { MermaidGeneratorVisitor } from './generator/generators/mermaid';
|
|
10
|
+
export { PlantUMLGeneratorVisitor } from './generator/generators/plantuml';
|
|
7
11
|
export * from './transformer';
|
|
8
12
|
export { Polagram, PolagramBuilder } from './api';
|
|
9
13
|
export * from './config';
|
package/dist/src/index.js
CHANGED
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
export * from './ast';
|
|
4
4
|
// Parsers (Factory & Strategy)
|
|
5
5
|
export { ParserFactory } from './parser';
|
|
6
|
+
export { FormatDetector } from './parser/format-detector';
|
|
6
7
|
// Generators
|
|
7
8
|
export { Traverser } from './generator/base/walker';
|
|
8
9
|
// Default Implementations (Optional, or force users to use Factory)
|
|
9
10
|
// We export Mermaid Generator specifically as it might be used directly or via a future Factory
|
|
11
|
+
// Config & Validation
|
|
12
|
+
export * from './config/schema';
|
|
10
13
|
export { MermaidGeneratorVisitor } from './generator/generators/mermaid';
|
|
14
|
+
export { PlantUMLGeneratorVisitor } from './generator/generators/plantuml';
|
|
11
15
|
// Transformation Engine
|
|
12
16
|
export * from './transformer';
|
|
13
17
|
// Fluent API (Recommended for most users)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare abstract class BaseLexer {
|
|
1
|
+
import { BaseToken } from './token';
|
|
2
|
+
export declare abstract class BaseLexer<T extends BaseToken = BaseToken> {
|
|
3
3
|
protected input: string;
|
|
4
4
|
protected position: number;
|
|
5
5
|
protected readPosition: number;
|
|
@@ -8,7 +8,7 @@ export declare abstract class BaseLexer {
|
|
|
8
8
|
protected column: number;
|
|
9
9
|
constructor(input: string);
|
|
10
10
|
getInput(): string;
|
|
11
|
-
abstract nextToken():
|
|
11
|
+
abstract nextToken(): T;
|
|
12
12
|
protected readChar(): void;
|
|
13
13
|
protected peekChar(): string;
|
|
14
14
|
protected skipWhitespace(): void;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { PolagramRoot } from '../../ast';
|
|
2
|
-
import { Token, TokenType } from '../languages/mermaid/tokens';
|
|
3
2
|
import { BaseLexer } from './lexer';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
protected
|
|
7
|
-
protected
|
|
8
|
-
|
|
3
|
+
import { BaseToken } from './token';
|
|
4
|
+
export declare abstract class BaseParser<T extends BaseToken = BaseToken> {
|
|
5
|
+
protected lexer: BaseLexer<T>;
|
|
6
|
+
protected currToken: T;
|
|
7
|
+
protected peekToken: T;
|
|
8
|
+
constructor(lexer: BaseLexer<T>);
|
|
9
9
|
protected advance(): void;
|
|
10
10
|
abstract parse(): PolagramRoot;
|
|
11
|
-
protected curTokenIs(t:
|
|
12
|
-
protected peekTokenIs(t:
|
|
13
|
-
protected expectPeek(t:
|
|
11
|
+
protected curTokenIs(t: string): boolean;
|
|
12
|
+
protected peekTokenIs(t: string): boolean;
|
|
13
|
+
protected expectPeek(t: string): boolean;
|
|
14
14
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base token interface that all token types should extend.
|
|
3
|
+
* Represents a lexical token with position information.
|
|
4
|
+
*/
|
|
5
|
+
export interface BaseToken {
|
|
6
|
+
/** Token type identifier */
|
|
7
|
+
type: string;
|
|
8
|
+
/** Literal string value of the token */
|
|
9
|
+
literal: string;
|
|
10
|
+
/** Line number (1-indexed) */
|
|
11
|
+
line: number;
|
|
12
|
+
/** Column number (0-indexed) */
|
|
13
|
+
column: number;
|
|
14
|
+
/** Start position in source (0-indexed) */
|
|
15
|
+
start: number;
|
|
16
|
+
/** End position in source (0-indexed) */
|
|
17
|
+
end: number;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported diagram formats
|
|
3
|
+
*/
|
|
4
|
+
export type DiagramFormat = 'mermaid' | 'plantuml';
|
|
5
|
+
/**
|
|
6
|
+
* Format detection utilities for diagram source code.
|
|
7
|
+
* Detects diagram format based on file extension and content analysis.
|
|
8
|
+
*/
|
|
9
|
+
export declare class FormatDetector {
|
|
10
|
+
/**
|
|
11
|
+
* File extensions mapped to their diagram formats
|
|
12
|
+
*/
|
|
13
|
+
private static readonly EXTENSION_MAP;
|
|
14
|
+
/**
|
|
15
|
+
* Content patterns for format detection
|
|
16
|
+
*/
|
|
17
|
+
private static readonly CONTENT_PATTERNS;
|
|
18
|
+
/**
|
|
19
|
+
* Detect diagram format from file path and content.
|
|
20
|
+
*
|
|
21
|
+
* @param filePath - Path to the diagram file
|
|
22
|
+
* @param content - Content of the diagram file
|
|
23
|
+
* @returns Detected format, or null if format cannot be determined
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const format = FormatDetector.detect('diagram.puml', content);
|
|
28
|
+
* if (format === 'plantuml') {
|
|
29
|
+
* // Process as PlantUML
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
static detect(filePath: string, content: string): DiagramFormat | null;
|
|
34
|
+
/**
|
|
35
|
+
* Detect format based on file extension.
|
|
36
|
+
*
|
|
37
|
+
* @param filePath - Path to the diagram file
|
|
38
|
+
* @returns Detected format, or null if extension is not recognized
|
|
39
|
+
*/
|
|
40
|
+
static detectByExtension(filePath: string): DiagramFormat | null;
|
|
41
|
+
/**
|
|
42
|
+
* Detect format based on content patterns.
|
|
43
|
+
*
|
|
44
|
+
* @param content - Content of the diagram file
|
|
45
|
+
* @returns Detected format, or null if no pattern matches
|
|
46
|
+
*/
|
|
47
|
+
static detectByContent(content: string): DiagramFormat | null;
|
|
48
|
+
/**
|
|
49
|
+
* Get file extension for a given format.
|
|
50
|
+
*
|
|
51
|
+
* @param format - Diagram format
|
|
52
|
+
* @returns Default file extension for the format
|
|
53
|
+
*/
|
|
54
|
+
static getDefaultExtension(format: DiagramFormat): string;
|
|
55
|
+
}
|