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.
Files changed (382) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build_deploy.yml +59 -0
  3. data/.github/workflows/links.yml +85 -0
  4. data/.github/workflows/rake.yml +15 -0
  5. data/.github/workflows/release.yml +27 -0
  6. data/.gitignore +68 -0
  7. data/.rspec +3 -0
  8. data/.rubocop.yml +14 -0
  9. data/.rubocop_todo.yml +70 -0
  10. data/ARCHITECTURE.md +744 -0
  11. data/Gemfile +12 -0
  12. data/LICENSE +25 -0
  13. data/README.adoc +357 -0
  14. data/Rakefile +11 -0
  15. data/docs/.gitignore +1 -0
  16. data/docs/Gemfile +13 -0
  17. data/docs/_config.yml +182 -0
  18. data/docs/_diagram_types/architecture-diagram.adoc +314 -0
  19. data/docs/_diagram_types/block-diagram.adoc +345 -0
  20. data/docs/_diagram_types/c4-diagram.adoc +559 -0
  21. data/docs/_diagram_types/class-diagram.adoc +816 -0
  22. data/docs/_diagram_types/er-diagram.adoc +719 -0
  23. data/docs/_diagram_types/error-diagram.adoc +114 -0
  24. data/docs/_diagram_types/examples/flowchart-examples.adoc +29 -0
  25. data/docs/_diagram_types/flowchart.adoc +488 -0
  26. data/docs/_diagram_types/gantt-chart.adoc +502 -0
  27. data/docs/_diagram_types/git-graph.adoc +600 -0
  28. data/docs/_diagram_types/index.adoc +192 -0
  29. data/docs/_diagram_types/info-diagram.adoc +103 -0
  30. data/docs/_diagram_types/kanban-diagram.adoc +262 -0
  31. data/docs/_diagram_types/mindmap.adoc +603 -0
  32. data/docs/_diagram_types/packet-diagram.adoc +378 -0
  33. data/docs/_diagram_types/pie-chart.adoc +335 -0
  34. data/docs/_diagram_types/quadrant-chart.adoc +406 -0
  35. data/docs/_diagram_types/radar-chart.adoc +528 -0
  36. data/docs/_diagram_types/requirement-diagram.adoc +416 -0
  37. data/docs/_diagram_types/sankey-diagram.adoc +357 -0
  38. data/docs/_diagram_types/sequence-diagram.adoc +664 -0
  39. data/docs/_diagram_types/state-diagram.adoc +658 -0
  40. data/docs/_diagram_types/timeline.adoc +352 -0
  41. data/docs/_diagram_types/treemap-diagram.adoc +462 -0
  42. data/docs/_diagram_types/user-journey.adoc +602 -0
  43. data/docs/_features/index.adoc +129 -0
  44. data/docs/_guides/cli-reference.adoc +203 -0
  45. data/docs/_guides/index.adoc +56 -0
  46. data/docs/_guides/installation.adoc +100 -0
  47. data/docs/_guides/quick-start.adoc +132 -0
  48. data/docs/_pages/comparison.adoc +441 -0
  49. data/docs/_pages/compatibility.adoc +300 -0
  50. data/docs/_pages/index.adoc +39 -0
  51. data/docs/_references/index.adoc +103 -0
  52. data/docs/_tutorials/index.adoc +57 -0
  53. data/docs/index.adoc +166 -0
  54. data/docs/lychee.toml +54 -0
  55. data/examples/.gitignore +10 -0
  56. data/examples/README.adoc +196 -0
  57. data/examples/README.md +64 -0
  58. data/examples/architecture/01-basic-services.mmd +9 -0
  59. data/examples/architecture/01-basic-services.svg +37 -0
  60. data/examples/architecture/02-service-groups.mmd +16 -0
  61. data/examples/architecture/02-service-groups.svg +55 -0
  62. data/examples/architecture/README.adoc +79 -0
  63. data/examples/block/01-basic-blocks.mmd +13 -0
  64. data/examples/block/01-basic-blocks.svg +44 -0
  65. data/examples/block/02-block-shapes.mmd +13 -0
  66. data/examples/block/02-block-shapes.svg +47 -0
  67. data/examples/block/README.adoc +85 -0
  68. data/examples/c4/01-context-diagram.mmd +10 -0
  69. data/examples/c4/01-context-diagram.svg +45 -0
  70. data/examples/c4/02-container-diagram.mmd +24 -0
  71. data/examples/c4/02-container-diagram.svg +105 -0
  72. data/examples/c4/README.adoc +92 -0
  73. data/examples/class_diagram/01-basic-classes.mmd +61 -0
  74. data/examples/class_diagram/01-basic-classes.svg +117 -0
  75. data/examples/class_diagram/02-relationships.mmd +61 -0
  76. data/examples/class_diagram/02-relationships.svg +129 -0
  77. data/examples/class_diagram/README.adoc +93 -0
  78. data/examples/er_diagram/01-basic-entities.mmd +64 -0
  79. data/examples/er_diagram/01-basic-entities.svg +5 -0
  80. data/examples/er_diagram/02-cardinality.mmd +57 -0
  81. data/examples/er_diagram/02-cardinality.svg +125 -0
  82. data/examples/er_diagram/README.adoc +88 -0
  83. data/examples/error/01-basic-error.mmd +1 -0
  84. data/examples/error/01-basic-error.svg +13 -0
  85. data/examples/error/02-error-display.mmd +1 -0
  86. data/examples/error/02-error-display.svg +13 -0
  87. data/examples/error/README.adoc +71 -0
  88. data/examples/error_message_example.svg +13 -0
  89. data/examples/flowchart/00-original.mmd +13 -0
  90. data/examples/flowchart/00-original.svg +5 -0
  91. data/examples/flowchart/01-basic-flow.mmd +7 -0
  92. data/examples/flowchart/01-basic-flow.svg +52 -0
  93. data/examples/flowchart/01-basic-flow.yml +13 -0
  94. data/examples/flowchart/02*.svg +87 -0
  95. data/examples/flowchart/02-node-shapes.mmd +9 -0
  96. data/examples/flowchart/02-node-shapes.svg +33 -0
  97. data/examples/flowchart/03-edge-types.mmd +7 -0
  98. data/examples/flowchart/03-edge-types.svg +53 -0
  99. data/examples/flowchart/04-subgraphs.mmd +9 -0
  100. data/examples/flowchart/04-subgraphs.svg +33 -0
  101. data/examples/flowchart/05-styling.mmd +9 -0
  102. data/examples/flowchart/05-styling.svg +33 -0
  103. data/examples/flowchart/06-complex-flow.mmd +8 -0
  104. data/examples/flowchart/06-complex-flow.svg +59 -0
  105. data/examples/flowchart/README.adoc +167 -0
  106. data/examples/gantt/01-simple-timeline.* +14 -0
  107. data/examples/gantt/01-simple-timeline.mmd +6 -0
  108. data/examples/gantt/01-simple-timeline.svg +26 -0
  109. data/examples/gantt/02-task-dependencies.mmd +6 -0
  110. data/examples/gantt/02-task-dependencies.svg +26 -0
  111. data/examples/gantt/README.adoc +86 -0
  112. data/examples/git_graph/01-linear-history.mmd +12 -0
  113. data/examples/git_graph/01-linear-history.svg +26 -0
  114. data/examples/git_graph/02-branching.mmd +12 -0
  115. data/examples/git_graph/02-branching.svg +26 -0
  116. data/examples/git_graph/README.adoc +73 -0
  117. data/examples/info/02-showinfo.mmd +1 -0
  118. data/examples/info/02-showinfo.svg +10 -0
  119. data/examples/info/README.adoc +58 -0
  120. data/examples/info_showinfo_example.svg +10 -0
  121. data/examples/kanban/01-simple-board.mmd +8 -0
  122. data/examples/kanban/01-simple-board.svg +43 -0
  123. data/examples/kanban/02-workflow.mmd +8 -0
  124. data/examples/kanban/02-workflow.svg +43 -0
  125. data/examples/kanban/README.adoc +79 -0
  126. data/examples/mindmap/01-simple-tree.mmd +19 -0
  127. data/examples/mindmap/01-simple-tree.svg +61 -0
  128. data/examples/mindmap/02-knowledge-map.mmd +19 -0
  129. data/examples/mindmap/02-knowledge-map.svg +61 -0
  130. data/examples/mindmap/README.adoc +77 -0
  131. data/examples/packet/01-basic-packet.* +17 -0
  132. data/examples/packet/01-basic-packet.mmd +4 -0
  133. data/examples/packet/01-basic-packet.svg +82 -0
  134. data/examples/packet/README.adoc +58 -0
  135. data/examples/pie/01-simple-chart.mmd +5 -0
  136. data/examples/pie/01-simple-chart.svg +17 -0
  137. data/examples/pie/02-labeled-slices.mmd +6 -0
  138. data/examples/pie/02-labeled-slices.svg +19 -0
  139. data/examples/pie/README.adoc +75 -0
  140. data/examples/quadrant/01-basic-quadrant.mmd +13 -0
  141. data/examples/quadrant/01-basic-quadrant.svg +33 -0
  142. data/examples/quadrant/02-positioned-items.mmd +14 -0
  143. data/examples/quadrant/02-positioned-items.svg +35 -0
  144. data/examples/quadrant/README.adoc +84 -0
  145. data/examples/radar/01-simple-radar.* +5 -0
  146. data/examples/radar/01-simple-radar.mmd +3 -0
  147. data/examples/radar/01-simple-radar.svg +25 -0
  148. data/examples/radar/02-multiple-curves.mmd +4 -0
  149. data/examples/radar/02-multiple-curves.svg +43 -0
  150. data/examples/radar/README.adoc +75 -0
  151. data/examples/requirement/01-basic-requirements.mmd +23 -0
  152. data/examples/requirement/01-basic-requirements.svg +49 -0
  153. data/examples/requirement/02-risk-levels.mmd +23 -0
  154. data/examples/requirement/02-risk-levels.svg +49 -0
  155. data/examples/requirement/README.adoc +85 -0
  156. data/examples/sankey/01-simple-flow.mmd +7 -0
  157. data/examples/sankey/01-simple-flow.svg +34 -0
  158. data/examples/sankey/02-multi-stage.mmd +11 -0
  159. data/examples/sankey/02-multi-stage.svg +44 -0
  160. data/examples/sankey/README.adoc +74 -0
  161. data/examples/sequence/01-basic-sequence.mmd +27 -0
  162. data/examples/sequence/01-basic-sequence.svg +5 -0
  163. data/examples/sequence/02-activations.mmd +17 -0
  164. data/examples/sequence/02-activations.svg +78 -0
  165. data/examples/sequence/README.adoc +86 -0
  166. data/examples/state_diagram/01-simple-states.mmd +29 -0
  167. data/examples/state_diagram/01-simple-states.svg +5 -0
  168. data/examples/state_diagram/02-composite.mmd +19 -0
  169. data/examples/state_diagram/02-composite.svg +81 -0
  170. data/examples/state_diagram/README.adoc +90 -0
  171. data/examples/timeline/01-simple-timeline.mmd +11 -0
  172. data/examples/timeline/01-simple-timeline.svg +36 -0
  173. data/examples/timeline/02-periods.mmd +15 -0
  174. data/examples/timeline/02-periods.svg +47 -0
  175. data/examples/timeline/README.adoc +78 -0
  176. data/examples/treemap/01-basic-treemap.mmd +12 -0
  177. data/examples/treemap/01-basic-treemap.svg +59 -0
  178. data/examples/treemap/README.adoc +59 -0
  179. data/examples/user_journey/01-simple-journey.mmd +23 -0
  180. data/examples/user_journey/01-simple-journey.svg +5 -0
  181. data/examples/user_journey/02-multi-actor.mmd +18 -0
  182. data/examples/user_journey/02-multi-actor.svg +129 -0
  183. data/examples/user_journey/README.adoc +81 -0
  184. data/examples/xychart/01-line-chart.mmd +5 -0
  185. data/examples/xychart/01-line-chart.svg +43 -0
  186. data/examples/xychart/02-bar-chart.mmd +7 -0
  187. data/examples/xychart/02-bar-chart.svg +48 -0
  188. data/examples/xychart/README.adoc +80 -0
  189. data/exe/sirena +7 -0
  190. data/lib/sirena/cli.rb +138 -0
  191. data/lib/sirena/commands/batch.rb +117 -0
  192. data/lib/sirena/commands/render.rb +80 -0
  193. data/lib/sirena/commands/types.rb +29 -0
  194. data/lib/sirena/commands/version.rb +24 -0
  195. data/lib/sirena/diagram/architecture.rb +46 -0
  196. data/lib/sirena/diagram/base.rb +61 -0
  197. data/lib/sirena/diagram/block.rb +81 -0
  198. data/lib/sirena/diagram/c4.rb +328 -0
  199. data/lib/sirena/diagram/class_diagram.rb +385 -0
  200. data/lib/sirena/diagram/er_diagram.rb +238 -0
  201. data/lib/sirena/diagram/error.rb +38 -0
  202. data/lib/sirena/diagram/flowchart.rb +160 -0
  203. data/lib/sirena/diagram/gantt.rb +71 -0
  204. data/lib/sirena/diagram/git_graph.rb +36 -0
  205. data/lib/sirena/diagram/info.rb +38 -0
  206. data/lib/sirena/diagram/kanban.rb +178 -0
  207. data/lib/sirena/diagram/mindmap.rb +54 -0
  208. data/lib/sirena/diagram/packet.rb +79 -0
  209. data/lib/sirena/diagram/pie.rb +115 -0
  210. data/lib/sirena/diagram/quadrant.rb +138 -0
  211. data/lib/sirena/diagram/radar.rb +52 -0
  212. data/lib/sirena/diagram/requirement.rb +133 -0
  213. data/lib/sirena/diagram/sankey.rb +217 -0
  214. data/lib/sirena/diagram/sequence.rb +242 -0
  215. data/lib/sirena/diagram/state_diagram.rb +237 -0
  216. data/lib/sirena/diagram/timeline.rb +171 -0
  217. data/lib/sirena/diagram/treemap.rb +84 -0
  218. data/lib/sirena/diagram/user_journey.rb +149 -0
  219. data/lib/sirena/diagram/xy_chart.rb +76 -0
  220. data/lib/sirena/diagram.rb +8 -0
  221. data/lib/sirena/diagram_registry.rb +101 -0
  222. data/lib/sirena/engine.rb +292 -0
  223. data/lib/sirena/parser/architecture.rb +41 -0
  224. data/lib/sirena/parser/base.rb +41 -0
  225. data/lib/sirena/parser/block.rb +72 -0
  226. data/lib/sirena/parser/c4.rb +53 -0
  227. data/lib/sirena/parser/class_diagram.rb +63 -0
  228. data/lib/sirena/parser/er_diagram.rb +40 -0
  229. data/lib/sirena/parser/error.rb +49 -0
  230. data/lib/sirena/parser/flowchart.rb +71 -0
  231. data/lib/sirena/parser/gantt.rb +60 -0
  232. data/lib/sirena/parser/git_graph.rb +95 -0
  233. data/lib/sirena/parser/grammars/architecture.rb +145 -0
  234. data/lib/sirena/parser/grammars/block.rb +190 -0
  235. data/lib/sirena/parser/grammars/c4.rb +226 -0
  236. data/lib/sirena/parser/grammars/class_diagram.rb +284 -0
  237. data/lib/sirena/parser/grammars/common.rb +84 -0
  238. data/lib/sirena/parser/grammars/er_diagram.rb +114 -0
  239. data/lib/sirena/parser/grammars/error.rb +40 -0
  240. data/lib/sirena/parser/grammars/flowchart.rb +298 -0
  241. data/lib/sirena/parser/grammars/gantt.rb +252 -0
  242. data/lib/sirena/parser/grammars/git_graph.rb +167 -0
  243. data/lib/sirena/parser/grammars/info.rb +58 -0
  244. data/lib/sirena/parser/grammars/kanban.rb +83 -0
  245. data/lib/sirena/parser/grammars/mindmap.rb +115 -0
  246. data/lib/sirena/parser/grammars/packet.rb +73 -0
  247. data/lib/sirena/parser/grammars/pie.rb +128 -0
  248. data/lib/sirena/parser/grammars/quadrant.rb +199 -0
  249. data/lib/sirena/parser/grammars/radar.rb +150 -0
  250. data/lib/sirena/parser/grammars/requirement.rb +188 -0
  251. data/lib/sirena/parser/grammars/sankey.rb +104 -0
  252. data/lib/sirena/parser/grammars/sequence.rb +247 -0
  253. data/lib/sirena/parser/grammars/state_diagram.rb +172 -0
  254. data/lib/sirena/parser/grammars/timeline.rb +142 -0
  255. data/lib/sirena/parser/grammars/treemap.rb +120 -0
  256. data/lib/sirena/parser/grammars/xy_chart.rb +120 -0
  257. data/lib/sirena/parser/info.rb +49 -0
  258. data/lib/sirena/parser/kanban.rb +97 -0
  259. data/lib/sirena/parser/mindmap.rb +106 -0
  260. data/lib/sirena/parser/packet.rb +76 -0
  261. data/lib/sirena/parser/pie.rb +49 -0
  262. data/lib/sirena/parser/quadrant.rb +57 -0
  263. data/lib/sirena/parser/radar.rb +104 -0
  264. data/lib/sirena/parser/requirement.rb +70 -0
  265. data/lib/sirena/parser/sankey.rb +64 -0
  266. data/lib/sirena/parser/sequence.rb +51 -0
  267. data/lib/sirena/parser/state_diagram.rb +69 -0
  268. data/lib/sirena/parser/timeline.rb +57 -0
  269. data/lib/sirena/parser/transforms/architecture.rb +97 -0
  270. data/lib/sirena/parser/transforms/block.rb +254 -0
  271. data/lib/sirena/parser/transforms/c4.rb +347 -0
  272. data/lib/sirena/parser/transforms/class_diagram.rb +352 -0
  273. data/lib/sirena/parser/transforms/er_diagram.rb +169 -0
  274. data/lib/sirena/parser/transforms/error.rb +58 -0
  275. data/lib/sirena/parser/transforms/flowchart.rb +293 -0
  276. data/lib/sirena/parser/transforms/gantt.rb +215 -0
  277. data/lib/sirena/parser/transforms/git_graph.rb +160 -0
  278. data/lib/sirena/parser/transforms/info.rb +58 -0
  279. data/lib/sirena/parser/transforms/kanban.rb +176 -0
  280. data/lib/sirena/parser/transforms/mindmap.rb +227 -0
  281. data/lib/sirena/parser/transforms/packet.rb +63 -0
  282. data/lib/sirena/parser/transforms/pie.rb +143 -0
  283. data/lib/sirena/parser/transforms/quadrant.rb +177 -0
  284. data/lib/sirena/parser/transforms/radar.rb +126 -0
  285. data/lib/sirena/parser/transforms/requirement.rb +272 -0
  286. data/lib/sirena/parser/transforms/sankey.rb +122 -0
  287. data/lib/sirena/parser/transforms/sequence.rb +342 -0
  288. data/lib/sirena/parser/transforms/state_diagram.rb +292 -0
  289. data/lib/sirena/parser/transforms/timeline.rb +177 -0
  290. data/lib/sirena/parser/transforms/treemap.rb +81 -0
  291. data/lib/sirena/parser/transforms/xy_chart.rb +132 -0
  292. data/lib/sirena/parser/treemap.rb +98 -0
  293. data/lib/sirena/parser/user_journey.rb +120 -0
  294. data/lib/sirena/parser/xy_chart.rb +114 -0
  295. data/lib/sirena/parser.rb +8 -0
  296. data/lib/sirena/renderer/architecture.rb +251 -0
  297. data/lib/sirena/renderer/base.rb +251 -0
  298. data/lib/sirena/renderer/block.rb +286 -0
  299. data/lib/sirena/renderer/c4.rb +490 -0
  300. data/lib/sirena/renderer/class_diagram.rb +499 -0
  301. data/lib/sirena/renderer/er_diagram.rb +417 -0
  302. data/lib/sirena/renderer/error.rb +131 -0
  303. data/lib/sirena/renderer/flowchart.rb +301 -0
  304. data/lib/sirena/renderer/gantt.rb +331 -0
  305. data/lib/sirena/renderer/git_graph.rb +368 -0
  306. data/lib/sirena/renderer/info.rb +93 -0
  307. data/lib/sirena/renderer/kanban.rb +295 -0
  308. data/lib/sirena/renderer/mindmap.rb +396 -0
  309. data/lib/sirena/renderer/packet.rb +239 -0
  310. data/lib/sirena/renderer/pie.rb +235 -0
  311. data/lib/sirena/renderer/quadrant.rb +292 -0
  312. data/lib/sirena/renderer/radar.rb +323 -0
  313. data/lib/sirena/renderer/requirement.rb +371 -0
  314. data/lib/sirena/renderer/sankey.rb +255 -0
  315. data/lib/sirena/renderer/sequence.rb +424 -0
  316. data/lib/sirena/renderer/state_diagram.rb +328 -0
  317. data/lib/sirena/renderer/timeline.rb +304 -0
  318. data/lib/sirena/renderer/treemap.rb +152 -0
  319. data/lib/sirena/renderer/user_journey.rb +331 -0
  320. data/lib/sirena/renderer/xy_chart.rb +452 -0
  321. data/lib/sirena/renderer.rb +8 -0
  322. data/lib/sirena/svg/circle.rb +41 -0
  323. data/lib/sirena/svg/document.rb +103 -0
  324. data/lib/sirena/svg/element.rb +65 -0
  325. data/lib/sirena/svg/ellipse.rb +33 -0
  326. data/lib/sirena/svg/group.rb +71 -0
  327. data/lib/sirena/svg/line.rb +49 -0
  328. data/lib/sirena/svg/path.rb +76 -0
  329. data/lib/sirena/svg/polygon.rb +43 -0
  330. data/lib/sirena/svg/polyline.rb +35 -0
  331. data/lib/sirena/svg/rect.rb +57 -0
  332. data/lib/sirena/svg/style.rb +44 -0
  333. data/lib/sirena/svg/text.rb +72 -0
  334. data/lib/sirena/svg.rb +19 -0
  335. data/lib/sirena/text_measurement.rb +71 -0
  336. data/lib/sirena/theme/builtin/dark.yml +70 -0
  337. data/lib/sirena/theme/builtin/default.yml +80 -0
  338. data/lib/sirena/theme/builtin/high_contrast.yml +70 -0
  339. data/lib/sirena/theme/builtin/light.yml +70 -0
  340. data/lib/sirena/theme/color_palette.rb +48 -0
  341. data/lib/sirena/theme/effect_styles.rb +28 -0
  342. data/lib/sirena/theme/registry.rb +41 -0
  343. data/lib/sirena/theme/shape_styles.rb +28 -0
  344. data/lib/sirena/theme/spacing_config.rb +24 -0
  345. data/lib/sirena/theme/typography.rb +30 -0
  346. data/lib/sirena/theme.rb +69 -0
  347. data/lib/sirena/transform/architecture.rb +273 -0
  348. data/lib/sirena/transform/base.rb +199 -0
  349. data/lib/sirena/transform/block.rb +215 -0
  350. data/lib/sirena/transform/c4.rb +288 -0
  351. data/lib/sirena/transform/class_diagram.rb +296 -0
  352. data/lib/sirena/transform/er_diagram.rb +204 -0
  353. data/lib/sirena/transform/error.rb +39 -0
  354. data/lib/sirena/transform/flowchart.rb +161 -0
  355. data/lib/sirena/transform/gantt.rb +253 -0
  356. data/lib/sirena/transform/git_graph.rb +283 -0
  357. data/lib/sirena/transform/info.rb +39 -0
  358. data/lib/sirena/transform/kanban.rb +180 -0
  359. data/lib/sirena/transform/mindmap.rb +251 -0
  360. data/lib/sirena/transform/packet.rb +185 -0
  361. data/lib/sirena/transform/pie.rb +62 -0
  362. data/lib/sirena/transform/quadrant.rb +167 -0
  363. data/lib/sirena/transform/radar.rb +227 -0
  364. data/lib/sirena/transform/requirement.rb +233 -0
  365. data/lib/sirena/transform/sankey.rb +212 -0
  366. data/lib/sirena/transform/sequence.rb +143 -0
  367. data/lib/sirena/transform/state_diagram.rb +228 -0
  368. data/lib/sirena/transform/timeline.rb +139 -0
  369. data/lib/sirena/transform/treemap.rb +120 -0
  370. data/lib/sirena/transform/user_journey.rb +207 -0
  371. data/lib/sirena/transform/xy_chart.rb +273 -0
  372. data/lib/sirena/transform.rb +8 -0
  373. data/lib/sirena/version.rb +5 -0
  374. data/lib/sirena.rb +328 -0
  375. data/lib/tasks/benchmark.rake +532 -0
  376. data/lib/tasks/examples.rake +468 -0
  377. data/lib/tasks/generate_mermaid_fixtures.rake +363 -0
  378. data/lib/tasks/mermaid_fixtures.rake +46 -0
  379. data/scripts/extract_mermaid_tests.rb +493 -0
  380. data/scripts/rename_to_sirena.rb +73 -0
  381. data/sirena.gemspec +47 -0
  382. metadata +529 -0
