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,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sirena
4
+ module Transform
5
+ # Transforms a Kanban diagram into a positioned layout structure.
6
+ #
7
+ # The layout algorithm handles:
8
+ # - Columns positioned horizontally
9
+ # - Cards stacked vertically within columns
10
+ # - Proper spacing and sizing
11
+ #
12
+ # @example Transform a kanban board
13
+ # transform = Transform::Kanban.new
14
+ # layout = transform.to_graph(diagram)
15
+ class Kanban
16
+ # Horizontal spacing between columns
17
+ COLUMN_HORIZONTAL_SPACING = 60
18
+
19
+ # Vertical spacing between cards
20
+ CARD_VERTICAL_SPACING = 15
21
+
22
+ # Column dimensions
23
+ COLUMN_WIDTH = 200
24
+ COLUMN_HEADER_HEIGHT = 50
25
+ COLUMN_PADDING = 10
26
+
27
+ # Card dimensions
28
+ CARD_HEIGHT = 80
29
+ CARD_PADDING = 10
30
+
31
+ # Metadata display height per item
32
+ METADATA_LINE_HEIGHT = 18
33
+
34
+ # Transforms the diagram into a layout structure.
35
+ #
36
+ # @param diagram [Diagram::Kanban] the kanban diagram
37
+ # @return [Hash] layout data with columns, cards, and dimensions
38
+ def to_graph(diagram)
39
+ return empty_graph if diagram.columns.empty?
40
+
41
+ # Position columns horizontally
42
+ positioned_columns = position_columns(diagram.columns)
43
+
44
+ # Position cards within each column
45
+ positioned_cards = position_cards(positioned_columns)
46
+
47
+ # Calculate overall bounds
48
+ bounds = calculate_bounds(positioned_columns, positioned_cards)
49
+
50
+ {
51
+ columns: positioned_columns,
52
+ cards: positioned_cards,
53
+ width: bounds[:width],
54
+ height: bounds[:height]
55
+ }
56
+ end
57
+
58
+ private
59
+
60
+ def empty_graph
61
+ {
62
+ columns: [],
63
+ cards: [],
64
+ width: 0,
65
+ height: 0
66
+ }
67
+ end
68
+
69
+ # Positions columns horizontally
70
+ #
71
+ # @param columns [Array<Diagram::KanbanColumn>] columns to position
72
+ # @return [Array<Hash>] positioned columns
73
+ def position_columns(columns)
74
+ positioned = []
75
+ current_x = 0
76
+
77
+ columns.each do |column|
78
+ positioned << {
79
+ id: column.id,
80
+ title: column.title,
81
+ x: current_x,
82
+ y: 0,
83
+ width: COLUMN_WIDTH,
84
+ height: calculate_column_height(column),
85
+ card_count: column.cards.size,
86
+ original: column
87
+ }
88
+
89
+ current_x += COLUMN_WIDTH + COLUMN_HORIZONTAL_SPACING
90
+ end
91
+
92
+ positioned
93
+ end
94
+
95
+ # Positions cards within their columns
96
+ #
97
+ # @param positioned_columns [Array<Hash>] positioned columns
98
+ # @return [Array<Hash>] positioned cards
99
+ def position_cards(positioned_columns)
100
+ cards = []
101
+
102
+ positioned_columns.each do |column_data|
103
+ column = column_data[:original]
104
+ column_x = column_data[:x]
105
+ current_y = COLUMN_HEADER_HEIGHT + COLUMN_PADDING
106
+
107
+ column.cards.each do |card|
108
+ card_height = calculate_card_height(card)
109
+
110
+ cards << {
111
+ id: card.id,
112
+ text: card.text,
113
+ column_id: column.id,
114
+ x: column_x + COLUMN_PADDING,
115
+ y: current_y,
116
+ width: COLUMN_WIDTH - (COLUMN_PADDING * 2),
117
+ height: card_height,
118
+ metadata: card.metadata,
119
+ has_metadata: card.has_metadata?,
120
+ original: card
121
+ }
122
+
123
+ current_y += card_height + CARD_VERTICAL_SPACING
124
+ end
125
+ end
126
+
127
+ cards
128
+ end
129
+
130
+ # Calculates the height needed for a column
131
+ #
132
+ # @param column [Diagram::KanbanColumn] column
133
+ # @return [Numeric] column height
134
+ def calculate_column_height(column)
135
+ return COLUMN_HEADER_HEIGHT + COLUMN_PADDING if column.cards.empty?
136
+
137
+ # Header + padding + sum of card heights + spacing between cards
138
+ total_card_height = column.cards.sum { |card| calculate_card_height(card) }
139
+ total_spacing = (column.cards.size - 1) * CARD_VERTICAL_SPACING
140
+ bottom_padding = COLUMN_PADDING
141
+
142
+ COLUMN_HEADER_HEIGHT + COLUMN_PADDING +
143
+ total_card_height + total_spacing + bottom_padding
144
+ end
145
+
146
+ # Calculates the height needed for a card
147
+ #
148
+ # @param card [Diagram::KanbanCard] card
149
+ # @return [Numeric] card height
150
+ def calculate_card_height(card)
151
+ base_height = CARD_HEIGHT
152
+
153
+ # Add height for metadata if present
154
+ if card.has_metadata?
155
+ metadata_count = card.metadata.size
156
+ base_height + (metadata_count * METADATA_LINE_HEIGHT)
157
+ else
158
+ base_height
159
+ end
160
+ end
161
+
162
+ # Calculates the bounding box for the entire board
163
+ #
164
+ # @param columns [Array<Hash>] positioned columns
165
+ # @param cards [Array<Hash>] positioned cards
166
+ # @return [Hash] width and height
167
+ def calculate_bounds(columns, cards)
168
+ return { width: 0, height: 0 } if columns.empty?
169
+
170
+ max_x = columns.map { |c| c[:x] + c[:width] }.max
171
+ max_y = columns.map { |c| c[:height] }.max
172
+
173
+ {
174
+ width: max_x,
175
+ height: max_y
176
+ }
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sirena
4
+ module Transform
5
+ # Transforms a Mindmap diagram into a positioned layout structure.
6
+ #
7
+ # The layout algorithm handles:
8
+ # - Tree-based layout with root at center
9
+ # - Radial positioning of branches
10
+ # - Level-based spacing
11
+ # - Connection path calculation
12
+ #
13
+ # @example Transform a mindmap
14
+ # transform = Transform::Mindmap.new
15
+ # layout = transform.to_graph(diagram)
16
+ class Mindmap
17
+ # Horizontal spacing between sibling nodes
18
+ NODE_HORIZONTAL_SPACING = 120
19
+
20
+ # Vertical spacing between levels
21
+ LEVEL_VERTICAL_SPACING = 80
22
+
23
+ # Default node dimensions
24
+ DEFAULT_NODE_WIDTH = 100
25
+ DEFAULT_NODE_HEIGHT = 40
26
+
27
+ # Padding for root node
28
+ ROOT_PADDING = 20
29
+
30
+ # Transforms the diagram into a layout structure.
31
+ #
32
+ # @param diagram [Diagram::Mindmap] the mindmap diagram
33
+ # @return [Hash] layout data with nodes and connections
34
+ def to_graph(diagram)
35
+ return empty_graph unless diagram.root
36
+
37
+ # Position nodes using tree layout
38
+ positioned_nodes = position_tree(diagram.root)
39
+
40
+ # Build connections between nodes
41
+ connections = build_connections(diagram.root)
42
+
43
+ # Calculate bounds
44
+ bounds = calculate_bounds(positioned_nodes)
45
+
46
+ {
47
+ nodes: positioned_nodes,
48
+ connections: connections,
49
+ width: bounds[:width],
50
+ height: bounds[:height],
51
+ root: positioned_nodes.first
52
+ }
53
+ end
54
+
55
+ private
56
+
57
+ def empty_graph
58
+ {
59
+ nodes: [],
60
+ connections: [],
61
+ width: 0,
62
+ height: 0,
63
+ root: nil
64
+ }
65
+ end
66
+
67
+ # Positions nodes in a tree layout
68
+ #
69
+ # @param root [Diagram::Mindmap::MindmapNode] root node
70
+ # @return [Array<Hash>] positioned nodes
71
+ def position_tree(root)
72
+ nodes = []
73
+
74
+ # Start with root at center-top
75
+ root_width = estimate_node_width(root)
76
+ root_height = estimate_node_height(root)
77
+
78
+ # Calculate tree width to center root
79
+ tree_width = calculate_tree_width(root)
80
+ root_x = tree_width / 2
81
+
82
+ # Position root
83
+ nodes << {
84
+ id: root.id,
85
+ content: root.content,
86
+ x: root_x,
87
+ y: ROOT_PADDING,
88
+ width: root_width,
89
+ height: root_height,
90
+ level: root.level,
91
+ shape: root.shape,
92
+ icon: root.icon,
93
+ classes: root.classes,
94
+ original: root
95
+ }
96
+
97
+ # Position children recursively
98
+ if root.children.any?
99
+ position_children(
100
+ root,
101
+ root_x,
102
+ ROOT_PADDING + root_height + LEVEL_VERTICAL_SPACING,
103
+ nodes
104
+ )
105
+ end
106
+
107
+ nodes
108
+ end
109
+
110
+ # Positions children of a node
111
+ #
112
+ # @param parent [Diagram::Mindmap::MindmapNode] parent node
113
+ # @param parent_x [Numeric] parent X position
114
+ # @param y [Numeric] Y position for this level
115
+ # @param nodes [Array<Hash>] accumulator for positioned nodes
116
+ def position_children(parent, parent_x, y, nodes)
117
+ children = parent.children
118
+ return if children.empty?
119
+
120
+ # Calculate total width needed for all children
121
+ total_width = children.sum { |c| estimate_subtree_width(c) }
122
+ total_width += (children.size - 1) * NODE_HORIZONTAL_SPACING
123
+
124
+ # Start x position (centered under parent)
125
+ start_x = parent_x - (total_width / 2)
126
+ current_x = start_x
127
+
128
+ children.each do |child|
129
+ child_width = estimate_node_width(child)
130
+ child_height = estimate_node_height(child)
131
+ subtree_width = estimate_subtree_width(child)
132
+
133
+ # Center the node within its subtree space
134
+ node_x = current_x + (subtree_width / 2)
135
+
136
+ nodes << {
137
+ id: child.id,
138
+ content: child.content,
139
+ x: node_x,
140
+ y: y,
141
+ width: child_width,
142
+ height: child_height,
143
+ level: child.level,
144
+ shape: child.shape,
145
+ icon: child.icon,
146
+ classes: child.classes,
147
+ parent_id: parent.id,
148
+ original: child
149
+ }
150
+
151
+ # Recursively position grandchildren
152
+ if child.children.any?
153
+ position_children(
154
+ child,
155
+ node_x,
156
+ y + child_height + LEVEL_VERTICAL_SPACING,
157
+ nodes
158
+ )
159
+ end
160
+
161
+ current_x += subtree_width + NODE_HORIZONTAL_SPACING
162
+ end
163
+ end
164
+
165
+ # Estimates the width of a single node based on content and shape
166
+ #
167
+ # @param node [Diagram::Mindmap::MindmapNode] node
168
+ # @return [Numeric] estimated width
169
+ def estimate_node_width(node)
170
+ # Base width on content length
171
+ content_length = node.content.to_s.length
172
+ base_width = [content_length * 8 + 20, DEFAULT_NODE_WIDTH].max
173
+
174
+ # Adjust for shape
175
+ case node.shape
176
+ when "circle", "hexagon"
177
+ base_width * 1.2
178
+ else
179
+ base_width
180
+ end
181
+ end
182
+
183
+ # Estimates the height of a node
184
+ #
185
+ # @param node [Diagram::Mindmap::MindmapNode] node
186
+ # @return [Numeric] estimated height
187
+ def estimate_node_height(node)
188
+ DEFAULT_NODE_HEIGHT
189
+ end
190
+
191
+ # Calculates the total width needed for a subtree
192
+ #
193
+ # @param node [Diagram::Mindmap::MindmapNode] root of subtree
194
+ # @return [Numeric] total width
195
+ def estimate_subtree_width(node)
196
+ node_width = estimate_node_width(node)
197
+ return node_width if node.children.empty?
198
+
199
+ # Width is max of node width or sum of children widths
200
+ children_width = node.children.sum { |c| estimate_subtree_width(c) }
201
+ children_width += (node.children.size - 1) * NODE_HORIZONTAL_SPACING
202
+
203
+ [node_width, children_width].max
204
+ end
205
+
206
+ # Calculates the total width of the entire tree
207
+ #
208
+ # @param root [Diagram::Mindmap::MindmapNode] root node
209
+ # @return [Numeric] tree width
210
+ def calculate_tree_width(root)
211
+ estimate_subtree_width(root) + ROOT_PADDING * 2
212
+ end
213
+
214
+ # Builds connections between parent and child nodes
215
+ #
216
+ # @param node [Diagram::Mindmap::MindmapNode] current node
217
+ # @param connections [Array<Hash>] accumulator
218
+ # @return [Array<Hash>] all connections
219
+ def build_connections(node, connections = [])
220
+ node.children.each do |child|
221
+ connections << {
222
+ from: node.id,
223
+ to: child.id,
224
+ type: :parent_child
225
+ }
226
+
227
+ # Recursively build connections for children
228
+ build_connections(child, connections)
229
+ end
230
+
231
+ connections
232
+ end
233
+
234
+ # Calculates the bounding box for all positioned nodes
235
+ #
236
+ # @param nodes [Array<Hash>] positioned nodes
237
+ # @return [Hash] width and height
238
+ def calculate_bounds(nodes)
239
+ return { width: 0, height: 0 } if nodes.empty?
240
+
241
+ max_x = nodes.map { |n| n[:x] + n[:width] / 2 }.max
242
+ max_y = nodes.map { |n| n[:y] + n[:height] }.max
243
+
244
+ {
245
+ width: max_x + ROOT_PADDING,
246
+ height: max_y + ROOT_PADDING
247
+ }
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sirena
4
+ module Transform
5
+ # Transforms a PacketDiagram into a positioned layout structure.
6
+ #
7
+ # The layout algorithm handles:
8
+ # - Organizing fields into rows based on bit positions
9
+ # - Calculating cell positions in the packet grid
10
+ # - Handling fields that span multiple bits
11
+ # - Typical packet width is 32 bits per row
12
+ #
13
+ # @example Transform a packet diagram
14
+ # transform = Transform::Packet.new
15
+ # layout = transform.to_graph(diagram)
16
+ class Packet
17
+ # Default number of bits per row (standard packet width)
18
+ BITS_PER_ROW = 32
19
+
20
+ # Cell dimensions
21
+ CELL_WIDTH = 30
22
+ CELL_HEIGHT = 40
23
+
24
+ # Padding around the diagram
25
+ PADDING = 40
26
+
27
+ # Header height for bit position markers
28
+ HEADER_HEIGHT = 30
29
+
30
+ # Title spacing
31
+ TITLE_HEIGHT = 40
32
+ TITLE_MARGIN = 20
33
+
34
+ # Transforms the diagram into a layout structure.
35
+ #
36
+ # @param diagram [Diagram::PacketDiagram] the packet diagram
37
+ # @return [Hash] layout data with positioned fields and dimensions
38
+ def to_graph(diagram)
39
+ return empty_layout if diagram.fields.empty?
40
+
41
+ # Calculate the number of rows needed
42
+ row_count = diagram.row_count(BITS_PER_ROW)
43
+
44
+ # Position each field
45
+ positioned_fields = position_fields(diagram.fields, row_count)
46
+
47
+ # Calculate dimensions
48
+ width = BITS_PER_ROW * CELL_WIDTH + (PADDING * 2)
49
+ content_height = row_count * CELL_HEIGHT + HEADER_HEIGHT
50
+ title_offset = diagram.title ? TITLE_HEIGHT + TITLE_MARGIN : 0
51
+ height = content_height + (PADDING * 2) + title_offset
52
+
53
+ {
54
+ fields: positioned_fields,
55
+ row_count: row_count,
56
+ bits_per_row: BITS_PER_ROW,
57
+ cell_width: CELL_WIDTH,
58
+ cell_height: CELL_HEIGHT,
59
+ padding: PADDING,
60
+ header_height: HEADER_HEIGHT,
61
+ title_height: diagram.title ? TITLE_HEIGHT : 0,
62
+ title_margin: diagram.title ? TITLE_MARGIN : 0,
63
+ width: width,
64
+ height: height,
65
+ title: diagram.title
66
+ }
67
+ end
68
+
69
+ private
70
+
71
+ # Returns an empty layout structure.
72
+ #
73
+ # @return [Hash] empty layout
74
+ def empty_layout
75
+ {
76
+ fields: [],
77
+ row_count: 0,
78
+ bits_per_row: BITS_PER_ROW,
79
+ cell_width: CELL_WIDTH,
80
+ cell_height: CELL_HEIGHT,
81
+ padding: PADDING,
82
+ header_height: HEADER_HEIGHT,
83
+ title_height: 0,
84
+ title_margin: 0,
85
+ width: PADDING * 2,
86
+ height: PADDING * 2,
87
+ title: nil
88
+ }
89
+ end
90
+
91
+ # Positions all fields in the grid.
92
+ #
93
+ # @param fields [Array<Diagram::PacketField>] fields to position
94
+ # @param row_count [Integer] total number of rows
95
+ # @return [Array<Hash>] positioned fields with coordinates
96
+ def position_fields(fields, row_count)
97
+ positioned = []
98
+
99
+ fields.each do |field|
100
+ # Handle fields that may span multiple rows
101
+ if field.spans_rows?(BITS_PER_ROW)
102
+ # Split into multiple visual segments
103
+ positioned.concat(split_field_across_rows(field))
104
+ else
105
+ # Single row field
106
+ positioned << position_single_field(field)
107
+ end
108
+ end
109
+
110
+ positioned
111
+ end
112
+
113
+ # Positions a field that fits in a single row.
114
+ #
115
+ # @param field [Diagram::PacketField] field to position
116
+ # @return [Hash] positioned field data
117
+ def position_single_field(field)
118
+ row = field.start_row(BITS_PER_ROW)
119
+ start_col = field.start_bit_in_row(BITS_PER_ROW)
120
+ end_col = field.end_bit_in_row(BITS_PER_ROW)
121
+
122
+ x = PADDING + (start_col * CELL_WIDTH)
123
+ y = PADDING + HEADER_HEIGHT + (row * CELL_HEIGHT)
124
+ width = (end_col - start_col + 1) * CELL_WIDTH
125
+ height = CELL_HEIGHT
126
+
127
+ {
128
+ label: field.label,
129
+ bit_start: field.bit_start,
130
+ bit_end: field.bit_end,
131
+ x: x,
132
+ y: y,
133
+ width: width,
134
+ height: height,
135
+ row: row,
136
+ start_col: start_col,
137
+ end_col: end_col
138
+ }
139
+ end
140
+
141
+ # Splits a field that spans multiple rows into visual segments.
142
+ #
143
+ # @param field [Diagram::PacketField] field to split
144
+ # @return [Array<Hash>] array of positioned segments
145
+ def split_field_across_rows(field)
146
+ segments = []
147
+ current_bit = field.bit_start
148
+
149
+ while current_bit <= field.bit_end
150
+ row = current_bit / BITS_PER_ROW
151
+ start_col = current_bit % BITS_PER_ROW
152
+
153
+ # Determine end column for this row
154
+ row_end_bit = ((row + 1) * BITS_PER_ROW) - 1
155
+ segment_end_bit = [field.bit_end, row_end_bit].min
156
+ end_col = segment_end_bit % BITS_PER_ROW
157
+
158
+ x = PADDING + (start_col * CELL_WIDTH)
159
+ y = PADDING + HEADER_HEIGHT + (row * CELL_HEIGHT)
160
+ width = (end_col - start_col + 1) * CELL_WIDTH
161
+ height = CELL_HEIGHT
162
+
163
+ segments << {
164
+ label: field.label,
165
+ bit_start: current_bit,
166
+ bit_end: segment_end_bit,
167
+ x: x,
168
+ y: y,
169
+ width: width,
170
+ height: height,
171
+ row: row,
172
+ start_col: start_col,
173
+ end_col: end_col,
174
+ is_continuation: current_bit > field.bit_start,
175
+ is_final: segment_end_bit == field.bit_end
176
+ }
177
+
178
+ current_bit = segment_end_bit + 1
179
+ end
180
+
181
+ segments
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../diagram/pie'
5
+
6
+ module Sirena
7
+ module Transform
8
+ # Pie chart transformer for converting pie models to renderable structure.
9
+ #
10
+ # Unlike flowcharts and sequence diagrams which require complex layout
11
+ # computation, pie charts have a fixed circular layout. This transformer
12
+ # simply validates and prepares the diagram data for direct rendering.
13
+ #
14
+ # @example Transform a pie chart
15
+ # transform = PieTransform.new
16
+ # data = transform.to_graph(pie_diagram)
17
+ class PieTransform < Base
18
+ # Converts a pie diagram to a simple data structure.
19
+ #
20
+ # Pie charts don't need graph layout computation since they have
21
+ # a fixed circular layout. This method validates the diagram and
22
+ # returns a simple structure for the renderer.
23
+ #
24
+ # @param diagram [Diagram::Pie] the pie diagram to transform
25
+ # @return [Hash] data structure for rendering
26
+ # @raise [TransformError] if diagram is invalid
27
+ def to_graph(diagram)
28
+ raise TransformError, 'Invalid diagram' unless diagram.valid?
29
+
30
+ {
31
+ id: diagram.id || 'pie',
32
+ title: diagram.title,
33
+ show_data: diagram.show_data || false,
34
+ acc_title: diagram.acc_title,
35
+ acc_description: diagram.acc_description,
36
+ slices: transform_slices(diagram),
37
+ metadata: {
38
+ total_value: diagram.total_value,
39
+ slice_count: (diagram.slices || []).length
40
+ }
41
+ }
42
+ end
43
+
44
+ private
45
+
46
+ def transform_slices(diagram)
47
+ return [] if diagram.slices.nil? || diagram.slices.empty?
48
+
49
+ diagram.slices.map.with_index do |slice, index|
50
+ {
51
+ id: "slice_#{index}",
52
+ label: slice.label,
53
+ value: slice.value,
54
+ percentage: slice.percentage(diagram.total_value),
55
+ angle: diagram.slice_angle(slice),
56
+ index: index
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end