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,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../diagram/timeline"
5
+
6
+ module Sirena
7
+ module Transform
8
+ # Timeline transformer for converting timeline models to renderable structure.
9
+ #
10
+ # Handles chronological ordering, positioning along the timeline axis,
11
+ # and grouping by sections.
12
+ #
13
+ # @example Transform a timeline
14
+ # transform = TimelineTransform.new
15
+ # data = transform.to_graph(timeline_diagram)
16
+ class TimelineTransform < Base
17
+ # Converts a Timeline diagram to a layout structure with calculated positions.
18
+ #
19
+ # @param diagram [Diagram::Timeline] the timeline diagram to transform
20
+ # @return [Hash] data structure for rendering
21
+ # @raise [TransformError] if diagram is invalid
22
+ def to_graph(diagram)
23
+ raise TransformError, "Invalid diagram" unless diagram.valid?
24
+
25
+ @diagram = diagram
26
+
27
+ # Collect all events and determine timeline bounds
28
+ all_events = collect_all_events(diagram)
29
+ timeline_range = calculate_timeline_range(all_events)
30
+
31
+ {
32
+ id: "timeline",
33
+ title: diagram.title,
34
+ acc_title: diagram.acc_title,
35
+ acc_description: diagram.acc_description,
36
+ sections: transform_sections(diagram, timeline_range),
37
+ events: transform_events(diagram.events, timeline_range),
38
+ timeline: timeline_range,
39
+ metadata: {
40
+ section_count: diagram.sections.length,
41
+ total_events: all_events.length,
42
+ has_sections: diagram.has_sections?
43
+ }
44
+ }
45
+ end
46
+
47
+ private
48
+
49
+ def collect_all_events(diagram)
50
+ events = diagram.events.dup
51
+ diagram.sections.each do |section|
52
+ events.concat(section.events)
53
+ end
54
+ events
55
+ end
56
+
57
+ def calculate_timeline_range(events)
58
+ return default_timeline_range if events.empty?
59
+
60
+ # Extract numeric time values for positioning
61
+ time_values = events.map { |e| extract_numeric_time(e.time) }.compact
62
+
63
+ if time_values.empty?
64
+ return default_timeline_range
65
+ end
66
+
67
+ min_time = time_values.min
68
+ max_time = time_values.max
69
+
70
+ # Add some padding
71
+ padding = ((max_time - min_time) * 0.1).ceil
72
+ padding = 1 if padding < 1
73
+
74
+ {
75
+ min: min_time - padding,
76
+ max: max_time + padding,
77
+ span: (max_time - min_time) + (2 * padding)
78
+ }
79
+ end
80
+
81
+ def default_timeline_range
82
+ {
83
+ min: 2000,
84
+ max: 2024,
85
+ span: 24
86
+ }
87
+ end
88
+
89
+ def extract_numeric_time(time_str)
90
+ # Try to extract a numeric value from the time string
91
+ # This handles years (2004), dates (2004-01-01), etc.
92
+ return nil if time_str.nil? || time_str.empty?
93
+
94
+ # Extract first continuous sequence of digits
95
+ match = time_str.match(/\d+/)
96
+ match ? match[0].to_i : nil
97
+ end
98
+
99
+ def transform_sections(diagram, timeline_range)
100
+ diagram.sections.map.with_index do |section, index|
101
+ {
102
+ id: "section_#{index}",
103
+ name: section.name,
104
+ events: transform_events(section.events, timeline_range),
105
+ tasks: section.tasks,
106
+ has_events: section.has_events?,
107
+ has_tasks: section.has_tasks?
108
+ }
109
+ end
110
+ end
111
+
112
+ def transform_events(events, timeline_range)
113
+ events.map.with_index do |event, index|
114
+ {
115
+ id: "event_#{index}",
116
+ time: event.time,
117
+ descriptions: event.descriptions,
118
+ primary_description: event.primary_description,
119
+ multiple_descriptions: event.multiple_descriptions?,
120
+ x_position: calculate_x_position(event.time, timeline_range)
121
+ }
122
+ end
123
+ end
124
+
125
+ def calculate_x_position(time_str, timeline_range)
126
+ numeric_time = extract_numeric_time(time_str)
127
+ return 0 unless numeric_time
128
+
129
+ # Calculate position as percentage along timeline
130
+ # This will be scaled by renderer to actual coordinates
131
+ if timeline_range[:span] > 0
132
+ ((numeric_time - timeline_range[:min]).to_f / timeline_range[:span]) * 100.0
133
+ else
134
+ 50.0 # Center if no span
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../diagram/treemap'
4
+
5
+ module Sirena
6
+ module Transform
7
+ # Layout calculator for treemap diagrams
8
+ class Treemap
9
+ PADDING = 10
10
+ MIN_CELL_SIZE = 40
11
+ LABEL_HEIGHT = 20
12
+ HEADER_HEIGHT = 40
13
+
14
+ # Transforms the diagram into a layout structure.
15
+ #
16
+ # @param diagram [Diagram::TreemapDiagram] the treemap diagram
17
+ # @return [Hash] layout data with positioned cells and dimensions
18
+ def to_graph(diagram)
19
+ total_value = diagram.total_value
20
+ return default_layout(diagram) if total_value <= 0
21
+
22
+ # Calculate dimensions
23
+ width = calculate_width(diagram)
24
+ height = calculate_height(diagram)
25
+ y_offset = diagram.title ? HEADER_HEIGHT + PADDING : PADDING
26
+
27
+ # Layout root nodes
28
+ cells = []
29
+ x = PADDING
30
+ y = y_offset
31
+
32
+ diagram.root_nodes.each do |node|
33
+ cell = layout_node(node, x, y, width - 2 * PADDING,
34
+ height - y_offset - PADDING, total_value)
35
+ cells << cell if cell
36
+ y += cell[:height] + PADDING if cell
37
+ end
38
+
39
+ {
40
+ width: width,
41
+ height: height,
42
+ title: diagram.title,
43
+ cells: cells,
44
+ class_defs: diagram.class_defs
45
+ }
46
+ end
47
+
48
+ private
49
+
50
+ def layout_node(node, x, y, available_width, available_height, total_value)
51
+ node_value = node.total_value
52
+ return nil if node_value <= 0
53
+
54
+ # Calculate proportional height
55
+ ratio = node_value / total_value
56
+ height = [available_height * ratio, MIN_CELL_SIZE].max
57
+
58
+ cell = {
59
+ label: node.label,
60
+ value: node_value,
61
+ x: x,
62
+ y: y,
63
+ width: available_width,
64
+ height: height,
65
+ css_class: node.css_class,
66
+ depth: node.depth,
67
+ children: []
68
+ }
69
+
70
+ # If this node has children, layout them recursively
71
+ if node.branch? && node.children.any?
72
+ child_y = y + LABEL_HEIGHT
73
+ child_height = height - LABEL_HEIGHT - PADDING
74
+
75
+ node.children.each do |child|
76
+ child_cell = layout_node(child, x + PADDING, child_y,
77
+ available_width - 2 * PADDING,
78
+ child_height, node_value)
79
+ if child_cell
80
+ cell[:children] << child_cell
81
+ child_y += child_cell[:height] + PADDING
82
+ end
83
+ end
84
+ end
85
+
86
+ cell
87
+ end
88
+
89
+ def calculate_width(diagram)
90
+ # Base width calculation
91
+ 800
92
+ end
93
+
94
+ def calculate_height(diagram)
95
+ # Calculate based on number of nodes
96
+ node_count = count_nodes(diagram.root_nodes)
97
+ base_height = 400
98
+ additional_height = [node_count * 30, 200].min
99
+
100
+ base_height + additional_height
101
+ end
102
+
103
+ def count_nodes(nodes)
104
+ nodes.sum do |node|
105
+ 1 + (node.children ? count_nodes(node.children) : 0)
106
+ end
107
+ end
108
+
109
+ def default_layout(diagram)
110
+ {
111
+ width: 800,
112
+ height: 400,
113
+ title: diagram.title,
114
+ cells: [],
115
+ class_defs: diagram.class_defs
116
+ }
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../diagram/user_journey'
5
+
6
+ module Sirena
7
+ module Transform
8
+ # User Journey diagram transformer for converting journey models to graphs.
9
+ #
10
+ # Converts a typed user journey model into a generic graph structure
11
+ # suitable for layout computation by elkrb. Handles task box sizing
12
+ # based on task name and actor count, sequential flow between tasks,
13
+ # and horizontal timeline layout.
14
+ #
15
+ # @example Transform a user journey
16
+ # transform = UserJourneyTransform.new
17
+ # graph = transform.to_graph(user_journey)
18
+ class UserJourneyTransform < Base
19
+ # Default font size for text measurement
20
+ DEFAULT_FONT_SIZE = 14
21
+
22
+ # Minimum width for a task box
23
+ MIN_TASK_WIDTH = 120
24
+
25
+ # Height per task box
26
+ TASK_HEIGHT = 80
27
+
28
+ # Padding within task box
29
+ TASK_PADDING = 10
30
+
31
+ # Horizontal spacing between tasks
32
+ TASK_SPACING = 60
33
+
34
+ # Vertical spacing between sections
35
+ SECTION_SPACING = 40
36
+
37
+ # Converts a user journey to a graph structure.
38
+ #
39
+ # @param diagram [Diagram::UserJourney] the user journey to transform
40
+ # @return [Hash] elkrb-compatible graph hash
41
+ # @raise [TransformError] if diagram is invalid
42
+ def to_graph(diagram)
43
+ raise TransformError, 'Invalid diagram' unless diagram.valid?
44
+
45
+ {
46
+ id: diagram.id || 'user_journey',
47
+ children: transform_tasks(diagram),
48
+ edges: transform_task_flow(diagram),
49
+ layoutOptions: layout_options,
50
+ metadata: {
51
+ title: diagram.title,
52
+ sections: diagram.sections.map(&:name)
53
+ }
54
+ }
55
+ end
56
+
57
+ private
58
+
59
+ def transform_tasks(diagram)
60
+ task_id = 0
61
+ nodes = []
62
+
63
+ diagram.sections.each_with_index do |section, section_idx|
64
+ section.tasks.each do |task|
65
+ dims = calculate_task_dimensions(task)
66
+
67
+ nodes << {
68
+ id: "task_#{task_id}",
69
+ width: dims[:width],
70
+ height: dims[:height],
71
+ labels: task_labels(task),
72
+ metadata: {
73
+ name: task.name,
74
+ score: task.score,
75
+ score_color: task.score_color,
76
+ actors: task.actors,
77
+ section_name: section.name,
78
+ section_index: section_idx
79
+ }
80
+ }
81
+
82
+ task_id += 1
83
+ end
84
+ end
85
+
86
+ nodes
87
+ end
88
+
89
+ def transform_task_flow(diagram)
90
+ # Create sequential edges between tasks
91
+ edges = []
92
+ all_tasks = diagram.all_tasks
93
+ task_id = 0
94
+
95
+ all_tasks.each_with_index do |_task, idx|
96
+ next if idx >= all_tasks.length - 1
97
+
98
+ edges << {
99
+ id: "flow_#{task_id}",
100
+ sources: ["task_#{task_id}"],
101
+ targets: ["task_#{task_id + 1}"],
102
+ metadata: {
103
+ type: 'sequence'
104
+ }
105
+ }
106
+
107
+ task_id += 1
108
+ end
109
+
110
+ edges
111
+ end
112
+
113
+ def calculate_task_dimensions(task)
114
+ # Calculate width based on task name and actors
115
+ max_width = MIN_TASK_WIDTH
116
+
117
+ # Check task name width
118
+ name_width = measure_text(
119
+ task.name,
120
+ font_size: DEFAULT_FONT_SIZE + 2
121
+ )[:width]
122
+ max_width = [max_width, name_width].max
123
+
124
+ # Check actors width (displayed as comma-separated list)
125
+ actors_text = task.actors.join(', ')
126
+ actors_width = measure_text(
127
+ actors_text,
128
+ font_size: DEFAULT_FONT_SIZE
129
+ )[:width]
130
+ max_width = [max_width, actors_width].max
131
+
132
+ # Add padding
133
+ total_width = max_width + (TASK_PADDING * 2)
134
+
135
+ {
136
+ width: total_width,
137
+ height: TASK_HEIGHT
138
+ }
139
+ end
140
+
141
+ def task_labels(task)
142
+ labels = []
143
+
144
+ # Task name label
145
+ name_dims = measure_text(
146
+ task.name,
147
+ font_size: DEFAULT_FONT_SIZE + 2
148
+ )
149
+
150
+ labels << {
151
+ text: task.name,
152
+ width: name_dims[:width],
153
+ height: name_dims[:height],
154
+ position: :top
155
+ }
156
+
157
+ # Score label
158
+ score_text = task.score.to_s
159
+ score_dims = measure_text(
160
+ score_text,
161
+ font_size: DEFAULT_FONT_SIZE + 4
162
+ )
163
+
164
+ labels << {
165
+ text: score_text,
166
+ width: score_dims[:width],
167
+ height: score_dims[:height],
168
+ position: :center
169
+ }
170
+
171
+ # Actors label
172
+ actors_text = task.actors.join(', ')
173
+ actors_dims = measure_text(
174
+ actors_text,
175
+ font_size: DEFAULT_FONT_SIZE - 2
176
+ )
177
+
178
+ labels << {
179
+ text: actors_text,
180
+ width: actors_dims[:width],
181
+ height: actors_dims[:height],
182
+ position: :bottom
183
+ }
184
+
185
+ labels
186
+ end
187
+
188
+ def layout_options
189
+ # User journeys use layered algorithm for sequential task flow
190
+ # DIRECTION_RIGHT provides left-to-right horizontal timeline
191
+ # SIMPLE node placement maintains task order in journey sequence
192
+ build_elk_options(
193
+ algorithm: ALGORITHM_LAYERED,
194
+ direction: DIRECTION_RIGHT,
195
+ ElkOptions::NODE_NODE_SPACING => TASK_SPACING,
196
+ ElkOptions::LAYER_SPACING => TASK_SPACING,
197
+ ElkOptions::EDGE_NODE_SPACING => 30,
198
+ ElkOptions::EDGE_EDGE_SPACING => 20,
199
+ # SIMPLE node placement for chronological task ordering
200
+ ElkOptions::NODE_PLACEMENT => 'SIMPLE',
201
+ ElkOptions::MODEL_ORDER => 'NODES_AND_EDGES',
202
+ ElkOptions::HIERARCHY_HANDLING => 'INCLUDE_CHILDREN'
203
+ )
204
+ end
205
+ end
206
+ end
207
+ end