@@ -0,0 +1,296 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../diagram/class_diagram'
5
+
6
+ module Sirena
7
+ module Transform
8
+ # Class diagram transformer for converting class models to graphs.
9
+ #
10
+ # Converts a typed class diagram model into a generic graph structure
11
+ # suitable for layout computation by elkrb. Handles class box sizing
12
+ # based on attributes and methods, relationship mapping, and hierarchical
13
+ # layout configuration.
14
+ #
15
+ # @example Transform a class diagram
16
+ # transform = ClassDiagramTransform.new
17
+ # graph = transform.to_graph(class_diagram)
18
+ class ClassDiagramTransform < Base
19
+ # Default font size for text measurement
20
+ DEFAULT_FONT_SIZE = 14
21
+
22
+ # Minimum width for a class box
23
+ MIN_CLASS_WIDTH = 120
24
+
25
+ # Height per class compartment line
26
+ LINE_HEIGHT = 20
27
+
28
+ # Padding within class box compartments
29
+ COMPARTMENT_PADDING = 10
30
+
31
+ # Spacing between classes
32
+ CLASS_SPACING = 80
33
+
34
+ # Converts a class diagram to a graph structure.
35
+ #
36
+ # @param diagram [Diagram::ClassDiagram] the class diagram to transform
37
+ # @return [Hash] elkrb-compatible graph hash
38
+ # @raise [TransformError] if diagram is invalid
39
+ def to_graph(diagram)
40
+ raise TransformError, 'Invalid diagram' unless diagram.valid?
41
+
42
+ {
43
+ id: diagram.id || 'class_diagram',
44
+ children: transform_entities(diagram),
45
+ edges: transform_relationships(diagram),
46
+ layoutOptions: layout_options(diagram)
47
+ }
48
+ end
49
+
50
+ private
51
+
52
+ def transform_entities(diagram)
53
+ diagram.entities.map do |entity|
54
+ dims = calculate_entity_dimensions(entity)
55
+
56
+ {
57
+ id: entity.id,
58
+ width: dims[:width],
59
+ height: dims[:height],
60
+ labels: entity_labels(entity),
61
+ metadata: {
62
+ name: entity.name,
63
+ stereotype: entity.stereotype,
64
+ attributes: entity.attributes.map { |a| attribute_to_hash(a) },
65
+ methods: entity.class_methods.map { |m| method_to_hash(m) }
66
+ }
67
+ }
68
+ end
69
+ end
70
+
71
+ def transform_relationships(diagram)
72
+ return [] if diagram.relationships.nil? ||
73
+ diagram.relationships.empty?
74
+
75
+ diagram.relationships.map do |rel|
76
+ {
77
+ id: "#{rel.from_id}_to_#{rel.to_id}",
78
+ sources: [rel.from_id],
79
+ targets: [rel.to_id],
80
+ labels: relationship_labels(rel),
81
+ metadata: {
82
+ relationship_type: rel.relationship_type,
83
+ source_cardinality: rel.source_cardinality,
84
+ target_cardinality: rel.target_cardinality
85
+ }
86
+ }
87
+ end
88
+ end
89
+
90
+ def calculate_entity_dimensions(entity)
91
+ # Calculate width based on longest line (name, attributes, methods)
92
+ max_width = MIN_CLASS_WIDTH
93
+
94
+ # Check class name width
95
+ name_text = if entity.stereotype
96
+ "<<#{entity.stereotype}>>\n#{entity.name}"
97
+ else
98
+ entity.name
99
+ end
100
+ name_width = measure_text(
101
+ name_text,
102
+ font_size: DEFAULT_FONT_SIZE
103
+ )[:width]
104
+ max_width = [max_width, name_width].max
105
+
106
+ # Check attribute widths
107
+ entity.attributes.each do |attr|
108
+ attr_text = format_attribute(attr)
109
+ attr_width = measure_text(
110
+ attr_text,
111
+ font_size: DEFAULT_FONT_SIZE
112
+ )[:width]
113
+ max_width = [max_width, attr_width].max
114
+ end
115
+
116
+ # Check method widths
117
+ entity.class_methods.each do |method|
118
+ method_text = format_method(method)
119
+ method_width = measure_text(
120
+ method_text,
121
+ font_size: DEFAULT_FONT_SIZE
122
+ )[:width]
123
+ max_width = [max_width, method_width].max
124
+ end
125
+
126
+ # Add padding
127
+ total_width = max_width + (COMPARTMENT_PADDING * 2)
128
+
129
+ # Calculate height based on compartments
130
+ # Name compartment
131
+ name_lines = entity.stereotype ? 2 : 1
132
+ name_height = name_lines * LINE_HEIGHT
133
+
134
+ # Attributes compartment
135
+ attr_height = if entity.attributes.empty?
136
+ 0
137
+ else
138
+ (entity.attributes.length * LINE_HEIGHT)
139
+ end
140
+
141
+ # Methods compartment
142
+ method_height = if entity.class_methods.empty?
143
+ 0
144
+ else
145
+ (entity.class_methods.length * LINE_HEIGHT)
146
+ end
147
+
148
+ # Total height with compartment separators
149
+ compartment_count = [
150
+ 1, # name always present
151
+ entity.attributes.empty? ? 0 : 1,
152
+ entity.class_methods.empty? ? 0 : 1
153
+ ].sum
154
+ separator_height = (compartment_count - 1) * 2 # 2px per separator
155
+
156
+ total_height = name_height + attr_height + method_height +
157
+ separator_height + (COMPARTMENT_PADDING * 2)
158
+
159
+ {
160
+ width: total_width,
161
+ height: total_height
162
+ }
163
+ end
164
+
165
+ def entity_labels(entity)
166
+ labels = []
167
+
168
+ # Main label with class name
169
+ name_text = if entity.stereotype
170
+ "<<#{entity.stereotype}>>\n#{entity.name}"
171
+ else
172
+ entity.name
173
+ end
174
+ name_dims = measure_text(name_text, font_size: DEFAULT_FONT_SIZE)
175
+
176
+ labels << {
177
+ text: name_text,
178
+ width: name_dims[:width],
179
+ height: name_dims[:height]
180
+ }
181
+
182
+ labels
183
+ end
184
+
185
+ def relationship_labels(relationship)
186
+ labels = []
187
+
188
+ # Add relationship label if present
189
+ if relationship.label && !relationship.label.empty?
190
+ label_dims = measure_text(
191
+ relationship.label,
192
+ font_size: DEFAULT_FONT_SIZE
193
+ )
194
+ labels << {
195
+ text: relationship.label,
196
+ width: label_dims[:width],
197
+ height: label_dims[:height]
198
+ }
199
+ end
200
+
201
+ # Add cardinality labels if present
202
+ if relationship.source_cardinality &&
203
+ !relationship.source_cardinality.empty?
204
+ card_dims = measure_text(
205
+ relationship.source_cardinality,
206
+ font_size: DEFAULT_FONT_SIZE - 2
207
+ )
208
+ labels << {
209
+ text: relationship.source_cardinality,
210
+ width: card_dims[:width],
211
+ height: card_dims[:height],
212
+ position: 'source'
213
+ }
214
+ end
215
+
216
+ if relationship.target_cardinality &&
217
+ !relationship.target_cardinality.empty?
218
+ card_dims = measure_text(
219
+ relationship.target_cardinality,
220
+ font_size: DEFAULT_FONT_SIZE - 2
221
+ )
222
+ labels << {
223
+ text: relationship.target_cardinality,
224
+ width: card_dims[:width],
225
+ height: card_dims[:height],
226
+ position: 'target'
227
+ }
228
+ end
229
+
230
+ labels
231
+ end
232
+
233
+ def format_attribute(attribute)
234
+ parts = [attribute.visibility_symbol, attribute.name]
235
+ parts << ": #{attribute.type}" if attribute.type &&
236
+ !attribute.type.empty?
237
+ parts.join(' ')
238
+ end
239
+
240
+ def format_method(method)
241
+ parts = [method.visibility_symbol, method.signature]
242
+ parts.join(' ')
243
+ end
244
+
245
+ def attribute_to_hash(attribute)
246
+ {
247
+ name: attribute.name,
248
+ type: attribute.type,
249
+ visibility: attribute.visibility
250
+ }
251
+ end
252
+
253
+ def method_to_hash(method)
254
+ {
255
+ name: method.name,
256
+ parameters: method.parameters,
257
+ return_type: method.return_type,
258
+ visibility: method.visibility
259
+ }
260
+ end
261
+
262
+ def layout_options(diagram)
263
+ # Class diagrams use layered algorithm for UML hierarchy
264
+ # This optimally handles inheritance relationships and class groupings
265
+ # NETWORK_SIMPLEX node placement balances hierarchy with aesthetics
266
+ build_elk_options(
267
+ algorithm: ALGORITHM_LAYERED,
268
+ direction: direction_to_layout(diagram.direction),
269
+ ElkOptions::NODE_NODE_SPACING => CLASS_SPACING,
270
+ ElkOptions::LAYER_SPACING => CLASS_SPACING,
271
+ ElkOptions::EDGE_NODE_SPACING => 40,
272
+ ElkOptions::EDGE_EDGE_SPACING => 20,
273
+ # NETWORK_SIMPLEX for better UML layout with inheritance
274
+ ElkOptions::NODE_PLACEMENT => 'NETWORK_SIMPLEX',
275
+ ElkOptions::MODEL_ORDER => 'NODES_AND_EDGES',
276
+ ElkOptions::HIERARCHY_HANDLING => 'INCLUDE_CHILDREN'
277
+ )
278
+ end
279
+
280
+ def direction_to_layout(direction)
281
+ case direction
282
+ when 'TD', 'TB'
283
+ DIRECTION_DOWN
284
+ when 'LR'
285
+ DIRECTION_RIGHT
286
+ when 'RL'
287
+ DIRECTION_LEFT
288
+ when 'BT'
289
+ DIRECTION_UP
290
+ else
291
+ DIRECTION_DOWN # Default for class diagrams is top-down
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../diagram/er_diagram'
5
+
6
+ module Sirena
7
+ module Transform
8
+ # ER diagram transformer for converting ER models to graphs.
9
+ #
10
+ # Converts a typed ER diagram model into a generic graph structure
11
+ # suitable for layout computation by elkrb. Handles entity box sizing
12
+ # based on entity name and attribute count, relationship mapping with
13
+ # cardinality, and layered layout configuration.
14
+ #
15
+ # @example Transform an ER diagram
16
+ # transform = ErDiagramTransform.new
17
+ # graph = transform.to_graph(er_diagram)
18
+ class ErDiagramTransform < Base
19
+ # Default font size for text measurement
20
+ DEFAULT_FONT_SIZE = 14
21
+
22
+ # Minimum width for an entity box
23
+ MIN_ENTITY_WIDTH = 150
24
+
25
+ # Height per entity line (name + attributes)
26
+ LINE_HEIGHT = 20
27
+
28
+ # Padding within entity box
29
+ ENTITY_PADDING = 10
30
+
31
+ # Spacing between entities
32
+ ENTITY_SPACING = 100
33
+
34
+ # Converts an ER diagram to a graph structure.
35
+ #
36
+ # @param diagram [Diagram::ErDiagram] the ER diagram to transform
37
+ # @return [Hash] elkrb-compatible graph hash
38
+ # @raise [TransformError] if diagram is invalid
39
+ def to_graph(diagram)
40
+ raise TransformError, 'Invalid diagram' unless diagram.valid?
41
+
42
+ {
43
+ id: diagram.id || 'er_diagram',
44
+ children: transform_entities(diagram),
45
+ edges: transform_relationships(diagram),
46
+ layoutOptions: layout_options
47
+ }
48
+ end
49
+
50
+ private
51
+
52
+ def transform_entities(diagram)
53
+ diagram.entities.map do |entity|
54
+ dims = calculate_entity_dimensions(entity)
55
+
56
+ {
57
+ id: entity.id,
58
+ width: dims[:width],
59
+ height: dims[:height],
60
+ labels: entity_labels(entity),
61
+ metadata: {
62
+ name: entity.name,
63
+ attributes: entity.attributes.map { |a| attribute_to_hash(a) }
64
+ }
65
+ }
66
+ end
67
+ end
68
+
69
+ def transform_relationships(diagram)
70
+ return [] if diagram.relationships.nil? ||
71
+ diagram.relationships.empty?
72
+
73
+ diagram.relationships.map do |rel|
74
+ {
75
+ id: "#{rel.from_id}_to_#{rel.to_id}",
76
+ sources: [rel.from_id],
77
+ targets: [rel.to_id],
78
+ labels: relationship_labels(rel),
79
+ metadata: {
80
+ relationship_type: rel.relationship_type,
81
+ cardinality_from: rel.cardinality_from,
82
+ cardinality_to: rel.cardinality_to
83
+ }
84
+ }
85
+ end
86
+ end
87
+
88
+ def calculate_entity_dimensions(entity)
89
+ # Calculate width based on entity name and attributes
90
+ max_width = MIN_ENTITY_WIDTH
91
+
92
+ # Check entity name width
93
+ name_width = measure_text(
94
+ entity.name,
95
+ font_size: DEFAULT_FONT_SIZE + 2
96
+ )[:width]
97
+ max_width = [max_width, name_width].max
98
+
99
+ # Check attribute widths
100
+ entity.attributes.each do |attr|
101
+ attr_text = format_attribute(attr)
102
+ attr_width = measure_text(
103
+ attr_text,
104
+ font_size: DEFAULT_FONT_SIZE
105
+ )[:width]
106
+ max_width = [max_width, attr_width].max
107
+ end
108
+
109
+ # Add padding
110
+ total_width = max_width + (ENTITY_PADDING * 2)
111
+
112
+ # Calculate height: entity name + attributes
113
+ line_count = 1 + entity.attributes.length
114
+ total_height = (line_count * LINE_HEIGHT) + (ENTITY_PADDING * 2)
115
+
116
+ {
117
+ width: total_width,
118
+ height: total_height
119
+ }
120
+ end
121
+
122
+ def entity_labels(entity)
123
+ labels = []
124
+
125
+ # Main label with entity name
126
+ name_dims = measure_text(
127
+ entity.name,
128
+ font_size: DEFAULT_FONT_SIZE + 2
129
+ )
130
+
131
+ labels << {
132
+ text: entity.name,
133
+ width: name_dims[:width],
134
+ height: name_dims[:height]
135
+ }
136
+
137
+ labels
138
+ end
139
+
140
+ def relationship_labels(relationship)
141
+ labels = []
142
+
143
+ # Add relationship label if present
144
+ if relationship.label && !relationship.label.empty?
145
+ label_dims = measure_text(
146
+ relationship.label,
147
+ font_size: DEFAULT_FONT_SIZE
148
+ )
149
+ labels << {
150
+ text: relationship.label,
151
+ width: label_dims[:width],
152
+ height: label_dims[:height]
153
+ }
154
+ end
155
+
156
+ labels
157
+ end
158
+
159
+ def format_attribute(attribute)
160
+ parts = []
161
+
162
+ # Add key type marker if present
163
+ parts << attribute.key_type if attribute.key_type &&
164
+ !attribute.key_type.empty?
165
+
166
+ # Add attribute name
167
+ parts << attribute.name
168
+
169
+ # Add type if present
170
+ parts << attribute.attribute_type if attribute.attribute_type &&
171
+ !attribute.attribute_type.empty?
172
+
173
+ parts.join(' ')
174
+ end
175
+
176
+ def attribute_to_hash(attribute)
177
+ {
178
+ name: attribute.name,
179
+ attribute_type: attribute.attribute_type,
180
+ key_type: attribute.key_type
181
+ }
182
+ end
183
+
184
+ def layout_options
185
+ # ER diagrams use layered algorithm for hierarchical entity layout
186
+ # DIRECTION_RIGHT provides left-to-right flow for entity relationships
187
+ # NETWORK_SIMPLEX placement optimizes entity box positioning while
188
+ # respecting relationship cardinality and foreign key constraints
189
+ build_elk_options(
190
+ algorithm: ALGORITHM_LAYERED,
191
+ direction: DIRECTION_RIGHT,
192
+ ElkOptions::NODE_NODE_SPACING => ENTITY_SPACING,
193
+ ElkOptions::LAYER_SPACING => ENTITY_SPACING,
194
+ ElkOptions::EDGE_NODE_SPACING => 50,
195
+ ElkOptions::EDGE_EDGE_SPACING => 30,
196
+ # NETWORK_SIMPLEX for better entity relationship layout
197
+ ElkOptions::NODE_PLACEMENT => 'NETWORK_SIMPLEX',
198
+ ElkOptions::MODEL_ORDER => 'NODES_AND_EDGES',
199
+ ElkOptions::HIERARCHY_HANDLING => 'INCLUDE_CHILDREN'
200
+ )
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../diagram/error'
5
+
6
+ module Sirena
7
+ module Transform
8
+ # Error diagram transformer for converting error models to renderable structure.
9
+ #
10
+ # Error diagrams have no complex layout requirements - they simply display
11
+ # an error message. This transformer prepares basic data for rendering.
12
+ #
13
+ # @example Transform an error diagram
14
+ # transform = ErrorTransform.new
15
+ # data = transform.to_graph(error_diagram)
16
+ class ErrorTransform < Base
17
+ # Converts an error diagram to a simple data structure.
18
+ #
19
+ # Error diagrams don't need layout computation. This method validates
20
+ # the diagram and returns a structure for the renderer.
21
+ #
22
+ # @param diagram [Diagram::Error] the error diagram to transform
23
+ # @return [Hash] data structure for rendering
24
+ # @raise [TransformError] if diagram is invalid
25
+ def to_graph(diagram)
26
+ raise TransformError, 'Invalid diagram' unless diagram.valid?
27
+
28
+ {
29
+ id: diagram.id || 'error',
30
+ title: diagram.title,
31
+ message: diagram.message,
32
+ metadata: {
33
+ diagram_type: :error
34
+ }
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../diagram/flowchart'
5
+
6
+ module Sirena
7
+ module Transform
8
+ # Flowchart transformer for converting flowchart models to graphs.
9
+ #
10
+ # Converts a typed flowchart diagram model into a generic graph structure
11
+ # suitable for layout computation by elkrb. Handles node dimension
12
+ # calculation, edge mapping, and layout configuration.
13
+ #
14
+ # @example Transform a flowchart
15
+ # transform = FlowchartTransform.new
16
+ # graph = transform.to_graph(flowchart_diagram)
17
+ class FlowchartTransform < Base
18
+ # Default font size for text measurement
19
+ DEFAULT_FONT_SIZE = 14
20
+
21
+ # Converts a flowchart diagram to a graph structure.
22
+ #
23
+ # @param diagram [Diagram::Flowchart] the flowchart to transform
24
+ # @return [Hash] elkrb-compatible graph hash
25
+ # @raise [TransformError] if diagram is invalid
26
+ def to_graph(diagram)
27
+ raise TransformError, 'Invalid diagram' unless diagram.valid?
28
+
29
+ {
30
+ id: diagram.id || 'flowchart',
31
+ children: transform_nodes(diagram),
32
+ edges: transform_edges(diagram),
33
+ layoutOptions: layout_options(diagram)
34
+ }
35
+ end
36
+
37
+ private
38
+
39
+ def transform_nodes(diagram)
40
+ diagram.nodes.map do |node|
41
+ dims = calculate_dimensions(node)
42
+
43
+ {
44
+ id: node.id,
45
+ width: dims[:width],
46
+ height: dims[:height],
47
+ labels: [
48
+ {
49
+ text: node.label,
50
+ width: dims[:label_width],
51
+ height: dims[:label_height]
52
+ }
53
+ ],
54
+ metadata: {
55
+ shape: node.shape,
56
+ classes: node.classes
57
+ }
58
+ }
59
+ end
60
+ end
61
+
62
+ def transform_edges(diagram)
63
+ return [] if diagram.edges.nil? || diagram.edges.empty?
64
+
65
+ diagram.edges.map do |edge|
66
+ {
67
+ id: "#{edge.source_id}_to_#{edge.target_id}",
68
+ sources: [edge.source_id],
69
+ targets: [edge.target_id],
70
+ labels: edge_labels(edge),
71
+ metadata: {
72
+ arrow_type: edge.arrow_type
73
+ }
74
+ }
75
+ end
76
+ end
77
+
78
+ def edge_labels(edge)
79
+ return [] if edge.label.nil? || edge.label.empty?
80
+
81
+ label_dims = measure_text(edge.label, font_size: DEFAULT_FONT_SIZE)
82
+
83
+ [
84
+ {
85
+ text: edge.label,
86
+ width: label_dims[:width],
87
+ height: label_dims[:height]
88
+ }
89
+ ]
90
+ end
91
+
92
+ def calculate_dimensions(node)
93
+ label_dims = measure_text(
94
+ node.label,
95
+ font_size: DEFAULT_FONT_SIZE
96
+ )
97
+
98
+ node_dims = calculate_node_dimensions(
99
+ label_dims[:width],
100
+ label_dims[:height],
101
+ shape_to_type(node.shape)
102
+ )
103
+
104
+ {
105
+ width: node_dims[:width],
106
+ height: node_dims[:height],
107
+ label_width: label_dims[:width],
108
+ label_height: label_dims[:height]
109
+ }
110
+ end
111
+
112
+ def shape_to_type(shape)
113
+ case shape
114
+ when 'rect', 'subroutine'
115
+ :rect
116
+ when 'circle', 'double_circle'
117
+ :circle
118
+ when 'rhombus', 'hexagon'
119
+ :diamond
120
+ else
121
+ :rect
122
+ end
123
+ end
124
+
125
+ def layout_options(diagram)
126
+ direction = direction_to_layout(diagram.direction)
127
+
128
+ # Flowcharts use layered algorithm for hierarchical flow
129
+ # This ensures nodes are placed in distinct layers and edges flow
130
+ # in the specified direction with minimal crossings
131
+ build_elk_options(
132
+ algorithm: ALGORITHM_LAYERED,
133
+ direction: direction,
134
+ # Additional flowchart-specific options
135
+ ElkOptions::NODE_NODE_SPACING => 50,
136
+ ElkOptions::LAYER_SPACING => 50,
137
+ ElkOptions::EDGE_NODE_SPACING => 30,
138
+ ElkOptions::EDGE_EDGE_SPACING => 20,
139
+ # SIMPLE node placement for predictable, straightforward layouts
140
+ # This is ideal for flowcharts where clarity is paramount
141
+ ElkOptions::NODE_PLACEMENT => 'SIMPLE'
142
+ )
143
+ end
144
+
145
+ def direction_to_layout(direction)
146
+ case direction
147
+ when 'TD', 'TB'
148
+ DIRECTION_DOWN
149
+ when 'LR'
150
+ DIRECTION_RIGHT
151
+ when 'RL'
152
+ DIRECTION_LEFT
153
+ when 'BT'
154
+ DIRECTION_UP
155
+ else
156
+ DIRECTION_DOWN # Default direction
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end