agentic-mermaid 0.1.1 → 0.2.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/AGENT_NATIVE.md +155 -91
- package/CHANGELOG.md +163 -4
- package/Instructions_for_agents.md +10 -10
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +31 -38
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/assets/hero.png +0 -0
- package/dist/agent-core.d.ts +463 -0
- package/dist/agent-core.js +397 -0
- package/dist/agent.d.ts +73 -1868
- package/dist/agent.js +336 -37325
- package/dist/agentic-mermaid-mcp.js +7 -38399
- package/dist/am.js +33626 -40446
- package/dist/chunk-2P5FKUQC.js +3485 -0
- package/dist/chunk-2S3VSS6I.js +149 -0
- package/dist/chunk-2SILJDW2.js +37 -0
- package/dist/chunk-BJYMBT4I.js +61963 -0
- package/dist/chunk-FDM43XU7.js +615 -0
- package/dist/chunk-MYPDVZCO.js +4702 -0
- package/dist/index-DWPa2MJs.d.ts +6780 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +310 -33014
- package/dist/mcp-cli-5J47WEY3.js +17 -0
- package/docs/README.md +39 -31
- package/docs/agent-api-cookbook.md +44 -35
- package/docs/agent-mutation-policy.md +1 -1
- package/docs/agent-workflow-examples.md +2 -2
- package/docs/api.md +284 -77
- package/docs/ascii.md +26 -16
- package/docs/code-mode-impact.md +214 -0
- package/docs/comparison.md +23 -11
- package/docs/computational-aesthetics-prototype-plan.md +252 -0
- package/docs/config.md +26 -10
- package/docs/custom-fonts.md +265 -0
- package/docs/custom-style-cookbook.md +130 -9
- package/docs/diagram-families.md +48 -23
- package/docs/features.md +59 -52
- package/docs/fork-differences.md +6 -3
- package/docs/getting-started.md +23 -16
- package/docs/issue-derived-test-cases.md +29 -22
- package/docs/mcp-code-mode-rationale.md +13 -6
- package/docs/mcp-http-transport.md +20 -13
- package/docs/mermaid-layout-complaints.md +66 -52
- package/docs/mutation-testing.md +145 -66
- package/docs/quality.md +23 -14
- package/docs/react.md +2 -2
- package/docs/schemas/style-spec.schema.json +1221 -30
- package/docs/style-authoring.md +241 -38
- package/docs/svg-semantic-contract.md +49 -0
- package/docs/testing-strategy.md +166 -58
- package/docs/theming.md +19 -32
- package/examples/styles/catalog.json +54 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
- package/examples/styles/cupertino-prototype.mmd +10 -0
- package/examples/styles/cupertino-prototype.style.json +63 -0
- package/examples/styles/mid-century-report.style.json +2 -2
- package/examples/styles/star-chart-atlas.style.json +2 -1
- package/examples/styles/transit-route-map.style.json +2 -2
- package/examples/styles/vercel-inspired-prototype.mmd +6 -0
- package/examples/styles/vercel-inspired-prototype.style.json +87 -0
- package/package.json +85 -45
- package/server.json +2 -2
- package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
- package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
- package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
- package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
- package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
- package/TODO.md +0 -684
- package/dist/index-wjRjbsP-.d.ts +0 -1102
- package/docs/assets/improvements/box-start-connector.png +0 -0
- package/docs/assets/improvements/comment-dropped-lint.png +0 -0
- package/docs/assets/improvements/er-content-loss.png +0 -0
- package/docs/assets/improvements/fan-in-trunks.png +0 -0
- package/docs/assets/improvements/gantt-family.png +0 -0
- package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
- package/docs/assets/improvements/gantt-verify-seam.png +0 -0
- package/docs/assets/improvements/subgraph-container-edges.png +0 -0
- package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
- package/docs/assets/improvements/wrapper-init-directive.png +0 -0
- package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
- package/docs/assets/style-cookbook/mid-century-report.png +0 -0
- package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
- package/docs/assets/style-cookbook/transit-route-map.png +0 -0
- package/docs/contributing/adding-diagram-types.md +0 -144
- package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
- package/docs/contributing/diagram-family-citizenship.md +0 -82
- package/docs/contributing/harvesting-upstream-tests.md +0 -126
- package/docs/contributing/lessons-learned.md +0 -276
- package/docs/contributing/releasing.md +0 -82
- package/docs/contributing/visual-review-evidence.md +0 -53
- package/docs/design/families/README.md +0 -26
- package/docs/design/families/architecture-beta.md +0 -48
- package/docs/design/families/flowchart-parser-conformance.md +0 -22
- package/docs/design/families/gantt-research.md +0 -137
- package/docs/design/families/gantt.md +0 -329
- package/docs/design/families/journey-agentic-cards-before.png +0 -0
- package/docs/design/families/journey-experience-curve-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
- package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
- package/docs/design/families/journey-migration-parity.md +0 -439
- package/docs/design/families/journey-section-overlap-after.png +0 -0
- package/docs/design/families/journey-section-overlap-before.png +0 -0
- package/docs/design/families/journey-usage-research.md +0 -423
- package/docs/design/families/journey.md +0 -153
- package/docs/design/families/xychart.md +0 -85
- package/docs/design/system/README.md +0 -45
- package/docs/design/system/abstraction-audit.md +0 -224
- package/docs/design/system/abstraction-recommendations.md +0 -655
- package/docs/design/system/architecture.mmd +0 -29
- package/docs/design/system/architecture.svg +0 -137
- package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
- package/docs/design/system/layout-rubric.md +0 -138
- package/docs/design/system/route-contracts.md +0 -848
- package/docs/design/system/source-preservation-ladder.md +0 -52
- package/docs/design/system/styles-rollout.md +0 -216
- package/docs/design/system/ugly-layouts.md +0 -119
- package/docs/layout-characterization/README.md +0 -245
- package/docs/layout-characterization/contact-sheet-families.md +0 -498
- package/docs/layout-characterization/contact-sheet.md +0 -638
- package/docs/layout-characterization/properties.md +0 -281
- package/docs/layout-characterization/visual-quality.md +0 -160
- package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
- package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
- package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
- package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
- package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/journey.svg +0 -98
- package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
- package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
- package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
- package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
- package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
- package/docs/project/divergences.md +0 -98
- package/docs/project/dogfooding-docs-strategy.md +0 -66
- package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
- package/docs/project/lessons-learned.md +0 -1019
- package/docs/project/remove-role-styling-plan.md +0 -176
- package/examples/agent-improve-auth-flow.ts +0 -206
- package/examples/agent-loop.ts +0 -53
- package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
- package/llms.txt +0 -104
- package/skill-evals/README.md +0 -77
- package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
- package/skill-evals/fixtures/class-payment/input.mmd +0 -4
- package/skill-evals/fixtures/er-orders/input.mmd +0 -10
- package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
- package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
- package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
- package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
- package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
- package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
- package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
- package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
- package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
- package/skill-evals/shared-benchmark.json +0 -510
- package/src/agent/analyze.ts +0 -157
- package/src/agent/apply.ts +0 -146
- package/src/agent/architecture-body.ts +0 -458
- package/src/agent/certificates.ts +0 -110
- package/src/agent/class-body.ts +0 -319
- package/src/agent/core.ts +0 -207
- package/src/agent/create.ts +0 -106
- package/src/agent/describe.ts +0 -368
- package/src/agent/er-body.ts +0 -224
- package/src/agent/facts.ts +0 -360
- package/src/agent/families-builtin.ts +0 -920
- package/src/agent/families.ts +0 -232
- package/src/agent/family-layouts.ts +0 -832
- package/src/agent/flowchart-body.ts +0 -372
- package/src/agent/flowchart-unsupported.ts +0 -243
- package/src/agent/font-coverage.ts +0 -288
- package/src/agent/gantt-body.ts +0 -495
- package/src/agent/index.ts +0 -10
- package/src/agent/journey-body.ts +0 -374
- package/src/agent/label-metrics.ts +0 -28
- package/src/agent/layout-to-rendered.ts +0 -63
- package/src/agent/mutate.ts +0 -97
- package/src/agent/mutation-ops.ts +0 -51
- package/src/agent/op-schema.ts +0 -443
- package/src/agent/parse.ts +0 -223
- package/src/agent/pie-body.ts +0 -270
- package/src/agent/png.ts +0 -171
- package/src/agent/quadrant-body.ts +0 -336
- package/src/agent/quality.ts +0 -363
- package/src/agent/readability-audit.ts +0 -92
- package/src/agent/rendered-route-audit.ts +0 -61
- package/src/agent/sequence-body.ts +0 -345
- package/src/agent/serialize.ts +0 -214
- package/src/agent/state-body.ts +0 -589
- package/src/agent/structural-count.ts +0 -150
- package/src/agent/timeline-body.ts +0 -280
- package/src/agent/trace-log.ts +0 -43
- package/src/agent/types.ts +0 -995
- package/src/agent/verify.ts +0 -603
- package/src/agent/xychart-body.ts +0 -472
- package/src/architecture/config.ts +0 -282
- package/src/architecture/layout.ts +0 -496
- package/src/architecture/parser.ts +0 -375
- package/src/architecture/renderer.ts +0 -611
- package/src/architecture/types.ts +0 -113
- package/src/ascii/ansi.ts +0 -423
- package/src/ascii/architecture.ts +0 -210
- package/src/ascii/canvas.ts +0 -443
- package/src/ascii/class-diagram.ts +0 -697
- package/src/ascii/converter.ts +0 -475
- package/src/ascii/draw.ts +0 -1693
- package/src/ascii/edge-bundling.ts +0 -328
- package/src/ascii/edge-routing.ts +0 -330
- package/src/ascii/er-diagram.ts +0 -439
- package/src/ascii/gantt.ts +0 -276
- package/src/ascii/grid.ts +0 -747
- package/src/ascii/index.ts +0 -196
- package/src/ascii/journey.ts +0 -169
- package/src/ascii/meta.ts +0 -244
- package/src/ascii/multiline-utils.ts +0 -80
- package/src/ascii/pathfinder.ts +0 -301
- package/src/ascii/pie.ts +0 -67
- package/src/ascii/quadrant.ts +0 -158
- package/src/ascii/reverse.ts +0 -216
- package/src/ascii/sequence.ts +0 -499
- package/src/ascii/shapes/circle.ts +0 -27
- package/src/ascii/shapes/corners.ts +0 -143
- package/src/ascii/shapes/diamond.ts +0 -27
- package/src/ascii/shapes/hexagon.ts +0 -27
- package/src/ascii/shapes/index.ts +0 -106
- package/src/ascii/shapes/rectangle.ts +0 -168
- package/src/ascii/shapes/rounded.ts +0 -27
- package/src/ascii/shapes/special.ts +0 -332
- package/src/ascii/shapes/stadium.ts +0 -110
- package/src/ascii/shapes/state.ts +0 -192
- package/src/ascii/shapes/types.ts +0 -73
- package/src/ascii/timeline.ts +0 -141
- package/src/ascii/types.ts +0 -312
- package/src/ascii/validate.ts +0 -120
- package/src/ascii/width.ts +0 -81
- package/src/ascii/wrap.ts +0 -131
- package/src/ascii/xychart.ts +0 -919
- package/src/browser.ts +0 -29
- package/src/class/layout.ts +0 -240
- package/src/class/parser.ts +0 -332
- package/src/class/renderer.ts +0 -638
- package/src/class/types.ts +0 -129
- package/src/cli/agent-instructions.ts +0 -1
- package/src/cli/am-bin.ts +0 -6
- package/src/cli/exit-codes.ts +0 -18
- package/src/cli/index.ts +0 -1132
- package/src/cli/init-agent.ts +0 -147
- package/src/cli/run-entrypoint.ts +0 -10
- package/src/color-resolver.ts +0 -152
- package/src/elk-instance.ts +0 -114
- package/src/er/layout.ts +0 -187
- package/src/er/parser.ts +0 -226
- package/src/er/renderer.ts +0 -792
- package/src/er/types.ts +0 -99
- package/src/family-rubric.ts +0 -425
- package/src/font-manifest.ts +0 -31
- package/src/gantt/layout.ts +0 -365
- package/src/gantt/parser.ts +0 -282
- package/src/gantt/pipeline.ts +0 -27
- package/src/gantt/renderer.ts +0 -414
- package/src/gantt/schedule.ts +0 -513
- package/src/gantt/types.ts +0 -255
- package/src/index.ts +0 -368
- package/src/journey/layout.ts +0 -554
- package/src/journey/parse-core.ts +0 -251
- package/src/journey/parser.ts +0 -101
- package/src/journey/renderer.ts +0 -917
- package/src/journey/types.ts +0 -144
- package/src/layout/geometry.ts +0 -182
- package/src/layout/pass.ts +0 -142
- package/src/layout/passes/index.ts +0 -3916
- package/src/layout-engine.ts +0 -1565
- package/src/layout-rubric.ts +0 -659
- package/src/mcp/artifacts.ts +0 -137
- package/src/mcp/dynamic-harness.ts +0 -31
- package/src/mcp/dynamic-modules.d.ts +0 -7
- package/src/mcp/facade.ts +0 -608
- package/src/mcp/harness-runtime.ts +0 -203
- package/src/mcp/hosted-server.ts +0 -468
- package/src/mcp/mcp-bin.ts +0 -3
- package/src/mcp/mcp-cli.ts +0 -82
- package/src/mcp/protocol.ts +0 -13
- package/src/mcp/sandbox.ts +0 -195
- package/src/mcp/sdk-decl.ts +0 -391
- package/src/mcp/server.ts +0 -362
- package/src/mcp/tool-surface.ts +0 -146
- package/src/mermaid-source.ts +0 -655
- package/src/multiline-utils.ts +0 -228
- package/src/parser.ts +0 -1031
- package/src/pie/layout.ts +0 -168
- package/src/pie/parser.ts +0 -136
- package/src/pie/renderer.ts +0 -234
- package/src/pie/types.ts +0 -82
- package/src/quadrant/layout.ts +0 -191
- package/src/quadrant/parser.ts +0 -183
- package/src/quadrant/renderer.ts +0 -322
- package/src/quadrant/types.ts +0 -113
- package/src/render-family-hooks.ts +0 -244
- package/src/renderer.ts +0 -1099
- package/src/route-contracts.ts +0 -2723
- package/src/scene/backend.ts +0 -76
- package/src/scene/fidelity.ts +0 -183
- package/src/scene/hybrid-backend.ts +0 -174
- package/src/scene/ir.ts +0 -181
- package/src/scene/marks.ts +0 -127
- package/src/scene/rough-backend.ts +0 -378
- package/src/scene/seed.ts +0 -46
- package/src/scene/style-registry.ts +0 -556
- package/src/sequence/layout.ts +0 -456
- package/src/sequence/parser.ts +0 -255
- package/src/sequence/renderer.ts +0 -604
- package/src/sequence/types.ts +0 -154
- package/src/shape-clipping.ts +0 -351
- package/src/shared/accessibility-directives.ts +0 -22
- package/src/shared/batched.ts +0 -61
- package/src/shared/color-math.ts +0 -93
- package/src/shared/svg-a11y.ts +0 -29
- package/src/shared/syntax-error.ts +0 -17
- package/src/shared/unicode-ranges.ts +0 -135
- package/src/styles.ts +0 -318
- package/src/svg-paths.ts +0 -28
- package/src/text-metrics.ts +0 -248
- package/src/theme.ts +0 -710
- package/src/timeline/layout.ts +0 -381
- package/src/timeline/parser.ts +0 -215
- package/src/timeline/renderer.ts +0 -538
- package/src/timeline/types.ts +0 -105
- package/src/types.ts +0 -462
- package/src/website-worker-modules.d.ts +0 -18
- package/src/xychart/axis-utils.ts +0 -109
- package/src/xychart/colors.ts +0 -124
- package/src/xychart/config.ts +0 -88
- package/src/xychart/layout.ts +0 -656
- package/src/xychart/parser.ts +0 -504
- package/src/xychart/renderer.ts +0 -640
- package/src/xychart/types.ts +0 -282
package/src/browser.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Browser entry point for Agentic Mermaid
|
|
3
|
-
//
|
|
4
|
-
// Exposes renderMermaid and renderMermaidAscii on window.__mermaid so they
|
|
5
|
-
// can be called from inline <script> tags in samples.html.
|
|
6
|
-
//
|
|
7
|
-
// Bundled via `Bun.build({ target: 'browser' })` in scripts/site/editor.ts and website/build.ts.
|
|
8
|
-
// ============================================================================
|
|
9
|
-
|
|
10
|
-
import { renderMermaidSVGAsync } from './index.ts'
|
|
11
|
-
import { verifyMermaid } from './agent/verify.ts'
|
|
12
|
-
import { renderMermaidASCII, diagramColorsToAsciiTheme } from './ascii/index.ts'
|
|
13
|
-
import { THEMES } from './theme.ts'
|
|
14
|
-
import { knownStyles, getStyle } from './scene/style-registry.ts'
|
|
15
|
-
import { getSeriesColor, CHART_ACCENT_FALLBACK } from './xychart/colors.ts'
|
|
16
|
-
|
|
17
|
-
declare const window: unknown
|
|
18
|
-
|
|
19
|
-
;(window as Record<string, unknown>).__mermaid = {
|
|
20
|
-
renderMermaidSVGAsync,
|
|
21
|
-
verifyMermaid,
|
|
22
|
-
renderMermaidASCII,
|
|
23
|
-
diagramColorsToAsciiTheme,
|
|
24
|
-
THEMES,
|
|
25
|
-
knownStyles,
|
|
26
|
-
getStyle,
|
|
27
|
-
getSeriesColor,
|
|
28
|
-
CHART_ACCENT_FALLBACK,
|
|
29
|
-
}
|
package/src/class/layout.ts
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Class diagram layout engine (ELK.js).
|
|
3
|
-
*
|
|
4
|
-
* Each class box has 3 compartments:
|
|
5
|
-
* 1. Header (class name + optional annotation)
|
|
6
|
-
* 2. Attributes section
|
|
7
|
-
* 3. Methods section
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { ElkNode, ElkExtendedEdge } from 'elkjs'
|
|
11
|
-
import type { ClassDiagram, ClassNode, ClassMember, PositionedClassDiagram, PositionedClassNode, PositionedClassRelationship } from './types.ts'
|
|
12
|
-
import type { RenderOptions, Point } from '../types.ts'
|
|
13
|
-
import { applyTextTransform, estimateTextWidth, estimateMonoTextWidth, FONT_SIZES, FONT_WEIGHTS, STROKE_WIDTHS, resolveRenderStyle } from '../styles.ts'
|
|
14
|
-
import type { RenderStyleDefaults } from '../styles.ts'
|
|
15
|
-
import { measureMultilineText } from '../text-metrics.ts'
|
|
16
|
-
import { elkLayoutSync } from '../elk-instance.ts'
|
|
17
|
-
|
|
18
|
-
/** Layout constants for class diagrams */
|
|
19
|
-
export const CLS = {
|
|
20
|
-
padding: 40,
|
|
21
|
-
boxPadX: 8,
|
|
22
|
-
headerBaseHeight: 32,
|
|
23
|
-
annotationHeight: 16,
|
|
24
|
-
memberRowHeight: 20,
|
|
25
|
-
sectionPadY: 8,
|
|
26
|
-
emptySectionHeight: 8,
|
|
27
|
-
minWidth: 120,
|
|
28
|
-
memberFontSize: 11,
|
|
29
|
-
memberFontWeight: 400,
|
|
30
|
-
nodeSpacing: 40,
|
|
31
|
-
layerSpacing: 60,
|
|
32
|
-
} as const
|
|
33
|
-
|
|
34
|
-
/** Shared by layout (sizing) and renderer (drawing) — keep it single-sourced. */
|
|
35
|
-
export const CLASS_STYLE_DEFAULTS: RenderStyleDefaults = {
|
|
36
|
-
nodeLabelFontSize: FONT_SIZES.nodeLabel,
|
|
37
|
-
edgeLabelFontSize: FONT_SIZES.edgeLabel,
|
|
38
|
-
groupHeaderFontSize: FONT_SIZES.groupHeader,
|
|
39
|
-
nodeLabelFontWeight: 700, // class titles are drawn bold; measure them bold too
|
|
40
|
-
edgeLabelFontWeight: FONT_WEIGHTS.edgeLabel,
|
|
41
|
-
groupHeaderFontWeight: FONT_WEIGHTS.groupHeader,
|
|
42
|
-
nodePaddingX: CLS.boxPadX,
|
|
43
|
-
nodePaddingY: CLS.sectionPadY,
|
|
44
|
-
nodeCornerRadius: 0,
|
|
45
|
-
nodeLineWidth: STROKE_WIDTHS.outerBox,
|
|
46
|
-
edgeLineWidth: STROKE_WIDTHS.connector,
|
|
47
|
-
groupCornerRadius: 0,
|
|
48
|
-
groupPaddingX: CLS.boxPadX,
|
|
49
|
-
groupPaddingY: CLS.sectionPadY,
|
|
50
|
-
groupLineWidth: STROKE_WIDTHS.outerBox,
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
type ClassSizeMap = Map<string, { width: number; height: number; headerHeight: number; attrHeight: number; methodHeight: number }>
|
|
54
|
-
|
|
55
|
-
/** Build ELK graph and size map from a class diagram. */
|
|
56
|
-
function buildClassElkGraph(
|
|
57
|
-
diagram: ClassDiagram,
|
|
58
|
-
options: RenderOptions
|
|
59
|
-
): { elkGraph: ElkNode; classSizes: ClassSizeMap } {
|
|
60
|
-
const style = resolveRenderStyle(options, CLASS_STYLE_DEFAULTS)
|
|
61
|
-
const classSizes: ClassSizeMap = new Map()
|
|
62
|
-
|
|
63
|
-
for (const cls of diagram.classes) {
|
|
64
|
-
const label = applyTextTransform(cls.label, style.nodeTextTransform)
|
|
65
|
-
const headerBaseHeight = Math.max(
|
|
66
|
-
CLS.headerBaseHeight,
|
|
67
|
-
measureMultilineText(label, style.nodeLabelFontSize, style.nodeLabelFontWeight).height + style.nodePaddingY * 2,
|
|
68
|
-
)
|
|
69
|
-
const headerHeight = cls.annotation
|
|
70
|
-
? headerBaseHeight + CLS.annotationHeight
|
|
71
|
-
: headerBaseHeight
|
|
72
|
-
|
|
73
|
-
const attrHeight = cls.attributes.length > 0
|
|
74
|
-
? cls.attributes.length * CLS.memberRowHeight + style.nodePaddingY
|
|
75
|
-
: CLS.emptySectionHeight
|
|
76
|
-
|
|
77
|
-
const methodHeight = cls.methods.length > 0
|
|
78
|
-
? cls.methods.length * CLS.memberRowHeight + style.nodePaddingY
|
|
79
|
-
: CLS.emptySectionHeight
|
|
80
|
-
|
|
81
|
-
const headerTextW = estimateTextWidth(label, style.nodeLabelFontSize, style.nodeLabelFontWeight)
|
|
82
|
-
const maxAttrW = maxMemberWidth(cls.attributes)
|
|
83
|
-
const maxMethodW = maxMemberWidth(cls.methods)
|
|
84
|
-
const width = Math.max(CLS.minWidth, headerTextW + style.nodePaddingX * 2, maxAttrW + style.nodePaddingX * 2, maxMethodW + style.nodePaddingX * 2)
|
|
85
|
-
const height = headerHeight + attrHeight + methodHeight
|
|
86
|
-
|
|
87
|
-
classSizes.set(cls.id, { width, height, headerHeight, attrHeight, methodHeight })
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const elkGraph: ElkNode = {
|
|
91
|
-
id: 'root',
|
|
92
|
-
layoutOptions: {
|
|
93
|
-
'elk.algorithm': 'layered',
|
|
94
|
-
'elk.direction': 'DOWN',
|
|
95
|
-
'elk.spacing.nodeNode': String(CLS.nodeSpacing),
|
|
96
|
-
'elk.layered.spacing.nodeNodeBetweenLayers': String(CLS.layerSpacing),
|
|
97
|
-
'elk.padding': `[top=${CLS.padding},left=${CLS.padding},bottom=${CLS.padding},right=${CLS.padding}]`,
|
|
98
|
-
'elk.edgeRouting': 'ORTHOGONAL',
|
|
99
|
-
'elk.edgeLabels.placement': 'CENTER',
|
|
100
|
-
},
|
|
101
|
-
children: [],
|
|
102
|
-
edges: [],
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
for (const cls of diagram.classes) {
|
|
106
|
-
const size = classSizes.get(cls.id)!
|
|
107
|
-
elkGraph.children!.push({ id: cls.id, width: size.width, height: size.height })
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
for (let i = 0; i < diagram.relationships.length; i++) {
|
|
111
|
-
const rel = diagram.relationships[i]!
|
|
112
|
-
const edge: ElkExtendedEdge = { id: `e${i}`, sources: [rel.from], targets: [rel.to] }
|
|
113
|
-
if (rel.label) {
|
|
114
|
-
const label = applyTextTransform(rel.label, style.edgeTextTransform)
|
|
115
|
-
const metrics = measureMultilineText(label, style.edgeLabelFontSize, style.edgeLabelFontWeight)
|
|
116
|
-
edge.labels = [{ text: label, width: metrics.width + 8, height: metrics.height + 6 }]
|
|
117
|
-
}
|
|
118
|
-
elkGraph.edges!.push(edge)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return { elkGraph, classSizes }
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Extract positioned classes and relationships from ELK result. */
|
|
125
|
-
function extractClassLayout(
|
|
126
|
-
result: ElkNode,
|
|
127
|
-
diagram: ClassDiagram,
|
|
128
|
-
classSizes: ClassSizeMap
|
|
129
|
-
): PositionedClassDiagram {
|
|
130
|
-
const classLookup = new Map<string, ClassNode>()
|
|
131
|
-
for (const cls of diagram.classes) classLookup.set(cls.id, cls)
|
|
132
|
-
|
|
133
|
-
const positionedClasses: PositionedClassNode[] = []
|
|
134
|
-
for (const child of result.children ?? []) {
|
|
135
|
-
const cls = classLookup.get(child.id)
|
|
136
|
-
if (cls) {
|
|
137
|
-
const size = classSizes.get(cls.id)!
|
|
138
|
-
positionedClasses.push({
|
|
139
|
-
id: cls.id,
|
|
140
|
-
label: cls.label,
|
|
141
|
-
annotation: cls.annotation,
|
|
142
|
-
attributes: cls.attributes,
|
|
143
|
-
methods: cls.methods,
|
|
144
|
-
x: child.x ?? 0,
|
|
145
|
-
y: child.y ?? 0,
|
|
146
|
-
width: child.width ?? size.width,
|
|
147
|
-
height: child.height ?? size.height,
|
|
148
|
-
headerHeight: size.headerHeight,
|
|
149
|
-
attrHeight: size.attrHeight,
|
|
150
|
-
methodHeight: size.methodHeight,
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const relationships: PositionedClassRelationship[] = []
|
|
156
|
-
for (let i = 0; i < (result.edges?.length ?? 0); i++) {
|
|
157
|
-
const elkEdge = result.edges![i]!
|
|
158
|
-
const rel = diagram.relationships[i]!
|
|
159
|
-
|
|
160
|
-
const points: Point[] = []
|
|
161
|
-
if (elkEdge.sections && elkEdge.sections.length > 0) {
|
|
162
|
-
const section = elkEdge.sections[0]!
|
|
163
|
-
points.push({ x: section.startPoint.x, y: section.startPoint.y })
|
|
164
|
-
if (section.bendPoints) {
|
|
165
|
-
for (const bp of section.bendPoints) {
|
|
166
|
-
points.push({ x: bp.x, y: bp.y })
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
points.push({ x: section.endPoint.x, y: section.endPoint.y })
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
let labelPosition: Point | undefined
|
|
173
|
-
if (elkEdge.labels && elkEdge.labels.length > 0) {
|
|
174
|
-
const label = elkEdge.labels[0]!
|
|
175
|
-
if (label.x != null && label.y != null) {
|
|
176
|
-
labelPosition = {
|
|
177
|
-
x: label.x + (label.width ?? 0) / 2,
|
|
178
|
-
y: label.y + (label.height ?? 0) / 2,
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
relationships.push({
|
|
184
|
-
from: rel.from,
|
|
185
|
-
to: rel.to,
|
|
186
|
-
type: rel.type,
|
|
187
|
-
markerAt: rel.markerAt,
|
|
188
|
-
label: rel.label,
|
|
189
|
-
fromCardinality: rel.fromCardinality,
|
|
190
|
-
toCardinality: rel.toCardinality,
|
|
191
|
-
points,
|
|
192
|
-
labelPosition,
|
|
193
|
-
})
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return {
|
|
197
|
-
width: result.width ?? 600,
|
|
198
|
-
height: result.height ?? 400,
|
|
199
|
-
accessibilityTitle: diagram.accessibilityTitle,
|
|
200
|
-
accessibilityDescription: diagram.accessibilityDescription,
|
|
201
|
-
classes: positionedClasses,
|
|
202
|
-
relationships,
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Lay out a parsed class diagram using ELK.js.
|
|
208
|
-
*/
|
|
209
|
-
export function layoutClassDiagram(
|
|
210
|
-
diagram: ClassDiagram,
|
|
211
|
-
options: RenderOptions = {}
|
|
212
|
-
): PositionedClassDiagram {
|
|
213
|
-
if (diagram.classes.length === 0) {
|
|
214
|
-
return { width: 0, height: 0, accessibilityTitle: diagram.accessibilityTitle, accessibilityDescription: diagram.accessibilityDescription, classes: [], relationships: [] }
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const { elkGraph, classSizes } = buildClassElkGraph(diagram, options)
|
|
218
|
-
const result = elkLayoutSync(elkGraph)
|
|
219
|
-
return extractClassLayout(result, diagram, classSizes)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/** Calculate the max width of a list of class members (uses mono metrics) */
|
|
223
|
-
function maxMemberWidth(members: ClassMember[]): number {
|
|
224
|
-
if (members.length === 0) return 0
|
|
225
|
-
let maxW = 0
|
|
226
|
-
for (const m of members) {
|
|
227
|
-
const text = memberToString(m)
|
|
228
|
-
const w = estimateMonoTextWidth(text, CLS.memberFontSize)
|
|
229
|
-
if (w > maxW) maxW = w
|
|
230
|
-
}
|
|
231
|
-
return maxW
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** Convert a class member to its display string */
|
|
235
|
-
export function memberToString(m: ClassMember): string {
|
|
236
|
-
const vis = m.visibility ? `${m.visibility} ` : ''
|
|
237
|
-
const name = m.isMethod ? `${m.name}(${m.params || ''})` : m.name
|
|
238
|
-
const type = m.type ? `: ${m.type}` : ''
|
|
239
|
-
return `${vis}${name}${type}`
|
|
240
|
-
}
|
package/src/class/parser.ts
DELETED
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
import type { ClassDiagram, ClassNode, ClassRelationship, ClassMember, RelationshipType, ClassNamespace } from './types.ts'
|
|
2
|
-
import { normalizeBrTags } from '../multiline-utils.ts'
|
|
3
|
-
|
|
4
|
-
// ============================================================================
|
|
5
|
-
// Class diagram parser
|
|
6
|
-
//
|
|
7
|
-
// Parses Mermaid classDiagram syntax into a ClassDiagram structure.
|
|
8
|
-
//
|
|
9
|
-
// Supported syntax:
|
|
10
|
-
// class Animal { +String name; +eat() void }
|
|
11
|
-
// class Shape { <<abstract>> }
|
|
12
|
-
// Animal <|-- Dog (inheritance)
|
|
13
|
-
// Car *-- Engine (composition)
|
|
14
|
-
// Car o-- Wheel (aggregation)
|
|
15
|
-
// A --> B (association)
|
|
16
|
-
// A ..> B (dependency)
|
|
17
|
-
// A ..|> B (realization)
|
|
18
|
-
// A "1" --> "*" B : label (with cardinality + label)
|
|
19
|
-
// Animal : +String name (inline attribute)
|
|
20
|
-
// namespace MyNamespace { class A { } }
|
|
21
|
-
// ============================================================================
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Parse a Mermaid class diagram.
|
|
25
|
-
* Expects the first line to be "classDiagram".
|
|
26
|
-
*/
|
|
27
|
-
export function parseClassDiagram(lines: string[]): ClassDiagram {
|
|
28
|
-
const diagram: ClassDiagram = {
|
|
29
|
-
classes: [],
|
|
30
|
-
relationships: [],
|
|
31
|
-
namespaces: [],
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Track classes by ID for deduplication
|
|
35
|
-
const classMap = new Map<string, ClassNode>()
|
|
36
|
-
// Track namespace nesting
|
|
37
|
-
let currentNamespace: ClassNamespace | null = null
|
|
38
|
-
// Track class body parsing
|
|
39
|
-
let currentClass: ClassNode | null = null
|
|
40
|
-
let braceDepth = 0
|
|
41
|
-
|
|
42
|
-
for (let i = 1; i < lines.length; i++) {
|
|
43
|
-
const line = lines[i]!
|
|
44
|
-
|
|
45
|
-
const accTitle = parseAccessibilityLine(line, 'accTitle')
|
|
46
|
-
if (accTitle !== undefined) {
|
|
47
|
-
diagram.accessibilityTitle = accTitle
|
|
48
|
-
continue
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const accDescrStart = line.match(/^accDescr\s*:?\s*\{\s*(.*)$/i)
|
|
52
|
-
if (accDescrStart) {
|
|
53
|
-
const parsed = collectAccessibilityBlock(accDescrStart[1] ?? '', lines, i)
|
|
54
|
-
diagram.accessibilityDescription = normalizeBrTags(parsed.text)
|
|
55
|
-
i = parsed.nextIndex
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const accDescr = parseAccessibilityLine(line, 'accDescr')
|
|
60
|
-
if (accDescr !== undefined) {
|
|
61
|
-
diagram.accessibilityDescription = accDescr
|
|
62
|
-
continue
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// --- Inside a class body block ---
|
|
66
|
-
if (currentClass && braceDepth > 0) {
|
|
67
|
-
if (line === '}') {
|
|
68
|
-
braceDepth--
|
|
69
|
-
if (braceDepth === 0) {
|
|
70
|
-
currentClass = null
|
|
71
|
-
}
|
|
72
|
-
continue
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Check for annotation like <<interface>>
|
|
76
|
-
const annotMatch = line.match(/^<<(\w+)>>$/)
|
|
77
|
-
if (annotMatch) {
|
|
78
|
-
currentClass.annotation = annotMatch[1]!
|
|
79
|
-
continue
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Parse member: visibility, name, type, optional parens for method
|
|
83
|
-
const member = parseMember(line)
|
|
84
|
-
if (member) {
|
|
85
|
-
if (member.isMethod) {
|
|
86
|
-
currentClass.methods.push(member.member)
|
|
87
|
-
} else {
|
|
88
|
-
currentClass.attributes.push(member.member)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
continue
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// --- Namespace block start ---
|
|
95
|
-
const nsMatch = line.match(/^namespace\s+(\S+)\s*\{$/)
|
|
96
|
-
if (nsMatch) {
|
|
97
|
-
currentNamespace = { name: nsMatch[1]!, classIds: [] }
|
|
98
|
-
continue
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// --- Namespace end ---
|
|
102
|
-
if (line === '}' && currentNamespace) {
|
|
103
|
-
diagram.namespaces.push(currentNamespace)
|
|
104
|
-
currentNamespace = null
|
|
105
|
-
continue
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// --- Class block start: `class ClassName {` or `class ClassName` ---
|
|
109
|
-
const classBlockMatch = line.match(/^class\s+(\S+?)(?:\s*~(\w+)~)?\s*\{$/)
|
|
110
|
-
if (classBlockMatch) {
|
|
111
|
-
const id = classBlockMatch[1]!
|
|
112
|
-
const generic = classBlockMatch[2]
|
|
113
|
-
const cls = ensureClass(classMap, id)
|
|
114
|
-
if (generic) {
|
|
115
|
-
cls.label = `${id}<${generic}>`
|
|
116
|
-
}
|
|
117
|
-
currentClass = cls
|
|
118
|
-
braceDepth = 1
|
|
119
|
-
if (currentNamespace) {
|
|
120
|
-
currentNamespace.classIds.push(id)
|
|
121
|
-
}
|
|
122
|
-
continue
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// --- Standalone class declaration (no body): `class ClassName` ---
|
|
126
|
-
const classOnlyMatch = line.match(/^class\s+(\S+?)(?:\s*~(\w+)~)?\s*$/)
|
|
127
|
-
if (classOnlyMatch) {
|
|
128
|
-
const id = classOnlyMatch[1]!
|
|
129
|
-
const generic = classOnlyMatch[2]
|
|
130
|
-
const cls = ensureClass(classMap, id)
|
|
131
|
-
if (generic) {
|
|
132
|
-
cls.label = `${id}<${generic}>`
|
|
133
|
-
}
|
|
134
|
-
if (currentNamespace) {
|
|
135
|
-
currentNamespace.classIds.push(id)
|
|
136
|
-
}
|
|
137
|
-
continue
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// --- Inline annotation: `class ClassName { <<interface>> }` (single line) ---
|
|
141
|
-
const inlineAnnotMatch = line.match(/^class\s+(\S+?)\s*\{\s*<<(\w+)>>\s*\}$/)
|
|
142
|
-
if (inlineAnnotMatch) {
|
|
143
|
-
const cls = ensureClass(classMap, inlineAnnotMatch[1]!)
|
|
144
|
-
cls.annotation = inlineAnnotMatch[2]!
|
|
145
|
-
continue
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// --- Inline attribute: `ClassName : +String name` ---
|
|
149
|
-
const inlineAttrMatch = line.match(/^(\S+?)\s*:\s*(.+)$/)
|
|
150
|
-
if (inlineAttrMatch) {
|
|
151
|
-
// Make sure this isn't a relationship line (those have arrows)
|
|
152
|
-
const rest = inlineAttrMatch[2]!
|
|
153
|
-
if (!rest.match(/<\|--|--|\*--|o--|-->|\.\.>|\.\.\|>/)) {
|
|
154
|
-
const cls = ensureClass(classMap, inlineAttrMatch[1]!)
|
|
155
|
-
const member = parseMember(rest)
|
|
156
|
-
if (member) {
|
|
157
|
-
if (member.isMethod) {
|
|
158
|
-
cls.methods.push(member.member)
|
|
159
|
-
} else {
|
|
160
|
-
cls.attributes.push(member.member)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
continue
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// --- Relationship ---
|
|
168
|
-
// Pattern: [FROM] ["card"] ARROW ["card"] [TO] [: label]
|
|
169
|
-
// Arrows: <|--, *--, o--, -->, ..|>, ..>
|
|
170
|
-
// Can also be reversed: --o, --*, --|>
|
|
171
|
-
const rel = parseRelationship(line)
|
|
172
|
-
if (rel) {
|
|
173
|
-
// Ensure both classes exist
|
|
174
|
-
ensureClass(classMap, rel.from)
|
|
175
|
-
ensureClass(classMap, rel.to)
|
|
176
|
-
diagram.relationships.push(rel)
|
|
177
|
-
continue
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
diagram.classes = [...classMap.values()]
|
|
182
|
-
return diagram
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function parseAccessibilityLine(line: string, directive: 'accTitle' | 'accDescr'): string | undefined {
|
|
186
|
-
const match = line.match(new RegExp(`^${directive}\\s*:?[ \\t]+(.+)$`, 'i'))
|
|
187
|
-
return match ? normalizeBrTags(match[1]!.trim()) : undefined
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function collectAccessibilityBlock(initial: string, lines: string[], startIndex: number): { text: string; nextIndex: number } {
|
|
191
|
-
const initialEnd = initial.indexOf('}')
|
|
192
|
-
if (initialEnd !== -1) return { text: initial.slice(0, initialEnd).trim(), nextIndex: startIndex }
|
|
193
|
-
const parts = [initial.trim()].filter(Boolean)
|
|
194
|
-
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
195
|
-
const line = lines[i]!
|
|
196
|
-
const end = line.indexOf('}')
|
|
197
|
-
if (end !== -1) {
|
|
198
|
-
const beforeBrace = line.slice(0, end).trim()
|
|
199
|
-
if (beforeBrace) parts.push(beforeBrace)
|
|
200
|
-
return { text: parts.join('\n'), nextIndex: i }
|
|
201
|
-
}
|
|
202
|
-
parts.push(line)
|
|
203
|
-
}
|
|
204
|
-
throw new Error('Class accDescr block is missing a closing "}"')
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/** Ensure a class exists in the map, creating a default if needed */
|
|
208
|
-
function ensureClass(classMap: Map<string, ClassNode>, id: string): ClassNode {
|
|
209
|
-
let cls = classMap.get(id)
|
|
210
|
-
if (!cls) {
|
|
211
|
-
cls = { id, label: id, attributes: [], methods: [] }
|
|
212
|
-
classMap.set(id, cls)
|
|
213
|
-
}
|
|
214
|
-
return cls
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/** Parse a class member line (attribute or method) */
|
|
218
|
-
function parseMember(line: string): { member: ClassMember; isMethod: boolean } | null {
|
|
219
|
-
const trimmed = line.trim().replace(/;$/, '')
|
|
220
|
-
if (!trimmed) return null
|
|
221
|
-
|
|
222
|
-
// Extract visibility prefix
|
|
223
|
-
let visibility: ClassMember['visibility'] = ''
|
|
224
|
-
let rest = trimmed
|
|
225
|
-
if (/^[+\-#~]/.test(rest)) {
|
|
226
|
-
visibility = rest[0] as ClassMember['visibility']
|
|
227
|
-
rest = rest.slice(1).trim()
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Check if it's a method (has parentheses)
|
|
231
|
-
const methodMatch = rest.match(/^(.+?)\(([^)]*)\)(?:\s*(.+))?$/)
|
|
232
|
-
if (methodMatch) {
|
|
233
|
-
const name = methodMatch[1]!.trim()
|
|
234
|
-
const params = methodMatch[2]?.trim() || undefined // Store the parameter string
|
|
235
|
-
const type = methodMatch[3]?.trim()
|
|
236
|
-
// Check for static ($) or abstract (*) markers
|
|
237
|
-
const isStatic = name.endsWith('$') || rest.includes('$')
|
|
238
|
-
const isAbstract = name.endsWith('*') || rest.includes('*')
|
|
239
|
-
return {
|
|
240
|
-
member: {
|
|
241
|
-
visibility,
|
|
242
|
-
name: name.replace(/[$*]$/, ''),
|
|
243
|
-
type: type || undefined,
|
|
244
|
-
isStatic,
|
|
245
|
-
isAbstract,
|
|
246
|
-
isMethod: true,
|
|
247
|
-
params,
|
|
248
|
-
},
|
|
249
|
-
isMethod: true,
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// It's an attribute: [Type] name or name Type
|
|
254
|
-
// Common patterns: "String name", "+int age", "name"
|
|
255
|
-
const parts = rest.split(/\s+/)
|
|
256
|
-
let name: string
|
|
257
|
-
let type: string | undefined
|
|
258
|
-
|
|
259
|
-
if (parts.length >= 2) {
|
|
260
|
-
// "Type name" pattern
|
|
261
|
-
type = parts[0]
|
|
262
|
-
name = parts.slice(1).join(' ')
|
|
263
|
-
} else {
|
|
264
|
-
name = parts[0] ?? rest
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const isStatic = name.endsWith('$')
|
|
268
|
-
const isAbstract = name.endsWith('*')
|
|
269
|
-
|
|
270
|
-
return {
|
|
271
|
-
member: {
|
|
272
|
-
visibility,
|
|
273
|
-
name: name.replace(/[$*]$/, ''),
|
|
274
|
-
type: type || undefined,
|
|
275
|
-
isStatic,
|
|
276
|
-
isAbstract,
|
|
277
|
-
isMethod: false,
|
|
278
|
-
},
|
|
279
|
-
isMethod: false,
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/** Parse a relationship line into a ClassRelationship */
|
|
284
|
-
function parseRelationship(line: string): ClassRelationship | null {
|
|
285
|
-
// Relationship regex — handles all arrow types with optional cardinality and labels
|
|
286
|
-
// Pattern: FROM ["card"] ARROW ["card"] TO [: label]
|
|
287
|
-
const match = line.match(
|
|
288
|
-
/^(\S+?)\s+(?:"([^"]*?)"\s+)?(<\|--|<\|\.\.|\*--|o--|-->|--\*|--o|--\|>|\.\.>|\.\.\|>|<--|<\.\.?|--)\s+(?:"([^"]*?)"\s+)?(\S+?)(?:\s*:\s*(.+))?$/
|
|
289
|
-
)
|
|
290
|
-
if (!match) return null
|
|
291
|
-
|
|
292
|
-
const from = match[1]!
|
|
293
|
-
const rawFromCardinality = match[2]
|
|
294
|
-
const fromCardinality = rawFromCardinality ? normalizeBrTags(rawFromCardinality) : undefined
|
|
295
|
-
const arrow = match[3]!.trim()
|
|
296
|
-
const rawToCardinality = match[4]
|
|
297
|
-
const toCardinality = rawToCardinality ? normalizeBrTags(rawToCardinality) : undefined
|
|
298
|
-
const to = match[5]!
|
|
299
|
-
const rawLabel = match[6]?.trim()
|
|
300
|
-
const label = rawLabel ? normalizeBrTags(rawLabel) : undefined
|
|
301
|
-
|
|
302
|
-
const parsed = parseArrow(arrow)
|
|
303
|
-
if (!parsed) return null
|
|
304
|
-
|
|
305
|
-
return { from, to, type: parsed.type, markerAt: parsed.markerAt, label, fromCardinality, toCardinality }
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Map arrow syntax to relationship type and marker placement side.
|
|
310
|
-
* Prefix markers (`<|--`, `*--`, `o--`) place the UML shape at the 'from' end.
|
|
311
|
-
* Suffix markers (`..|>`, `-->`, `..>`, `--*`, `--o`) place it at the 'to' end.
|
|
312
|
-
*/
|
|
313
|
-
function parseArrow(arrow: string): { type: RelationshipType; markerAt: 'from' | 'to' } | null {
|
|
314
|
-
// Trim whitespace that might be captured by the regex
|
|
315
|
-
const a = arrow.trim()
|
|
316
|
-
switch (a) {
|
|
317
|
-
case '<|--': return { type: 'inheritance', markerAt: 'from' }
|
|
318
|
-
case '--|>': return { type: 'inheritance', markerAt: 'to' }
|
|
319
|
-
case '<|..': return { type: 'realization', markerAt: 'from' }
|
|
320
|
-
case '..|>': return { type: 'realization', markerAt: 'to' }
|
|
321
|
-
case '*--': return { type: 'composition', markerAt: 'from' }
|
|
322
|
-
case '--*': return { type: 'composition', markerAt: 'to' }
|
|
323
|
-
case 'o--': return { type: 'aggregation', markerAt: 'from' }
|
|
324
|
-
case '--o': return { type: 'aggregation', markerAt: 'to' }
|
|
325
|
-
case '-->': return { type: 'association', markerAt: 'to' }
|
|
326
|
-
case '<--': return { type: 'association', markerAt: 'from' }
|
|
327
|
-
case '..>': return { type: 'dependency', markerAt: 'to' }
|
|
328
|
-
case '<..': return { type: 'dependency', markerAt: 'from' }
|
|
329
|
-
case '--': return { type: 'association', markerAt: 'to' }
|
|
330
|
-
default: return null
|
|
331
|
-
}
|
|
332
|
-
}
|