sirena 0.1.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.
- checksums.yaml +7 -0
- data/.github/workflows/build_deploy.yml +59 -0
- data/.github/workflows/links.yml +85 -0
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +27 -0
- data/.gitignore +68 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/.rubocop_todo.yml +70 -0
- data/ARCHITECTURE.md +744 -0
- data/Gemfile +12 -0
- data/LICENSE +25 -0
- data/README.adoc +357 -0
- data/Rakefile +11 -0
- data/docs/.gitignore +1 -0
- data/docs/Gemfile +13 -0
- data/docs/_config.yml +182 -0
- data/docs/_diagram_types/architecture-diagram.adoc +314 -0
- data/docs/_diagram_types/block-diagram.adoc +345 -0
- data/docs/_diagram_types/c4-diagram.adoc +559 -0
- data/docs/_diagram_types/class-diagram.adoc +816 -0
- data/docs/_diagram_types/er-diagram.adoc +719 -0
- data/docs/_diagram_types/error-diagram.adoc +114 -0
- data/docs/_diagram_types/examples/flowchart-examples.adoc +29 -0
- data/docs/_diagram_types/flowchart.adoc +488 -0
- data/docs/_diagram_types/gantt-chart.adoc +502 -0
- data/docs/_diagram_types/git-graph.adoc +600 -0
- data/docs/_diagram_types/index.adoc +192 -0
- data/docs/_diagram_types/info-diagram.adoc +103 -0
- data/docs/_diagram_types/kanban-diagram.adoc +262 -0
- data/docs/_diagram_types/mindmap.adoc +603 -0
- data/docs/_diagram_types/packet-diagram.adoc +378 -0
- data/docs/_diagram_types/pie-chart.adoc +335 -0
- data/docs/_diagram_types/quadrant-chart.adoc +406 -0
- data/docs/_diagram_types/radar-chart.adoc +528 -0
- data/docs/_diagram_types/requirement-diagram.adoc +416 -0
- data/docs/_diagram_types/sankey-diagram.adoc +357 -0
- data/docs/_diagram_types/sequence-diagram.adoc +664 -0
- data/docs/_diagram_types/state-diagram.adoc +658 -0
- data/docs/_diagram_types/timeline.adoc +352 -0
- data/docs/_diagram_types/treemap-diagram.adoc +462 -0
- data/docs/_diagram_types/user-journey.adoc +602 -0
- data/docs/_features/index.adoc +129 -0
- data/docs/_guides/cli-reference.adoc +203 -0
- data/docs/_guides/index.adoc +56 -0
- data/docs/_guides/installation.adoc +100 -0
- data/docs/_guides/quick-start.adoc +132 -0
- data/docs/_pages/comparison.adoc +441 -0
- data/docs/_pages/compatibility.adoc +300 -0
- data/docs/_pages/index.adoc +39 -0
- data/docs/_references/index.adoc +103 -0
- data/docs/_tutorials/index.adoc +57 -0
- data/docs/index.adoc +166 -0
- data/docs/lychee.toml +54 -0
- data/examples/.gitignore +10 -0
- data/examples/README.adoc +196 -0
- data/examples/README.md +64 -0
- data/examples/architecture/01-basic-services.mmd +9 -0
- data/examples/architecture/01-basic-services.svg +37 -0
- data/examples/architecture/02-service-groups.mmd +16 -0
- data/examples/architecture/02-service-groups.svg +55 -0
- data/examples/architecture/README.adoc +79 -0
- data/examples/block/01-basic-blocks.mmd +13 -0
- data/examples/block/01-basic-blocks.svg +44 -0
- data/examples/block/02-block-shapes.mmd +13 -0
- data/examples/block/02-block-shapes.svg +47 -0
- data/examples/block/README.adoc +85 -0
- data/examples/c4/01-context-diagram.mmd +10 -0
- data/examples/c4/01-context-diagram.svg +45 -0
- data/examples/c4/02-container-diagram.mmd +24 -0
- data/examples/c4/02-container-diagram.svg +105 -0
- data/examples/c4/README.adoc +92 -0
- data/examples/class_diagram/01-basic-classes.mmd +61 -0
- data/examples/class_diagram/01-basic-classes.svg +117 -0
- data/examples/class_diagram/02-relationships.mmd +61 -0
- data/examples/class_diagram/02-relationships.svg +129 -0
- data/examples/class_diagram/README.adoc +93 -0
- data/examples/er_diagram/01-basic-entities.mmd +64 -0
- data/examples/er_diagram/01-basic-entities.svg +5 -0
- data/examples/er_diagram/02-cardinality.mmd +57 -0
- data/examples/er_diagram/02-cardinality.svg +125 -0
- data/examples/er_diagram/README.adoc +88 -0
- data/examples/error/01-basic-error.mmd +1 -0
- data/examples/error/01-basic-error.svg +13 -0
- data/examples/error/02-error-display.mmd +1 -0
- data/examples/error/02-error-display.svg +13 -0
- data/examples/error/README.adoc +71 -0
- data/examples/error_message_example.svg +13 -0
- data/examples/flowchart/00-original.mmd +13 -0
- data/examples/flowchart/00-original.svg +5 -0
- data/examples/flowchart/01-basic-flow.mmd +7 -0
- data/examples/flowchart/01-basic-flow.svg +52 -0
- data/examples/flowchart/01-basic-flow.yml +13 -0
- data/examples/flowchart/02*.svg +87 -0
- data/examples/flowchart/02-node-shapes.mmd +9 -0
- data/examples/flowchart/02-node-shapes.svg +33 -0
- data/examples/flowchart/03-edge-types.mmd +7 -0
- data/examples/flowchart/03-edge-types.svg +53 -0
- data/examples/flowchart/04-subgraphs.mmd +9 -0
- data/examples/flowchart/04-subgraphs.svg +33 -0
- data/examples/flowchart/05-styling.mmd +9 -0
- data/examples/flowchart/05-styling.svg +33 -0
- data/examples/flowchart/06-complex-flow.mmd +8 -0
- data/examples/flowchart/06-complex-flow.svg +59 -0
- data/examples/flowchart/README.adoc +167 -0
- data/examples/gantt/01-simple-timeline.* +14 -0
- data/examples/gantt/01-simple-timeline.mmd +6 -0
- data/examples/gantt/01-simple-timeline.svg +26 -0
- data/examples/gantt/02-task-dependencies.mmd +6 -0
- data/examples/gantt/02-task-dependencies.svg +26 -0
- data/examples/gantt/README.adoc +86 -0
- data/examples/git_graph/01-linear-history.mmd +12 -0
- data/examples/git_graph/01-linear-history.svg +26 -0
- data/examples/git_graph/02-branching.mmd +12 -0
- data/examples/git_graph/02-branching.svg +26 -0
- data/examples/git_graph/README.adoc +73 -0
- data/examples/info/02-showinfo.mmd +1 -0
- data/examples/info/02-showinfo.svg +10 -0
- data/examples/info/README.adoc +58 -0
- data/examples/info_showinfo_example.svg +10 -0
- data/examples/kanban/01-simple-board.mmd +8 -0
- data/examples/kanban/01-simple-board.svg +43 -0
- data/examples/kanban/02-workflow.mmd +8 -0
- data/examples/kanban/02-workflow.svg +43 -0
- data/examples/kanban/README.adoc +79 -0
- data/examples/mindmap/01-simple-tree.mmd +19 -0
- data/examples/mindmap/01-simple-tree.svg +61 -0
- data/examples/mindmap/02-knowledge-map.mmd +19 -0
- data/examples/mindmap/02-knowledge-map.svg +61 -0
- data/examples/mindmap/README.adoc +77 -0
- data/examples/packet/01-basic-packet.* +17 -0
- data/examples/packet/01-basic-packet.mmd +4 -0
- data/examples/packet/01-basic-packet.svg +82 -0
- data/examples/packet/README.adoc +58 -0
- data/examples/pie/01-simple-chart.mmd +5 -0
- data/examples/pie/01-simple-chart.svg +17 -0
- data/examples/pie/02-labeled-slices.mmd +6 -0
- data/examples/pie/02-labeled-slices.svg +19 -0
- data/examples/pie/README.adoc +75 -0
- data/examples/quadrant/01-basic-quadrant.mmd +13 -0
- data/examples/quadrant/01-basic-quadrant.svg +33 -0
- data/examples/quadrant/02-positioned-items.mmd +14 -0
- data/examples/quadrant/02-positioned-items.svg +35 -0
- data/examples/quadrant/README.adoc +84 -0
- data/examples/radar/01-simple-radar.* +5 -0
- data/examples/radar/01-simple-radar.mmd +3 -0
- data/examples/radar/01-simple-radar.svg +25 -0
- data/examples/radar/02-multiple-curves.mmd +4 -0
- data/examples/radar/02-multiple-curves.svg +43 -0
- data/examples/radar/README.adoc +75 -0
- data/examples/requirement/01-basic-requirements.mmd +23 -0
- data/examples/requirement/01-basic-requirements.svg +49 -0
- data/examples/requirement/02-risk-levels.mmd +23 -0
- data/examples/requirement/02-risk-levels.svg +49 -0
- data/examples/requirement/README.adoc +85 -0
- data/examples/sankey/01-simple-flow.mmd +7 -0
- data/examples/sankey/01-simple-flow.svg +34 -0
- data/examples/sankey/02-multi-stage.mmd +11 -0
- data/examples/sankey/02-multi-stage.svg +44 -0
- data/examples/sankey/README.adoc +74 -0
- data/examples/sequence/01-basic-sequence.mmd +27 -0
- data/examples/sequence/01-basic-sequence.svg +5 -0
- data/examples/sequence/02-activations.mmd +17 -0
- data/examples/sequence/02-activations.svg +78 -0
- data/examples/sequence/README.adoc +86 -0
- data/examples/state_diagram/01-simple-states.mmd +29 -0
- data/examples/state_diagram/01-simple-states.svg +5 -0
- data/examples/state_diagram/02-composite.mmd +19 -0
- data/examples/state_diagram/02-composite.svg +81 -0
- data/examples/state_diagram/README.adoc +90 -0
- data/examples/timeline/01-simple-timeline.mmd +11 -0
- data/examples/timeline/01-simple-timeline.svg +36 -0
- data/examples/timeline/02-periods.mmd +15 -0
- data/examples/timeline/02-periods.svg +47 -0
- data/examples/timeline/README.adoc +78 -0
- data/examples/treemap/01-basic-treemap.mmd +12 -0
- data/examples/treemap/01-basic-treemap.svg +59 -0
- data/examples/treemap/README.adoc +59 -0
- data/examples/user_journey/01-simple-journey.mmd +23 -0
- data/examples/user_journey/01-simple-journey.svg +5 -0
- data/examples/user_journey/02-multi-actor.mmd +18 -0
- data/examples/user_journey/02-multi-actor.svg +129 -0
- data/examples/user_journey/README.adoc +81 -0
- data/examples/xychart/01-line-chart.mmd +5 -0
- data/examples/xychart/01-line-chart.svg +43 -0
- data/examples/xychart/02-bar-chart.mmd +7 -0
- data/examples/xychart/02-bar-chart.svg +48 -0
- data/examples/xychart/README.adoc +80 -0
- data/exe/sirena +7 -0
- data/lib/sirena/cli.rb +138 -0
- data/lib/sirena/commands/batch.rb +117 -0
- data/lib/sirena/commands/render.rb +80 -0
- data/lib/sirena/commands/types.rb +29 -0
- data/lib/sirena/commands/version.rb +24 -0
- data/lib/sirena/diagram/architecture.rb +46 -0
- data/lib/sirena/diagram/base.rb +61 -0
- data/lib/sirena/diagram/block.rb +81 -0
- data/lib/sirena/diagram/c4.rb +328 -0
- data/lib/sirena/diagram/class_diagram.rb +385 -0
- data/lib/sirena/diagram/er_diagram.rb +238 -0
- data/lib/sirena/diagram/error.rb +38 -0
- data/lib/sirena/diagram/flowchart.rb +160 -0
- data/lib/sirena/diagram/gantt.rb +71 -0
- data/lib/sirena/diagram/git_graph.rb +36 -0
- data/lib/sirena/diagram/info.rb +38 -0
- data/lib/sirena/diagram/kanban.rb +178 -0
- data/lib/sirena/diagram/mindmap.rb +54 -0
- data/lib/sirena/diagram/packet.rb +79 -0
- data/lib/sirena/diagram/pie.rb +115 -0
- data/lib/sirena/diagram/quadrant.rb +138 -0
- data/lib/sirena/diagram/radar.rb +52 -0
- data/lib/sirena/diagram/requirement.rb +133 -0
- data/lib/sirena/diagram/sankey.rb +217 -0
- data/lib/sirena/diagram/sequence.rb +242 -0
- data/lib/sirena/diagram/state_diagram.rb +237 -0
- data/lib/sirena/diagram/timeline.rb +171 -0
- data/lib/sirena/diagram/treemap.rb +84 -0
- data/lib/sirena/diagram/user_journey.rb +149 -0
- data/lib/sirena/diagram/xy_chart.rb +76 -0
- data/lib/sirena/diagram.rb +8 -0
- data/lib/sirena/diagram_registry.rb +101 -0
- data/lib/sirena/engine.rb +292 -0
- data/lib/sirena/parser/architecture.rb +41 -0
- data/lib/sirena/parser/base.rb +41 -0
- data/lib/sirena/parser/block.rb +72 -0
- data/lib/sirena/parser/c4.rb +53 -0
- data/lib/sirena/parser/class_diagram.rb +63 -0
- data/lib/sirena/parser/er_diagram.rb +40 -0
- data/lib/sirena/parser/error.rb +49 -0
- data/lib/sirena/parser/flowchart.rb +71 -0
- data/lib/sirena/parser/gantt.rb +60 -0
- data/lib/sirena/parser/git_graph.rb +95 -0
- data/lib/sirena/parser/grammars/architecture.rb +145 -0
- data/lib/sirena/parser/grammars/block.rb +190 -0
- data/lib/sirena/parser/grammars/c4.rb +226 -0
- data/lib/sirena/parser/grammars/class_diagram.rb +284 -0
- data/lib/sirena/parser/grammars/common.rb +84 -0
- data/lib/sirena/parser/grammars/er_diagram.rb +114 -0
- data/lib/sirena/parser/grammars/error.rb +40 -0
- data/lib/sirena/parser/grammars/flowchart.rb +298 -0
- data/lib/sirena/parser/grammars/gantt.rb +252 -0
- data/lib/sirena/parser/grammars/git_graph.rb +167 -0
- data/lib/sirena/parser/grammars/info.rb +58 -0
- data/lib/sirena/parser/grammars/kanban.rb +83 -0
- data/lib/sirena/parser/grammars/mindmap.rb +115 -0
- data/lib/sirena/parser/grammars/packet.rb +73 -0
- data/lib/sirena/parser/grammars/pie.rb +128 -0
- data/lib/sirena/parser/grammars/quadrant.rb +199 -0
- data/lib/sirena/parser/grammars/radar.rb +150 -0
- data/lib/sirena/parser/grammars/requirement.rb +188 -0
- data/lib/sirena/parser/grammars/sankey.rb +104 -0
- data/lib/sirena/parser/grammars/sequence.rb +247 -0
- data/lib/sirena/parser/grammars/state_diagram.rb +172 -0
- data/lib/sirena/parser/grammars/timeline.rb +142 -0
- data/lib/sirena/parser/grammars/treemap.rb +120 -0
- data/lib/sirena/parser/grammars/xy_chart.rb +120 -0
- data/lib/sirena/parser/info.rb +49 -0
- data/lib/sirena/parser/kanban.rb +97 -0
- data/lib/sirena/parser/mindmap.rb +106 -0
- data/lib/sirena/parser/packet.rb +76 -0
- data/lib/sirena/parser/pie.rb +49 -0
- data/lib/sirena/parser/quadrant.rb +57 -0
- data/lib/sirena/parser/radar.rb +104 -0
- data/lib/sirena/parser/requirement.rb +70 -0
- data/lib/sirena/parser/sankey.rb +64 -0
- data/lib/sirena/parser/sequence.rb +51 -0
- data/lib/sirena/parser/state_diagram.rb +69 -0
- data/lib/sirena/parser/timeline.rb +57 -0
- data/lib/sirena/parser/transforms/architecture.rb +97 -0
- data/lib/sirena/parser/transforms/block.rb +254 -0
- data/lib/sirena/parser/transforms/c4.rb +347 -0
- data/lib/sirena/parser/transforms/class_diagram.rb +352 -0
- data/lib/sirena/parser/transforms/er_diagram.rb +169 -0
- data/lib/sirena/parser/transforms/error.rb +58 -0
- data/lib/sirena/parser/transforms/flowchart.rb +293 -0
- data/lib/sirena/parser/transforms/gantt.rb +215 -0
- data/lib/sirena/parser/transforms/git_graph.rb +160 -0
- data/lib/sirena/parser/transforms/info.rb +58 -0
- data/lib/sirena/parser/transforms/kanban.rb +176 -0
- data/lib/sirena/parser/transforms/mindmap.rb +227 -0
- data/lib/sirena/parser/transforms/packet.rb +63 -0
- data/lib/sirena/parser/transforms/pie.rb +143 -0
- data/lib/sirena/parser/transforms/quadrant.rb +177 -0
- data/lib/sirena/parser/transforms/radar.rb +126 -0
- data/lib/sirena/parser/transforms/requirement.rb +272 -0
- data/lib/sirena/parser/transforms/sankey.rb +122 -0
- data/lib/sirena/parser/transforms/sequence.rb +342 -0
- data/lib/sirena/parser/transforms/state_diagram.rb +292 -0
- data/lib/sirena/parser/transforms/timeline.rb +177 -0
- data/lib/sirena/parser/transforms/treemap.rb +81 -0
- data/lib/sirena/parser/transforms/xy_chart.rb +132 -0
- data/lib/sirena/parser/treemap.rb +98 -0
- data/lib/sirena/parser/user_journey.rb +120 -0
- data/lib/sirena/parser/xy_chart.rb +114 -0
- data/lib/sirena/parser.rb +8 -0
- data/lib/sirena/renderer/architecture.rb +251 -0
- data/lib/sirena/renderer/base.rb +251 -0
- data/lib/sirena/renderer/block.rb +286 -0
- data/lib/sirena/renderer/c4.rb +490 -0
- data/lib/sirena/renderer/class_diagram.rb +499 -0
- data/lib/sirena/renderer/er_diagram.rb +417 -0
- data/lib/sirena/renderer/error.rb +131 -0
- data/lib/sirena/renderer/flowchart.rb +301 -0
- data/lib/sirena/renderer/gantt.rb +331 -0
- data/lib/sirena/renderer/git_graph.rb +368 -0
- data/lib/sirena/renderer/info.rb +93 -0
- data/lib/sirena/renderer/kanban.rb +295 -0
- data/lib/sirena/renderer/mindmap.rb +396 -0
- data/lib/sirena/renderer/packet.rb +239 -0
- data/lib/sirena/renderer/pie.rb +235 -0
- data/lib/sirena/renderer/quadrant.rb +292 -0
- data/lib/sirena/renderer/radar.rb +323 -0
- data/lib/sirena/renderer/requirement.rb +371 -0
- data/lib/sirena/renderer/sankey.rb +255 -0
- data/lib/sirena/renderer/sequence.rb +424 -0
- data/lib/sirena/renderer/state_diagram.rb +328 -0
- data/lib/sirena/renderer/timeline.rb +304 -0
- data/lib/sirena/renderer/treemap.rb +152 -0
- data/lib/sirena/renderer/user_journey.rb +331 -0
- data/lib/sirena/renderer/xy_chart.rb +452 -0
- data/lib/sirena/renderer.rb +8 -0
- data/lib/sirena/svg/circle.rb +41 -0
- data/lib/sirena/svg/document.rb +103 -0
- data/lib/sirena/svg/element.rb +65 -0
- data/lib/sirena/svg/ellipse.rb +33 -0
- data/lib/sirena/svg/group.rb +71 -0
- data/lib/sirena/svg/line.rb +49 -0
- data/lib/sirena/svg/path.rb +76 -0
- data/lib/sirena/svg/polygon.rb +43 -0
- data/lib/sirena/svg/polyline.rb +35 -0
- data/lib/sirena/svg/rect.rb +57 -0
- data/lib/sirena/svg/style.rb +44 -0
- data/lib/sirena/svg/text.rb +72 -0
- data/lib/sirena/svg.rb +19 -0
- data/lib/sirena/text_measurement.rb +71 -0
- data/lib/sirena/theme/builtin/dark.yml +70 -0
- data/lib/sirena/theme/builtin/default.yml +80 -0
- data/lib/sirena/theme/builtin/high_contrast.yml +70 -0
- data/lib/sirena/theme/builtin/light.yml +70 -0
- data/lib/sirena/theme/color_palette.rb +48 -0
- data/lib/sirena/theme/effect_styles.rb +28 -0
- data/lib/sirena/theme/registry.rb +41 -0
- data/lib/sirena/theme/shape_styles.rb +28 -0
- data/lib/sirena/theme/spacing_config.rb +24 -0
- data/lib/sirena/theme/typography.rb +30 -0
- data/lib/sirena/theme.rb +69 -0
- data/lib/sirena/transform/architecture.rb +273 -0
- data/lib/sirena/transform/base.rb +199 -0
- data/lib/sirena/transform/block.rb +215 -0
- data/lib/sirena/transform/c4.rb +288 -0
- data/lib/sirena/transform/class_diagram.rb +296 -0
- data/lib/sirena/transform/er_diagram.rb +204 -0
- data/lib/sirena/transform/error.rb +39 -0
- data/lib/sirena/transform/flowchart.rb +161 -0
- data/lib/sirena/transform/gantt.rb +253 -0
- data/lib/sirena/transform/git_graph.rb +283 -0
- data/lib/sirena/transform/info.rb +39 -0
- data/lib/sirena/transform/kanban.rb +180 -0
- data/lib/sirena/transform/mindmap.rb +251 -0
- data/lib/sirena/transform/packet.rb +185 -0
- data/lib/sirena/transform/pie.rb +62 -0
- data/lib/sirena/transform/quadrant.rb +167 -0
- data/lib/sirena/transform/radar.rb +227 -0
- data/lib/sirena/transform/requirement.rb +233 -0
- data/lib/sirena/transform/sankey.rb +212 -0
- data/lib/sirena/transform/sequence.rb +143 -0
- data/lib/sirena/transform/state_diagram.rb +228 -0
- data/lib/sirena/transform/timeline.rb +139 -0
- data/lib/sirena/transform/treemap.rb +120 -0
- data/lib/sirena/transform/user_journey.rb +207 -0
- data/lib/sirena/transform/xy_chart.rb +273 -0
- data/lib/sirena/transform.rb +8 -0
- data/lib/sirena/version.rb +5 -0
- data/lib/sirena.rb +328 -0
- data/lib/tasks/benchmark.rake +532 -0
- data/lib/tasks/examples.rake +468 -0
- data/lib/tasks/generate_mermaid_fixtures.rake +363 -0
- data/lib/tasks/mermaid_fixtures.rake +46 -0
- data/scripts/extract_mermaid_tests.rb +493 -0
- data/scripts/rename_to_sirena.rb +73 -0
- data/sirena.gemspec +47 -0
- metadata +529 -0
data/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
# Sirena Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Sirena is a pure Ruby implementation of Mermaid diagram generation following
|
|
6
|
+
strict object-oriented principles with model-driven architecture. The system
|
|
7
|
+
transforms Mermaid syntax into SVG output through a pipeline of well-separated
|
|
8
|
+
components.
|
|
9
|
+
|
|
10
|
+
## Core Principles
|
|
11
|
+
|
|
12
|
+
1. **Model-Driven Design**: All data structures use `Lutaml::Model` classes
|
|
13
|
+
2. **MECE Separation**: Each component has mutually exclusive, collectively
|
|
14
|
+
exhaustive responsibilities
|
|
15
|
+
3. **Register-Based**: Diagram types and renderers registered dynamically
|
|
16
|
+
4. **Open/Closed**: Extensible without modification via inheritance/composition
|
|
17
|
+
5. **Single Responsibility**: Each class handles one cohesive concern
|
|
18
|
+
|
|
19
|
+
## Processing Pipeline
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Mermaid Syntax Input (String)
|
|
23
|
+
│
|
|
24
|
+
▼
|
|
25
|
+
┌─────────┐
|
|
26
|
+
│ Parser │ Parslet Grammar → Transform → Parser
|
|
27
|
+
└────┬────┘ (3-layer architecture)
|
|
28
|
+
│
|
|
29
|
+
▼
|
|
30
|
+
┌───────────┐
|
|
31
|
+
│ Diagram │ Lutaml::Model AST
|
|
32
|
+
│ Model │ (Flowchart, Sequence, etc.)
|
|
33
|
+
└─────┬─────┘
|
|
34
|
+
│
|
|
35
|
+
▼
|
|
36
|
+
┌───────────┐
|
|
37
|
+
│Transform │ Diagram → Graph Conversion
|
|
38
|
+
│ Layer │
|
|
39
|
+
└─────┬─────┘
|
|
40
|
+
│
|
|
41
|
+
▼
|
|
42
|
+
┌───────────┐
|
|
43
|
+
│ Elkrb │ Layout Computation
|
|
44
|
+
│ Layout │ (Node positions, edge routes)
|
|
45
|
+
└─────┬─────┘
|
|
46
|
+
│
|
|
47
|
+
▼
|
|
48
|
+
┌───────────┐
|
|
49
|
+
│ SVG │ Graph → SVG Conversion
|
|
50
|
+
│ Renderer │
|
|
51
|
+
└─────┬─────┘
|
|
52
|
+
│
|
|
53
|
+
▼
|
|
54
|
+
┌───────────┐
|
|
55
|
+
│ SVG │ Lutaml::Model Structure
|
|
56
|
+
│ Builder │
|
|
57
|
+
└─────┬─────┘
|
|
58
|
+
│
|
|
59
|
+
▼
|
|
60
|
+
SVG XML Output (String)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Component Architecture
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
Sirena (Root Module)
|
|
67
|
+
│
|
|
68
|
+
├── Engine (Orchestrator)
|
|
69
|
+
│ │
|
|
70
|
+
│ ├── coordinates overall flow
|
|
71
|
+
│ ├── delegates to Parser
|
|
72
|
+
│ ├── delegates to Transform
|
|
73
|
+
│ └── delegates to Renderer
|
|
74
|
+
│
|
|
75
|
+
├── Parser (Syntax Analysis - Parslet-based)
|
|
76
|
+
│ │
|
|
77
|
+
│ ├── Grammars (Parslet syntax rules)
|
|
78
|
+
│ ├── Transforms (Parslet tree transformations)
|
|
79
|
+
│ ├── Parsers (orchestrate Grammar → Transform)
|
|
80
|
+
│ └── produces Diagram Models
|
|
81
|
+
│
|
|
82
|
+
├── Diagram (Domain Models)
|
|
83
|
+
│ │
|
|
84
|
+
│ ├── Base (Abstract)
|
|
85
|
+
│ ├── Flowchart
|
|
86
|
+
│ ├── Sequence
|
|
87
|
+
│ ├── ClassDiagram
|
|
88
|
+
│ ├── StateDiagram
|
|
89
|
+
│ ├── ErDiagram
|
|
90
|
+
│ └── UserJourney
|
|
91
|
+
│
|
|
92
|
+
├── Transform (Model Conversion)
|
|
93
|
+
│ │
|
|
94
|
+
│ ├── Base (Abstract)
|
|
95
|
+
│ ├── FlowchartTransform
|
|
96
|
+
│ ├── SequenceTransform
|
|
97
|
+
│ ├── ClassDiagramTransform
|
|
98
|
+
│ ├── StateDiagramTransform
|
|
99
|
+
│ ├── ErDiagramTransform
|
|
100
|
+
│ └── UserJourneyTransform
|
|
101
|
+
│
|
|
102
|
+
├── Renderer (SVG Generation)
|
|
103
|
+
│ │
|
|
104
|
+
│ ├── Base (Abstract)
|
|
105
|
+
│ ├── FlowchartRenderer
|
|
106
|
+
│ ├── SequenceRenderer
|
|
107
|
+
│ ├── ClassDiagramRenderer
|
|
108
|
+
│ ├── StateDiagramRenderer
|
|
109
|
+
│ ├── ErDiagramRenderer
|
|
110
|
+
│ └── UserJourneyRenderer
|
|
111
|
+
│
|
|
112
|
+
├── Svg (SVG Models)
|
|
113
|
+
│ │
|
|
114
|
+
│ ├── Document
|
|
115
|
+
│ ├── Element
|
|
116
|
+
│ ├── Group
|
|
117
|
+
│ ├── Path
|
|
118
|
+
│ ├── Text
|
|
119
|
+
│ ├── Rect
|
|
120
|
+
│ ├── Circle
|
|
121
|
+
│ ├── Line
|
|
122
|
+
│ ├── Polygon
|
|
123
|
+
│ └── Style
|
|
124
|
+
│
|
|
125
|
+
├── DiagramRegistry (Type Registration)
|
|
126
|
+
│ │
|
|
127
|
+
│ ├── register(type, parser, transform, renderer)
|
|
128
|
+
│ └── get(type) -> handler triple
|
|
129
|
+
│
|
|
130
|
+
└── TextMeasurement (Dimension Calculation)
|
|
131
|
+
│
|
|
132
|
+
├── measure_text(text, font_size) -> {width, height}
|
|
133
|
+
└── character-based approximations
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Parslet-Based Parser Architecture
|
|
137
|
+
|
|
138
|
+
**All 11 implemented diagram types use Parslet exclusively** for parsing. The
|
|
139
|
+
legacy lexer-based approach has been fully deprecated (see
|
|
140
|
+
[`lib/sirena/parser/lexer.rb`](lib/sirena/parser/lexer.rb) deprecation notice).
|
|
141
|
+
|
|
142
|
+
### 3-Layer Parslet Architecture
|
|
143
|
+
|
|
144
|
+
Every parser follows a consistent 3-layer pattern:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
┌─────────────────────────────────────────────┐
|
|
148
|
+
│ Layer 1: Grammar (Parslet::Parser) │
|
|
149
|
+
│ ──────────────────────────────────── │
|
|
150
|
+
│ • Defines syntax rules using Parslet DSL │
|
|
151
|
+
│ • Parses text into intermediate tree │
|
|
152
|
+
│ • Returns Hash/Array structures │
|
|
153
|
+
│ │
|
|
154
|
+
│ File: lib/sirena/parser/grammars/*.rb │
|
|
155
|
+
└────────────┬────────────────────────────────┘
|
|
156
|
+
│
|
|
157
|
+
▼ Intermediate tree (Hash/Array)
|
|
158
|
+
┌─────────────────────────────────────────────┐
|
|
159
|
+
│ Layer 2: Transform (Parslet::Transform) │
|
|
160
|
+
│ ────────────────────────────────────── │
|
|
161
|
+
│ • Converts intermediate tree to models │
|
|
162
|
+
│ • Maps patterns to Diagram objects │
|
|
163
|
+
│ • Returns structured Diagram models │
|
|
164
|
+
│ │
|
|
165
|
+
│ File: lib/sirena/parser/transforms/*.rb │
|
|
166
|
+
└────────────┬────────────────────────────────┘
|
|
167
|
+
│
|
|
168
|
+
▼ Diagram models (Lutaml::Model)
|
|
169
|
+
┌─────────────────────────────────────────────┐
|
|
170
|
+
│ Layer 3: Parser (Orchestrator) │
|
|
171
|
+
│ ──────────────────────────────────── │
|
|
172
|
+
│ • Combines Grammar + Transform │
|
|
173
|
+
│ • Public API: parse(input) → Diagram │
|
|
174
|
+
│ • Handles errors and edge cases │
|
|
175
|
+
│ │
|
|
176
|
+
│ File: lib/sirena/parser/*.rb │
|
|
177
|
+
└─────────────────────────────────────────────┘
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Benefits of Parslet Architecture
|
|
181
|
+
|
|
182
|
+
1. **Superior Error Messages**: Parslet provides detailed context and line
|
|
183
|
+
numbers for syntax errors, making debugging much easier.
|
|
184
|
+
|
|
185
|
+
2. **Composability**: Grammar rules can be composed and reused across diagram
|
|
186
|
+
types through the Common grammar module.
|
|
187
|
+
|
|
188
|
+
3. **Maintainability**: The 3-layer separation makes code easier to understand,
|
|
189
|
+
test, and modify. ~80% code reduction achieved vs. lexer approach.
|
|
190
|
+
|
|
191
|
+
4. **Declarative Syntax**: Grammar rules are declarative and self-documenting,
|
|
192
|
+
making the parser logic clear and concise.
|
|
193
|
+
|
|
194
|
+
5. **Type Safety**: Transform layer produces strongly-typed Lutaml::Model
|
|
195
|
+
objects, catching errors early.
|
|
196
|
+
|
|
197
|
+
### Example: Flowchart Parser
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
# Layer 1: Grammar (lib/sirena/parser/grammars/flowchart.rb)
|
|
201
|
+
class FlowchartGrammar < Parslet::Parser
|
|
202
|
+
include Common # Shared rules
|
|
203
|
+
|
|
204
|
+
rule(:flowchart) do
|
|
205
|
+
diagram_type >> nodes >> edges >> eof
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
rule(:node) do
|
|
209
|
+
identifier >> (str('[') >> text >> str(']')).as(:shape)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Layer 2: Transform (lib/sirena/parser/transforms/flowchart.rb)
|
|
214
|
+
class FlowchartTransform < Parslet::Transform
|
|
215
|
+
rule(node: simple(:id), shape: simple(:text)) do
|
|
216
|
+
Diagram::Flowchart::Node.new(id: id, text: text)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Layer 3: Parser (lib/sirena/parser/flowchart.rb)
|
|
221
|
+
class FlowchartParser
|
|
222
|
+
def initialize
|
|
223
|
+
@grammar = Grammars::FlowchartGrammar.new
|
|
224
|
+
@transform = Transforms::FlowchartTransform.new
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def parse(input)
|
|
228
|
+
tree = @grammar.parse(input)
|
|
229
|
+
@transform.apply(tree)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Migration Summary
|
|
235
|
+
|
|
236
|
+
All 4 legacy lexer-based parsers have been successfully migrated to Parslet:
|
|
237
|
+
|
|
238
|
+
| Parser | Migration | Code Reduction | Week |
|
|
239
|
+
|--------|-----------|----------------|------|
|
|
240
|
+
| Flowchart | ✅ Complete | ~80% | Week 7 |
|
|
241
|
+
| Class Diagram | ✅ Complete | ~85% | Week 8 |
|
|
242
|
+
| ER Diagram | ✅ Complete | ~82% | Week 9 |
|
|
243
|
+
| State Diagram | ✅ Complete | ~78% | Week 10 |
|
|
244
|
+
|
|
245
|
+
All remaining 7 diagram types (Sequence, User Journey, Timeline, Git Graph,
|
|
246
|
+
Gantt, Pie, Mindmap, Quadrant) were built with Parslet from the start.
|
|
247
|
+
|
|
248
|
+
**Result**: 100% architectural consistency across all 11 diagram types.
|
|
249
|
+
|
|
250
|
+
## Data Flow Details
|
|
251
|
+
|
|
252
|
+
### 1. Parsing Phase (Parslet-based)
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
Mermaid Source
|
|
256
|
+
│
|
|
257
|
+
▼
|
|
258
|
+
Grammar.parse
|
|
259
|
+
(Parslet rules)
|
|
260
|
+
│
|
|
261
|
+
▼
|
|
262
|
+
Intermediate Tree
|
|
263
|
+
(Hash/Array)
|
|
264
|
+
│
|
|
265
|
+
▼
|
|
266
|
+
Transform.apply
|
|
267
|
+
(Pattern matching)
|
|
268
|
+
│
|
|
269
|
+
▼
|
|
270
|
+
Diagram Model
|
|
271
|
+
(Lutaml::Model)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The parser produces typed diagram models that fully represent the diagram
|
|
275
|
+
structure. Each diagram type has its own Grammar, Transform, and Parser classes
|
|
276
|
+
following the consistent 3-layer pattern.
|
|
277
|
+
|
|
278
|
+
### 2. Transform Phase
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
Diagram Model
|
|
282
|
+
│
|
|
283
|
+
▼
|
|
284
|
+
Transform.to_graph
|
|
285
|
+
│
|
|
286
|
+
├── Convert nodes to Elkrb::Graph::Node
|
|
287
|
+
├── Convert edges to Elkrb::Graph::Edge
|
|
288
|
+
├── Apply text measurement for dimensions
|
|
289
|
+
└── Set layout options
|
|
290
|
+
│
|
|
291
|
+
▼
|
|
292
|
+
Elkrb Graph
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The transform layer is responsible for:
|
|
296
|
+
- Converting diagram-specific structures to generic graph structures
|
|
297
|
+
- Calculating node dimensions based on content
|
|
298
|
+
- Mapping diagram relationships to graph edges
|
|
299
|
+
- Setting appropriate layout algorithm options
|
|
300
|
+
|
|
301
|
+
### 3. Layout Phase
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
Elkrb Graph
|
|
305
|
+
│
|
|
306
|
+
▼
|
|
307
|
+
Elkrb::LayoutEngine.layout
|
|
308
|
+
│
|
|
309
|
+
├── Select algorithm (layered, force, etc.)
|
|
310
|
+
├── Compute node positions
|
|
311
|
+
├── Route edge paths with bend points
|
|
312
|
+
└── Position labels
|
|
313
|
+
│
|
|
314
|
+
▼
|
|
315
|
+
Laid Out Graph
|
|
316
|
+
(with x, y coordinates)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
This phase is fully delegated to elkrb, which handles all layout computation.
|
|
320
|
+
|
|
321
|
+
### 4. Rendering Phase
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
Laid Out Graph
|
|
325
|
+
│
|
|
326
|
+
▼
|
|
327
|
+
Renderer.render
|
|
328
|
+
│
|
|
329
|
+
├── Create SVG::Document
|
|
330
|
+
├── Add nodes as SVG shapes
|
|
331
|
+
├── Add edges as SVG paths
|
|
332
|
+
├── Add labels as SVG text
|
|
333
|
+
└── Apply styles
|
|
334
|
+
│
|
|
335
|
+
▼
|
|
336
|
+
SVG Model
|
|
337
|
+
(Lutaml::Model)
|
|
338
|
+
│
|
|
339
|
+
▼
|
|
340
|
+
SVG.to_xml
|
|
341
|
+
│
|
|
342
|
+
▼
|
|
343
|
+
SVG String
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The renderer converts positioned graph elements into SVG graphic primitives.
|
|
347
|
+
|
|
348
|
+
## Class Responsibility Matrix
|
|
349
|
+
|
|
350
|
+
| Component | Responsibility | Dependencies |
|
|
351
|
+
|-----------|---------------|--------------|
|
|
352
|
+
| `Engine` | Orchestrate entire pipeline | Parser, Transform, Renderer |
|
|
353
|
+
| `Parser::Grammars::*` | Define Parslet syntax rules | Parslet, Common |
|
|
354
|
+
| `Parser::Transforms::*` | Transform parse trees | Parslet::Transform, Diagram models |
|
|
355
|
+
| `Parser::*` | Orchestrate Grammar+Transform | Grammars, Transforms |
|
|
356
|
+
| `Diagram::Base` | Abstract diagram model | Lutaml::Model |
|
|
357
|
+
| `Diagram::*` | Specific diagram structures | Diagram::Base |
|
|
358
|
+
| `Transform::Base` | Abstract graph converter | Elkrb |
|
|
359
|
+
| `Transform::*` | Diagram-specific conversion | Transform::Base, TextMeasurement |
|
|
360
|
+
| `Renderer::Base` | Abstract SVG renderer | Svg |
|
|
361
|
+
| `Renderer::*` | Diagram-specific rendering | Renderer::Base, Svg |
|
|
362
|
+
| `Svg::*` | SVG graphic primitives | Lutaml::Model, Moxml |
|
|
363
|
+
| `DiagramRegistry` | Type handler registration | None |
|
|
364
|
+
| `TextMeasurement` | Text dimension calculation | None |
|
|
365
|
+
|
|
366
|
+
**Note**: `Parser::Lexer` is deprecated and will be removed in v2.0.0. All
|
|
367
|
+
parsers now use the Parslet-based 3-layer architecture.
|
|
368
|
+
|
|
369
|
+
## Extensibility Points
|
|
370
|
+
|
|
371
|
+
### Adding New Diagram Types
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
# 1. Define diagram model
|
|
375
|
+
class Diagram::NewType < Diagram::Base
|
|
376
|
+
attribute :elements, :array
|
|
377
|
+
# ... diagram-specific attributes
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# 2. Implement transform
|
|
381
|
+
class Transform::NewTypeTransform < Transform::Base
|
|
382
|
+
def to_graph(diagram)
|
|
383
|
+
# Convert diagram to Elkrb::Graph
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# 3. Implement renderer
|
|
388
|
+
class Renderer::NewTypeRenderer < Renderer::Base
|
|
389
|
+
def render(graph)
|
|
390
|
+
# Convert graph to SVG
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# 4. Register
|
|
395
|
+
DiagramRegistry.register(
|
|
396
|
+
:new_type,
|
|
397
|
+
parser: Parser::NewTypeGrammar,
|
|
398
|
+
transform: Transform::NewTypeTransform,
|
|
399
|
+
renderer: Renderer::NewTypeRenderer
|
|
400
|
+
)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Adding New SVG Shapes
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
# Define new SVG element
|
|
407
|
+
class Svg::NewShape < Svg::Element
|
|
408
|
+
attribute :custom_attr, :string
|
|
409
|
+
|
|
410
|
+
xml do
|
|
411
|
+
map_element "new-shape", to: :itself
|
|
412
|
+
# ... XML mappings
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## SVG Builder Architecture
|
|
418
|
+
|
|
419
|
+
The SVG builder implements the complete SVG 1.2 graphic model using
|
|
420
|
+
Lutaml::Model. This enables:
|
|
421
|
+
|
|
422
|
+
1. **Object-Oriented SVG Construction**: Build SVG programmatically
|
|
423
|
+
2. **Type Safety**: Strong typing via Lutaml::Model attributes
|
|
424
|
+
3. **Serialization**: Automatic XML generation via Moxml
|
|
425
|
+
4. **Composability**: Nested element structures
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
SVG::Document
|
|
429
|
+
│
|
|
430
|
+
├── viewBox: String
|
|
431
|
+
├── width: Numeric
|
|
432
|
+
├── height: Numeric
|
|
433
|
+
└── children: Array<SVG::Element>
|
|
434
|
+
│
|
|
435
|
+
├── SVG::Group
|
|
436
|
+
│ ├── transform: String
|
|
437
|
+
│ ├── style: SVG::Style
|
|
438
|
+
│ └── children: Array<SVG::Element>
|
|
439
|
+
│
|
|
440
|
+
├── SVG::Path
|
|
441
|
+
│ ├── d: String (path data)
|
|
442
|
+
│ └── style: SVG::Style
|
|
443
|
+
│
|
|
444
|
+
├── SVG::Text
|
|
445
|
+
│ ├── x, y: Numeric
|
|
446
|
+
│ ├── content: String
|
|
447
|
+
│ └── style: SVG::Style
|
|
448
|
+
│
|
|
449
|
+
└── SVG::Rect, Circle, Line, Polygon...
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Text Measurement Strategy
|
|
453
|
+
|
|
454
|
+
Text dimensions are calculated using character-based approximations:
|
|
455
|
+
|
|
456
|
+
```ruby
|
|
457
|
+
TextMeasurement.measure("Hello", font_size: 14)
|
|
458
|
+
# => { width: 35.0, height: 14.0 }
|
|
459
|
+
|
|
460
|
+
# Algorithm:
|
|
461
|
+
# - Average character width: font_size * 0.5
|
|
462
|
+
# - Height: font_size * 1.0
|
|
463
|
+
# - Width: char_count * avg_char_width
|
|
464
|
+
# - Users can override with explicit dimensions
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
This provides reasonable estimates for layout without requiring font metrics
|
|
468
|
+
libraries. Elkrb uses these dimensions for node sizing.
|
|
469
|
+
|
|
470
|
+
## Error Handling
|
|
471
|
+
|
|
472
|
+
```
|
|
473
|
+
Error (Base Exception)
|
|
474
|
+
│
|
|
475
|
+
├── ParseError
|
|
476
|
+
│ ├── LexerError
|
|
477
|
+
│ └── GrammarError
|
|
478
|
+
│
|
|
479
|
+
├── TransformError
|
|
480
|
+
│ └── LayoutError
|
|
481
|
+
│
|
|
482
|
+
└── RenderError
|
|
483
|
+
└── SvgBuildError
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Each phase has specific error types for clear debugging and error reporting.
|
|
487
|
+
|
|
488
|
+
## Testing Strategy
|
|
489
|
+
|
|
490
|
+
### Unit Tests (Per Class)
|
|
491
|
+
- Each model class has structural tests
|
|
492
|
+
- Each parser component tests syntax handling
|
|
493
|
+
- Each transform tests graph conversion
|
|
494
|
+
- Each renderer tests SVG generation
|
|
495
|
+
|
|
496
|
+
### Integration Tests
|
|
497
|
+
- End-to-end: Mermaid syntax → SVG output
|
|
498
|
+
- Verify structural correctness of SVG
|
|
499
|
+
- Compare with expected fixture outputs
|
|
500
|
+
- Manual visual verification
|
|
501
|
+
|
|
502
|
+
### Fixtures
|
|
503
|
+
- Located in `spec/fixtures/`
|
|
504
|
+
- One subdirectory per diagram type
|
|
505
|
+
- Input `.mmd` files and expected outputs
|
|
506
|
+
- Cover edge cases and complex scenarios
|
|
507
|
+
|
|
508
|
+
## Directory Structure
|
|
509
|
+
|
|
510
|
+
```
|
|
511
|
+
sirena/
|
|
512
|
+
├── lib/
|
|
513
|
+
│ └── sirena/
|
|
514
|
+
│ ├── version.rb
|
|
515
|
+
│ ├── engine.rb
|
|
516
|
+
│ ├── diagram_registry.rb
|
|
517
|
+
│ ├── text_measurement.rb
|
|
518
|
+
│ ├── parser/
|
|
519
|
+
│ │ ├── base.rb
|
|
520
|
+
│ │ ├── lexer.rb (DEPRECATED - will be removed in v2.0.0)
|
|
521
|
+
│ │ ├── grammars/ # Parslet grammars (Layer 1)
|
|
522
|
+
│ │ │ ├── common.rb # Shared grammar rules
|
|
523
|
+
│ │ │ ├── flowchart.rb
|
|
524
|
+
│ │ │ ├── class_diagram.rb
|
|
525
|
+
│ │ │ ├── er_diagram.rb
|
|
526
|
+
│ │ │ ├── state_diagram.rb
|
|
527
|
+
│ │ │ └── (other diagram grammars)
|
|
528
|
+
│ │ ├── transforms/ # Parslet transforms (Layer 2)
|
|
529
|
+
│ │ │ ├── flowchart.rb
|
|
530
|
+
│ │ │ ├── class_diagram.rb
|
|
531
|
+
│ │ │ ├── er_diagram.rb
|
|
532
|
+
│ │ │ ├── state_diagram.rb
|
|
533
|
+
│ │ │ └── (other diagram transforms)
|
|
534
|
+
│ │ ├── flowchart.rb # Parser orchestrators (Layer 3)
|
|
535
|
+
│ │ ├── class_diagram.rb
|
|
536
|
+
│ │ ├── er_diagram.rb
|
|
537
|
+
│ │ ├── state_diagram.rb
|
|
538
|
+
│ │ └── (other diagram parsers)
|
|
539
|
+
│ ├── diagram/
|
|
540
|
+
│ │ ├── base.rb
|
|
541
|
+
│ │ ├── flowchart.rb
|
|
542
|
+
│ │ ├── sequence.rb
|
|
543
|
+
│ │ ├── class_diagram.rb
|
|
544
|
+
│ │ ├── state_diagram.rb
|
|
545
|
+
│ │ ├── er_diagram.rb
|
|
546
|
+
│ │ └── user_journey.rb
|
|
547
|
+
│ ├── transform/
|
|
548
|
+
│ │ ├── base.rb
|
|
549
|
+
│ │ └── (diagram-specific graph transforms)
|
|
550
|
+
│ ├── renderer/
|
|
551
|
+
│ │ ├── base.rb
|
|
552
|
+
│ │ └── (diagram-specific renderers)
|
|
553
|
+
│ ├── svg/
|
|
554
|
+
│ │ ├── document.rb
|
|
555
|
+
│ │ ├── element.rb
|
|
556
|
+
│ │ ├── group.rb
|
|
557
|
+
│ │ ├── path.rb
|
|
558
|
+
│ │ ├── text.rb
|
|
559
|
+
│ │ ├── rect.rb
|
|
560
|
+
│ │ ├── circle.rb
|
|
561
|
+
│ │ ├── line.rb
|
|
562
|
+
│ │ ├── polygon.rb
|
|
563
|
+
│ │ └── style.rb
|
|
564
|
+
│ └── cli.rb
|
|
565
|
+
├── spec/
|
|
566
|
+
│ ├── spec_helper.rb
|
|
567
|
+
│ ├── fixtures/
|
|
568
|
+
│ │ ├── flowchart/
|
|
569
|
+
│ │ ├── sequence/
|
|
570
|
+
│ │ ├── class_diagram/
|
|
571
|
+
│ │ ├── state_diagram/
|
|
572
|
+
│ │ ├── er_diagram/
|
|
573
|
+
│ │ └── user_journey/
|
|
574
|
+
│ └── (test files mirroring lib/)
|
|
575
|
+
└── examples/
|
|
576
|
+
├── flowchart_example.mmd
|
|
577
|
+
├── sequence_example.mmd
|
|
578
|
+
└── (other diagram type examples)
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
**Note**: The 3-layer Parslet architecture (grammars/ + transforms/ + parsers)
|
|
582
|
+
is now standard across all diagram types.
|
|
583
|
+
|
|
584
|
+
## Component Interaction Sequence
|
|
585
|
+
|
|
586
|
+
### Example: Rendering a Flowchart
|
|
587
|
+
|
|
588
|
+
```
|
|
589
|
+
User calls: Sirena.render(mermaid_source)
|
|
590
|
+
│
|
|
591
|
+
▼
|
|
592
|
+
Engine.render
|
|
593
|
+
│
|
|
594
|
+
┌────────────┼────────────┐
|
|
595
|
+
│ │ │
|
|
596
|
+
▼ ▼ ▼
|
|
597
|
+
Parse Transform Render
|
|
598
|
+
│ │ │
|
|
599
|
+
│ │ │
|
|
600
|
+
Diagram. Elkrb. SVG.
|
|
601
|
+
Flowchart Graph Document
|
|
602
|
+
│ │ │
|
|
603
|
+
└────────────┴────────────┘
|
|
604
|
+
│
|
|
605
|
+
▼
|
|
606
|
+
SVG XML String
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### Detailed Flow
|
|
610
|
+
|
|
611
|
+
1. **Engine receives mermaid source**
|
|
612
|
+
- Detects diagram type from syntax prefix
|
|
613
|
+
- Looks up handler in DiagramRegistry
|
|
614
|
+
|
|
615
|
+
2. **Parser processes syntax** (Parslet 3-layer architecture)
|
|
616
|
+
- Grammar parses input using Parslet rules
|
|
617
|
+
- Transform converts intermediate tree to Diagram model
|
|
618
|
+
- Returns typed Diagram model (Lutaml::Model)
|
|
619
|
+
|
|
620
|
+
3. **Transform converts to graph**
|
|
621
|
+
- Analyzes diagram structure
|
|
622
|
+
- Creates Elkrb::Graph::Node for each diagram element
|
|
623
|
+
- Creates Elkrb::Graph::Edge for relationships
|
|
624
|
+
- Applies TextMeasurement for node dimensions
|
|
625
|
+
- Sets layout algorithm and options
|
|
626
|
+
|
|
627
|
+
4. **Elkrb computes layout**
|
|
628
|
+
- Runs selected algorithm (layered, force, etc.)
|
|
629
|
+
- Calculates x, y coordinates for all nodes
|
|
630
|
+
- Routes edges with bend points
|
|
631
|
+
- Positions labels
|
|
632
|
+
|
|
633
|
+
5. **Renderer generates SVG**
|
|
634
|
+
- Creates SVG::Document root
|
|
635
|
+
- Adds SVG shapes for each node
|
|
636
|
+
- Adds SVG paths for each edge
|
|
637
|
+
- Adds SVG text for labels
|
|
638
|
+
- Applies styling
|
|
639
|
+
|
|
640
|
+
6. **SVG Builder serializes**
|
|
641
|
+
- Traverses SVG model tree
|
|
642
|
+
- Generates XML using Moxml
|
|
643
|
+
- Returns SVG 1.2 compliant string
|
|
644
|
+
|
|
645
|
+
## Key Design Patterns
|
|
646
|
+
|
|
647
|
+
### Registry Pattern (DiagramRegistry)
|
|
648
|
+
|
|
649
|
+
Allows dynamic registration and retrieval of diagram type handlers without
|
|
650
|
+
hardcoding type checks.
|
|
651
|
+
|
|
652
|
+
```ruby
|
|
653
|
+
DiagramRegistry.register(:flowchart, {
|
|
654
|
+
parser: Parser::FlowchartGrammar,
|
|
655
|
+
transform: Transform::FlowchartTransform,
|
|
656
|
+
renderer: Renderer::FlowchartRenderer
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
handler = DiagramRegistry.get(:flowchart)
|
|
660
|
+
diagram = handler[:parser].parse(source)
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Strategy Pattern (Transform/Renderer)
|
|
664
|
+
|
|
665
|
+
Each diagram type has its own transformation and rendering strategy,
|
|
666
|
+
implementing a common interface.
|
|
667
|
+
|
|
668
|
+
```ruby
|
|
669
|
+
class Transform::Base
|
|
670
|
+
def to_graph(diagram)
|
|
671
|
+
raise NotImplementedError
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
class Transform::FlowchartTransform < Transform::Base
|
|
676
|
+
def to_graph(diagram)
|
|
677
|
+
# Flowchart-specific conversion
|
|
678
|
+
end
|
|
679
|
+
end
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### Builder Pattern (SVG)
|
|
683
|
+
|
|
684
|
+
SVG construction uses the builder pattern through Lutaml::Model to create
|
|
685
|
+
complex nested structures programmatically.
|
|
686
|
+
|
|
687
|
+
```ruby
|
|
688
|
+
svg = Svg::Document.new(width: 800, height: 600).tap do |doc|
|
|
689
|
+
doc.children << Svg::Group.new.tap do |group|
|
|
690
|
+
group.children << Svg::Rect.new(x: 0, y: 0, width: 100, height: 50)
|
|
691
|
+
group.children << Svg::Text.new(x: 50, y: 25, content: "Node")
|
|
692
|
+
end
|
|
693
|
+
end
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
### Template Method Pattern (Base Classes)
|
|
697
|
+
|
|
698
|
+
Base classes define the algorithm structure, with subclasses providing
|
|
699
|
+
specific implementations.
|
|
700
|
+
|
|
701
|
+
```ruby
|
|
702
|
+
class Renderer::Base
|
|
703
|
+
def render(graph)
|
|
704
|
+
svg = create_document(graph)
|
|
705
|
+
render_nodes(graph, svg)
|
|
706
|
+
render_edges(graph, svg)
|
|
707
|
+
render_labels(graph, svg)
|
|
708
|
+
svg
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
def render_nodes(graph, svg)
|
|
712
|
+
raise NotImplementedError
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
# ... other template methods
|
|
716
|
+
end
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
## Dependencies and Their Roles
|
|
720
|
+
|
|
721
|
+
- **parslet (~> 2.0)**: Parser construction framework for all diagram parsers
|
|
722
|
+
- **lutaml-model (~> 0.7)**: Serialization framework for all models
|
|
723
|
+
- **elkrb**: Graph layout computation engine
|
|
724
|
+
- **moxml**: XML/SVG serialization via Nokogiri
|
|
725
|
+
- **thor**: CLI framework
|
|
726
|
+
|
|
727
|
+
## Integration with Metanorma
|
|
728
|
+
|
|
729
|
+
Sirena produces SVG 1.2 compliant output that can be directly embedded in
|
|
730
|
+
Metanorma documents without post-processing. The SVG includes:
|
|
731
|
+
|
|
732
|
+
- Proper XML namespace declarations
|
|
733
|
+
- ViewBox for scalability
|
|
734
|
+
- Embedded styling (no external CSS dependencies)
|
|
735
|
+
- Standard-compliant path data
|
|
736
|
+
- UTF-8 text encoding
|
|
737
|
+
|
|
738
|
+
Integration point:
|
|
739
|
+
|
|
740
|
+
```ruby
|
|
741
|
+
# In Metanorma document processing
|
|
742
|
+
svg_output = Sirena.render(mermaid_diagram_source)
|
|
743
|
+
# Embed directly in document
|
|
744
|
+
```
|