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,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../diagram/gantt"
5
+ require "date"
6
+
7
+ module Sirena
8
+ module Transform
9
+ # Gantt chart transformer for converting gantt models to renderable structure.
10
+ #
11
+ # Handles date calculations, dependency resolution, and timeline positioning.
12
+ #
13
+ # @example Transform a Gantt chart
14
+ # transform = GanttTransform.new
15
+ # data = transform.to_graph(gantt_diagram)
16
+ class GanttTransform < Base
17
+ # Converts a Gantt diagram to a layout structure with calculated positions.
18
+ #
19
+ # @param diagram [Diagram::GanttChart] the Gantt diagram to transform
20
+ # @return [Hash] data structure for rendering
21
+ # @raise [TransformError] if diagram is invalid
22
+ def to_graph(diagram)
23
+ @diagram = diagram
24
+ @task_map = build_task_map(diagram)
25
+
26
+ # Calculate dates for all tasks
27
+ calculate_task_dates
28
+
29
+ # Determine timeline range
30
+ timeline = calculate_timeline
31
+
32
+ {
33
+ id: "gantt",
34
+ title: diagram.title,
35
+ date_format: diagram.date_format,
36
+ axis_format: diagram.axis_format,
37
+ tick_interval: diagram.tick_interval,
38
+ excludes: diagram.excludes,
39
+ today_marker: diagram.today_marker,
40
+ sections: transform_sections(diagram, timeline),
41
+ timeline: timeline,
42
+ metadata: {
43
+ section_count: diagram.sections.length,
44
+ task_count: total_task_count(diagram)
45
+ }
46
+ }
47
+ end
48
+
49
+ private
50
+
51
+ def build_task_map(diagram)
52
+ map = {}
53
+ diagram.sections.each do |section|
54
+ section.tasks.each do |task|
55
+ map[task.id] = task if task.id
56
+ end
57
+ end
58
+ map
59
+ end
60
+
61
+ def calculate_task_dates
62
+ # First pass: calculate tasks with explicit dates
63
+ @diagram.sections.each do |section|
64
+ section.tasks.each do |task|
65
+ calculate_task_date(task) if task.start_date && !task.after_task
66
+ end
67
+ end
68
+
69
+ # Second pass: resolve dependencies
70
+ max_iterations = 100
71
+ iteration = 0
72
+ loop do
73
+ changed = false
74
+ @diagram.sections.each do |section|
75
+ section.tasks.each do |task|
76
+ if !task.calculated_start && (task.after_task || task.until_task)
77
+ if resolve_task_dependency(task)
78
+ changed = true
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ iteration += 1
85
+ break if !changed || iteration >= max_iterations
86
+ end
87
+ end
88
+
89
+ def calculate_task_date(task)
90
+ return if task.calculated_start
91
+
92
+ if task.start_date
93
+ task.calculated_start = parse_date(task.start_date)
94
+ end
95
+
96
+ if task.end_date
97
+ task.calculated_end = parse_date(task.end_date)
98
+ elsif task.duration && task.calculated_start
99
+ task.calculated_end = add_duration(task.calculated_start, task.duration)
100
+ end
101
+
102
+ task.calculated_start
103
+ end
104
+
105
+ def resolve_task_dependency(task)
106
+ if task.after_task
107
+ ref_task = @task_map[task.after_task]
108
+ return false unless ref_task && ref_task.calculated_end
109
+
110
+ task.calculated_start = ref_task.calculated_end
111
+ if task.duration
112
+ task.calculated_end = add_duration(task.calculated_start, task.duration)
113
+ elsif task.end_date
114
+ task.calculated_end = parse_date(task.end_date)
115
+ elsif task.until_task
116
+ until_task = @task_map[task.until_task]
117
+ task.calculated_end = until_task.calculated_start if until_task
118
+ end
119
+ return true
120
+ end
121
+
122
+ if task.until_task && task.start_date
123
+ task.calculated_start = parse_date(task.start_date)
124
+ until_task = @task_map[task.until_task]
125
+ if until_task && until_task.calculated_start
126
+ task.calculated_end = until_task.calculated_start
127
+ return true
128
+ end
129
+ end
130
+
131
+ false
132
+ end
133
+
134
+ def parse_date(date_str)
135
+ # Handle various date formats
136
+ Date.parse(date_str)
137
+ rescue ArgumentError
138
+ # Default to today if parsing fails
139
+ Date.today
140
+ end
141
+
142
+ def add_duration(start_date, duration_str)
143
+ # Parse duration string (e.g., "30d", "2w", "48h", "1M")
144
+ value = duration_str.to_i
145
+ unit = duration_str[-1]
146
+
147
+ case unit
148
+ when "d"
149
+ start_date + value
150
+ when "w"
151
+ start_date + (value * 7)
152
+ when "h"
153
+ # Convert hours to days (rough approximation)
154
+ start_date + (value / 24.0).ceil
155
+ when "M"
156
+ # Add months (rough approximation)
157
+ start_date >> value
158
+ else
159
+ start_date + value # Default to days
160
+ end
161
+ end
162
+
163
+ def calculate_timeline
164
+ min_date = nil
165
+ max_date = nil
166
+
167
+ @diagram.sections.each do |section|
168
+ section.tasks.each do |task|
169
+ if task.calculated_start
170
+ min_date = task.calculated_start if !min_date || task.calculated_start < min_date
171
+ end
172
+ if task.calculated_end
173
+ max_date = task.calculated_end if !max_date || task.calculated_end > max_date
174
+ end
175
+ end
176
+ end
177
+
178
+ # Default to a reasonable range if no dates found
179
+ min_date ||= Date.today
180
+ max_date ||= Date.today + 30
181
+
182
+ # Add padding
183
+ min_date -= 1
184
+ max_date += 1
185
+
186
+ {
187
+ start_date: min_date,
188
+ end_date: max_date,
189
+ total_days: (max_date - min_date).to_i
190
+ }
191
+ end
192
+
193
+ def transform_sections(diagram, timeline)
194
+ diagram.sections.map.with_index do |section, section_index|
195
+ {
196
+ id: "section_#{section_index}",
197
+ name: section.name,
198
+ tasks: transform_tasks(section, timeline, section_index)
199
+ }
200
+ end
201
+ end
202
+
203
+ def transform_tasks(section, timeline, section_index)
204
+ section.tasks.map.with_index do |task, task_index|
205
+ {
206
+ id: task.id || "task_#{section_index}_#{task_index}",
207
+ description: task.description,
208
+ tags: task.tags,
209
+ done: task.done?,
210
+ active: task.active?,
211
+ critical: task.critical?,
212
+ milestone: task.milestone?,
213
+ start_date: task.calculated_start,
214
+ end_date: task.calculated_end,
215
+ start_x: calculate_x_position(task.calculated_start, timeline),
216
+ width: calculate_width(task.calculated_start, task.calculated_end, timeline),
217
+ click_href: task.click_href,
218
+ click_callback: task.click_callback
219
+ }
220
+ end
221
+ end
222
+
223
+ def calculate_x_position(date, timeline)
224
+ return 0 unless date
225
+
226
+ days_from_start = (date - timeline[:start_date]).to_i
227
+ (days_from_start.to_f / timeline[:total_days]) * 800 # 800 is the timeline width
228
+ end
229
+
230
+ def calculate_width(start_date, end_date, timeline)
231
+ return 20 unless start_date && end_date # Minimum width for milestones
232
+
233
+ duration_days = (end_date - start_date).to_i
234
+ return 10 if duration_days <= 0 # Milestone or zero duration
235
+
236
+ (duration_days.to_f / timeline[:total_days]) * 800
237
+ end
238
+
239
+ def total_task_count(diagram)
240
+ diagram.sections.sum { |s| s.tasks.length }
241
+ end
242
+ end
243
+ end
244
+ end
245
+
246
+ # Extend GanttTask to hold calculated dates
247
+ module Sirena
248
+ module Diagram
249
+ class GanttTask
250
+ attr_accessor :calculated_start, :calculated_end
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,283 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sirena
4
+ module Transform
5
+ # Transforms a GitGraph diagram into a positioned layout structure.
6
+ #
7
+ # Unlike other transformers that use ELK layout, GitGraph uses a custom
8
+ # layout algorithm that assigns commits to horizontal lanes (Y positions)
9
+ # and sequential X positions based on commit order.
10
+ #
11
+ # The layout algorithm handles:
12
+ # - Branch lane assignment (main branch at top)
13
+ # - Commit positioning (chronological X coordinates)
14
+ # - Parent-child relationships for drawing connections
15
+ # - Merge point tracking for drawing merge arrows
16
+ # - Cherry-pick visualization
17
+ #
18
+ # @example Transform a git graph
19
+ # transform = Transform::GitGraph.new
20
+ # layout = transform.to_graph(diagram)
21
+ class GitGraph
22
+ # Spacing between commits horizontally
23
+ COMMIT_SPACING = 80
24
+
25
+ # Spacing between branch lanes vertically
26
+ LANE_SPACING = 60
27
+
28
+ # Radius of commit circles
29
+ COMMIT_RADIUS = 8
30
+
31
+ # Default branch colors (cycling through these)
32
+ DEFAULT_COLORS = %w[
33
+ #2563eb #7c3aed #db2777 #ea580c #ca8a04
34
+ #16a34a #0891b2 #4f46e5 #c026d3 #dc2626
35
+ ].freeze
36
+
37
+ # Transforms the diagram into a layout structure.
38
+ #
39
+ # @param diagram [Diagram::GitGraph] the git graph diagram
40
+ # @return [Hash] layout data with commits, branches, and connections
41
+ def to_graph(diagram)
42
+ # Build commit lookup and parent tracking
43
+ commits_by_id = build_commit_lookup(diagram.commits)
44
+ branch_info = build_branch_info(diagram)
45
+
46
+ # Assign lanes to branches
47
+ lane_assignments = assign_lanes(diagram, branch_info)
48
+
49
+ # Position commits
50
+ positioned_commits = position_commits(
51
+ diagram.commits,
52
+ commits_by_id,
53
+ lane_assignments
54
+ )
55
+
56
+ # Build connections between commits
57
+ connections = build_connections(
58
+ positioned_commits,
59
+ commits_by_id
60
+ )
61
+
62
+ # Build branch metadata
63
+ branches = build_branch_metadata(
64
+ diagram.branches,
65
+ lane_assignments,
66
+ positioned_commits
67
+ )
68
+
69
+ {
70
+ commits: positioned_commits,
71
+ branches: branches,
72
+ connections: connections,
73
+ width: calculate_width(positioned_commits),
74
+ height: calculate_height(lane_assignments)
75
+ }
76
+ end
77
+
78
+ private
79
+
80
+ # Builds a lookup hash of commits by ID.
81
+ #
82
+ # @param commits [Array<Diagram::GitGraph::Commit>] commits
83
+ # @return [Hash<String, Diagram::GitGraph::Commit>] commit lookup
84
+ def build_commit_lookup(commits)
85
+ lookup = {}
86
+ commits.each_with_index do |commit, idx|
87
+ # Use index as fallback ID if no ID specified
88
+ key = commit.id || "commit_#{idx}"
89
+ lookup[key] = commit
90
+ end
91
+ lookup
92
+ end
93
+
94
+ # Builds branch information from diagram.
95
+ #
96
+ # @param diagram [Diagram::GitGraph] diagram
97
+ # @return [Hash] branch info with parent relationships
98
+ def build_branch_info(diagram)
99
+ info = {}
100
+
101
+ # Start with main branch
102
+ info["main"] = {
103
+ parent: nil,
104
+ order: 0,
105
+ created_at: nil
106
+ }
107
+
108
+ # Add other branches
109
+ diagram.branches.each do |branch|
110
+ info[branch.name] = {
111
+ parent: branch.parent_branch || "main",
112
+ order: branch.order || info.size,
113
+ created_at: branch.created_at_commit
114
+ }
115
+ end
116
+
117
+ info
118
+ end
119
+
120
+ # Assigns lanes (Y positions) to branches.
121
+ #
122
+ # Main branch gets lane 0, child branches get lanes below parent.
123
+ #
124
+ # @param diagram [Diagram::GitGraph] diagram
125
+ # @param branch_info [Hash] branch information
126
+ # @return [Hash<String, Integer>] branch name to lane number
127
+ def assign_lanes(diagram, branch_info)
128
+ lanes = {}
129
+ next_lane = 0
130
+
131
+ # Assign main branch to lane 0
132
+ lanes["main"] = next_lane
133
+ next_lane += 1
134
+
135
+ # Sort branches by order then by creation
136
+ sorted_branches = diagram.branches.sort_by do |b|
137
+ [branch_info[b.name][:order] || 999, b.name]
138
+ end
139
+
140
+ # Assign lanes to other branches
141
+ sorted_branches.each do |branch|
142
+ lanes[branch.name] = next_lane
143
+ next_lane += 1
144
+ end
145
+
146
+ lanes
147
+ end
148
+
149
+ # Positions commits with X and Y coordinates.
150
+ #
151
+ # @param commits [Array<Diagram::GitGraph::Commit>] commits
152
+ # @param commits_by_id [Hash] commit lookup
153
+ # @param lane_assignments [Hash] branch to lane mapping
154
+ # @return [Array<Hash>] positioned commits
155
+ def position_commits(commits, commits_by_id, lane_assignments)
156
+ positioned = []
157
+
158
+ commits.each_with_index do |commit, idx|
159
+ # X position is based on commit order
160
+ x = idx * COMMIT_SPACING + COMMIT_SPACING
161
+
162
+ # Y position is based on branch lane
163
+ branch = commit.branch_name || "main"
164
+ lane = lane_assignments[branch] || 0
165
+ y = lane * LANE_SPACING + LANE_SPACING
166
+
167
+ commit_id = commit.id || "commit_#{idx}"
168
+
169
+ positioned << {
170
+ id: commit_id,
171
+ x: x,
172
+ y: y,
173
+ branch: branch,
174
+ lane: lane,
175
+ type: commit.type || "NORMAL",
176
+ tag: commit.tag,
177
+ parent_ids: commit.parent_ids,
178
+ is_merge: commit.is_merge || false,
179
+ merge_branch: commit.merge_branch,
180
+ is_cherry_pick: commit.is_cherry_pick || false,
181
+ cherry_pick_parent: commit.cherry_pick_parent,
182
+ # Store original commit for reference
183
+ original: commit
184
+ }
185
+ end
186
+
187
+ positioned
188
+ end
189
+
190
+ # Builds connections between commits.
191
+ #
192
+ # @param positioned_commits [Array<Hash>] positioned commits
193
+ # @param commits_by_id [Hash] commit lookup
194
+ # @return [Array<Hash>] connections with from/to commits and type
195
+ def build_connections(positioned_commits, commits_by_id)
196
+ connections = []
197
+ commit_positions = positioned_commits.each_with_object({}) do |c, h|
198
+ h[c[:id]] = c
199
+ end
200
+
201
+ positioned_commits.each do |commit|
202
+ # Connect to parent commits
203
+ commit[:parent_ids].each do |parent_id|
204
+ parent = commit_positions[parent_id]
205
+ next unless parent
206
+
207
+ connection_type = if commit[:is_merge]
208
+ :merge
209
+ elsif commit[:is_cherry_pick]
210
+ :cherry_pick
211
+ else
212
+ :normal
213
+ end
214
+
215
+ connections << {
216
+ from: parent[:id],
217
+ to: commit[:id],
218
+ from_x: parent[:x],
219
+ from_y: parent[:y],
220
+ to_x: commit[:x],
221
+ to_y: commit[:y],
222
+ from_branch: parent[:branch],
223
+ to_branch: commit[:branch],
224
+ type: connection_type
225
+ }
226
+ end
227
+ end
228
+
229
+ connections
230
+ end
231
+
232
+ # Builds branch metadata for rendering.
233
+ #
234
+ # @param branches [Array<Diagram::GitGraph::Branch>] branches
235
+ # @param lane_assignments [Hash] lane assignments
236
+ # @param positioned_commits [Array<Hash>] positioned commits
237
+ # @return [Array<Hash>] branch metadata
238
+ def build_branch_metadata(branches, lane_assignments, positioned_commits)
239
+ metadata = []
240
+
241
+ # Add main branch
242
+ metadata << {
243
+ name: "main",
244
+ lane: lane_assignments["main"] || 0,
245
+ color: DEFAULT_COLORS[0]
246
+ }
247
+
248
+ # Add other branches with cycling colors
249
+ branches.each_with_index do |branch, idx|
250
+ metadata << {
251
+ name: branch.name,
252
+ lane: lane_assignments[branch.name] || (idx + 1),
253
+ color: DEFAULT_COLORS[(idx + 1) % DEFAULT_COLORS.length]
254
+ }
255
+ end
256
+
257
+ metadata
258
+ end
259
+
260
+ # Calculates total width needed for the layout.
261
+ #
262
+ # @param positioned_commits [Array<Hash>] positioned commits
263
+ # @return [Numeric] total width in pixels
264
+ def calculate_width(positioned_commits)
265
+ return COMMIT_SPACING * 2 if positioned_commits.empty?
266
+
267
+ max_x = positioned_commits.map { |c| c[:x] }.max
268
+ max_x + COMMIT_SPACING
269
+ end
270
+
271
+ # Calculates total height needed for the layout.
272
+ #
273
+ # @param lane_assignments [Hash] lane assignments
274
+ # @return [Numeric] total height in pixels
275
+ def calculate_height(lane_assignments)
276
+ return LANE_SPACING * 2 if lane_assignments.empty?
277
+
278
+ max_lane = lane_assignments.values.max
279
+ (max_lane + 1) * LANE_SPACING + LANE_SPACING
280
+ end
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../diagram/info'
5
+
6
+ module Sirena
7
+ module Transform
8
+ # Info diagram transformer for converting info models to renderable structure.
9
+ #
10
+ # Info diagrams have no complex layout requirements - they simply display
11
+ # an informational message. This transformer prepares basic data for rendering.
12
+ #
13
+ # @example Transform an info diagram
14
+ # transform = InfoTransform.new
15
+ # data = transform.to_graph(info_diagram)
16
+ class InfoTransform < Base
17
+ # Converts an info diagram to a simple data structure.
18
+ #
19
+ # Info diagrams don't need layout computation. This method validates
20
+ # the diagram and returns a structure for the renderer.
21
+ #
22
+ # @param diagram [Diagram::Info] the info 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 || 'info',
30
+ title: diagram.title,
31
+ show_info: diagram.show_info || false,
32
+ metadata: {
33
+ diagram_type: :info
34
+ }
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end