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,301 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Sirena
6
+ module Renderer
7
+ # Flowchart renderer for converting graphs to SVG.
8
+ #
9
+ # Converts a laid-out graph structure (with computed positions) into
10
+ # SVG using the Svg builder classes. Handles different node shapes,
11
+ # edge routing, and label positioning.
12
+ #
13
+ # @example Render a flowchart
14
+ # renderer = FlowchartRenderer.new
15
+ # svg = renderer.render(laid_out_graph)
16
+ class FlowchartRenderer < Base
17
+ # Renders a laid-out graph to SVG.
18
+ #
19
+ # @param graph [Hash] laid-out graph with node positions
20
+ # @return [Svg::Document] the rendered SVG document
21
+ def render(graph)
22
+ svg = create_document(graph)
23
+
24
+ # Render edges first (so they appear under nodes)
25
+ render_edges(graph, svg) if graph[:edges]
26
+
27
+ # Render nodes
28
+ render_nodes(graph, svg) if graph[:children]
29
+
30
+ svg
31
+ end
32
+
33
+ protected
34
+
35
+ def calculate_width(graph)
36
+ return 800 unless graph[:children]
37
+
38
+ max_x = graph[:children].map do |node|
39
+ (node[:x] || 0) + (node[:width] || 100)
40
+ end.max || 800
41
+
42
+ max_x + 40 # Add padding
43
+ end
44
+
45
+ def calculate_height(graph)
46
+ return 600 unless graph[:children]
47
+
48
+ max_y = graph[:children].map do |node|
49
+ (node[:y] || 0) + (node[:height] || 50)
50
+ end.max || 600
51
+
52
+ max_y + 40 # Add padding
53
+ end
54
+
55
+ def render_nodes(graph, svg)
56
+ graph[:children].each do |node|
57
+ render_node(node, svg)
58
+ end
59
+ end
60
+
61
+ def render_node(node, svg)
62
+ shape = node.dig(:metadata, :shape) || 'rect'
63
+
64
+ # Create group for node and its label
65
+ group = Svg::Group.new.tap do |g|
66
+ g.id = "node-#{node[:id]}"
67
+ end
68
+
69
+ # Render node shape
70
+ shape_element = create_node_shape(node, shape)
71
+ group.children << shape_element if shape_element
72
+
73
+ # Render node label
74
+ if node[:labels] && !node[:labels].empty?
75
+ label = node[:labels].first
76
+ text_element = create_node_label(node, label)
77
+ group.children << text_element if text_element
78
+ end
79
+
80
+ svg << group
81
+ end
82
+
83
+ def create_node_shape(node, shape)
84
+ x = node[:x] || 0
85
+ y = node[:y] || 0
86
+ width = node[:width] || 100
87
+ height = node[:height] || 50
88
+
89
+ case shape
90
+ when 'rect', 'subroutine'
91
+ create_rectangle(x, y, width, height)
92
+ when 'rounded', 'stadium'
93
+ create_rounded_rectangle(x, y, width, height)
94
+ when 'circle', 'double_circle'
95
+ create_circle_shape(x, y, width, height)
96
+ when 'rhombus'
97
+ create_rhombus(x, y, width, height)
98
+ when 'hexagon'
99
+ create_hexagon(x, y, width, height)
100
+ else
101
+ create_rectangle(x, y, width, height)
102
+ end
103
+ end
104
+
105
+ def create_rectangle(x, y, width, height)
106
+ Svg::Rect.new.tap do |rect|
107
+ rect.x = x
108
+ rect.y = y
109
+ rect.width = width
110
+ rect.height = height
111
+ apply_theme_to_node(rect)
112
+ end
113
+ end
114
+
115
+ def create_rounded_rectangle(x, y, width, height)
116
+ Svg::Rect.new.tap do |rect|
117
+ rect.x = x
118
+ rect.y = y
119
+ rect.width = width
120
+ rect.height = height
121
+ rect.rx = height / 2
122
+ rect.ry = height / 2
123
+ apply_theme_to_node(rect)
124
+ end
125
+ end
126
+
127
+ def create_circle_shape(x, y, width, height)
128
+ cx = x + width / 2
129
+ cy = y + height / 2
130
+ r = [width, height].min / 2
131
+
132
+ Svg::Circle.new.tap do |circle|
133
+ circle.cx = cx
134
+ circle.cy = cy
135
+ circle.r = r
136
+ apply_theme_to_node(circle)
137
+ end
138
+ end
139
+
140
+ def create_rhombus(x, y, width, height)
141
+ cx = x + width / 2
142
+ cy = y + height / 2
143
+
144
+ points = [
145
+ "#{cx},#{y}",
146
+ "#{x + width},#{cy}",
147
+ "#{cx},#{y + height}",
148
+ "#{x},#{cy}"
149
+ ].join(' ')
150
+
151
+ Svg::Polygon.new.tap do |polygon|
152
+ polygon.points = points
153
+ apply_theme_to_node(polygon)
154
+ end
155
+ end
156
+
157
+ def create_hexagon(x, y, width, height)
158
+ cy = y + height / 2
159
+ w4 = width / 4
160
+
161
+ points = [
162
+ "#{x + w4},#{y}",
163
+ "#{x + width - w4},#{y}",
164
+ "#{x + width},#{cy}",
165
+ "#{x + width - w4},#{y + height}",
166
+ "#{x + w4},#{y + height}",
167
+ "#{x},#{cy}"
168
+ ].join(' ')
169
+
170
+ Svg::Polygon.new.tap do |polygon|
171
+ polygon.points = points
172
+ apply_theme_to_node(polygon)
173
+ end
174
+ end
175
+
176
+ def create_node_label(node, label)
177
+ x = node[:x] || 0
178
+ y = node[:y] || 0
179
+ width = node[:width] || 100
180
+ height = node[:height] || 50
181
+
182
+ # Center text in node
183
+ text_x = x + width / 2
184
+ text_y = y + height / 2
185
+
186
+ Svg::Text.new.tap do |text|
187
+ text.x = text_x
188
+ text.y = text_y
189
+ text.content = label[:text]
190
+ apply_theme_to_text(text)
191
+ text.text_anchor = 'middle'
192
+ text.dominant_baseline = 'middle'
193
+ end
194
+ end
195
+
196
+ def render_edges(graph, svg)
197
+ graph[:edges].each do |edge|
198
+ render_edge(edge, graph, svg)
199
+ end
200
+ end
201
+
202
+ def render_edge(edge, graph, svg)
203
+ source = find_node(graph, edge[:sources]&.first)
204
+ target = find_node(graph, edge[:targets]&.first)
205
+
206
+ return unless source && target
207
+
208
+ # Calculate edge path
209
+ path_data = calculate_edge_path(source, target, edge)
210
+
211
+ # Create path element
212
+ path = Svg::Path.new.tap do |p|
213
+ p.d = path_data
214
+ p.fill = 'none'
215
+ apply_theme_to_edge(p)
216
+ p.marker_end = 'url(#arrowhead)' if arrow_type?(edge)
217
+ end
218
+
219
+ # Create group for edge and label
220
+ group = Svg::Group.new.tap do |g|
221
+ g.id = "edge-#{edge[:id]}"
222
+ end
223
+
224
+ group.children << path
225
+
226
+ # Render edge label if present
227
+ if edge[:labels] && !edge[:labels].empty?
228
+ label = edge[:labels].first
229
+ text = create_edge_label(source, target, label)
230
+ group.children << text if text
231
+ end
232
+
233
+ svg << group
234
+ end
235
+
236
+ def find_node(graph, node_id)
237
+ return nil unless graph[:children] && node_id
238
+
239
+ graph[:children].find { |n| n[:id] == node_id }
240
+ end
241
+
242
+ def calculate_edge_path(source, target, edge)
243
+ # Simple straight line path
244
+ sx = (source[:x] || 0) + (source[:width] || 100) / 2
245
+ sy = (source[:y] || 0) + (source[:height] || 50) / 2
246
+ tx = (target[:x] || 0) + (target[:width] || 100) / 2
247
+ ty = (target[:y] || 0) + (target[:height] || 50) / 2
248
+
249
+ # Use sections if available (from elkrb layout)
250
+ if edge[:sections] && !edge[:sections].empty?
251
+ section = edge[:sections].first
252
+ if section[:bendPoints] && !section[:bendPoints].empty?
253
+ return create_path_with_bends(sx, sy, tx, ty,
254
+ section[:bendPoints])
255
+ end
256
+ end
257
+
258
+ "M #{sx} #{sy} L #{tx} #{ty}"
259
+ end
260
+
261
+ def create_path_with_bends(sx, sy, tx, ty, bend_points)
262
+ path_parts = ["M #{sx} #{sy}"]
263
+
264
+ bend_points.each do |point|
265
+ path_parts << "L #{point[:x]} #{point[:y]}"
266
+ end
267
+
268
+ path_parts << "L #{tx} #{ty}"
269
+ path_parts.join(' ')
270
+ end
271
+
272
+ def arrow_type?(edge)
273
+ arrow_type = edge.dig(:metadata, :arrow_type)
274
+ %w[arrow dotted_arrow thick_arrow].include?(arrow_type)
275
+ end
276
+
277
+ def create_edge_label(source, target, label)
278
+ # Position label at midpoint of edge
279
+ sx = (source[:x] || 0) + (source[:width] || 100) / 2
280
+ sy = (source[:y] || 0) + (source[:height] || 50) / 2
281
+ tx = (target[:x] || 0) + (target[:width] || 100) / 2
282
+ ty = (target[:y] || 0) + (target[:height] || 50) / 2
283
+
284
+ mid_x = (sx + tx) / 2
285
+ mid_y = (sy + ty) / 2
286
+
287
+ Svg::Text.new.tap do |text|
288
+ text.x = mid_x
289
+ text.y = mid_y - 5 # Offset slightly above line
290
+ text.content = label[:text]
291
+ apply_theme_to_text(text)
292
+ # Use smaller font for edge labels
293
+ if theme_typography(:font_size_small)
294
+ text.font_size = theme_typography(:font_size_small).to_s
295
+ end
296
+ text.text_anchor = 'middle'
297
+ end
298
+ end
299
+ end
300
+ end
301
+ end
@@ -0,0 +1,331 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../svg/document"
5
+ require_relative "../svg/rect"
6
+ require_relative "../svg/text"
7
+ require_relative "../svg/line"
8
+ require_relative "../svg/path"
9
+ require_relative "../svg/group"
10
+ require_relative "../svg/polygon"
11
+
12
+ module Sirena
13
+ module Renderer
14
+ # Gantt chart renderer for converting Gantt diagrams to SVG.
15
+ #
16
+ # Converts a Gantt diagram model into SVG with timeline, tasks,
17
+ # sections, and dependency indicators.
18
+ #
19
+ # @example Render a Gantt chart
20
+ # renderer = GanttRenderer.new
21
+ # svg = renderer.render(gantt_graph)
22
+ class GanttRenderer < Base
23
+ # Gantt chart dimensions
24
+ MARGIN_LEFT = 200
25
+ MARGIN_TOP = 80
26
+ MARGIN_RIGHT = 50
27
+ MARGIN_BOTTOM = 50
28
+ ROW_HEIGHT = 40
29
+ SECTION_HEIGHT = 30
30
+ TASK_BAR_HEIGHT = 24
31
+ TIMELINE_WIDTH = 800
32
+ TIMELINE_HEIGHT = 40
33
+ TITLE_Y = 40
34
+
35
+ # Task status colors
36
+ TASK_COLORS = {
37
+ done: "#5CB85C", # Green
38
+ active: "#5BC0DE", # Blue
39
+ critical: "#D9534F", # Red
40
+ default: "#428BCA" # Default blue
41
+ }.freeze
42
+
43
+ # Renders a Gantt chart diagram to SVG.
44
+ #
45
+ # @param graph [Hash] the Gantt chart graph structure from transform
46
+ # @return [Svg::Document] the rendered SVG document
47
+ def render(graph)
48
+ @graph = graph
49
+ @current_y = MARGIN_TOP + TIMELINE_HEIGHT
50
+
51
+ svg = create_document_for_gantt(graph)
52
+
53
+ # Render title if present
54
+ render_title(graph, svg) if graph[:title]
55
+
56
+ # Render timeline axis
57
+ render_timeline(graph, svg)
58
+
59
+ # Render sections and tasks
60
+ render_sections(graph, svg)
61
+
62
+ svg
63
+ end
64
+
65
+ protected
66
+
67
+ def create_document_for_gantt(graph)
68
+ width = calculate_width_for_gantt(graph)
69
+ height = calculate_height_for_gantt(graph)
70
+
71
+ Svg::Document.new.tap do |doc|
72
+ doc.width = width
73
+ doc.height = height
74
+ doc.view_box = "0 0 #{width} #{height}"
75
+ end
76
+ end
77
+
78
+ def calculate_width_for_gantt(_graph)
79
+ MARGIN_LEFT + TIMELINE_WIDTH + MARGIN_RIGHT
80
+ end
81
+
82
+ def calculate_height_for_gantt(graph)
83
+ sections = graph[:sections] || []
84
+ total_rows = sections.sum { |s| s[:tasks].length + 1 } # +1 for section header
85
+
86
+ MARGIN_TOP + TIMELINE_HEIGHT + (total_rows * ROW_HEIGHT) + MARGIN_BOTTOM
87
+ end
88
+
89
+ def render_title(graph, svg)
90
+ title_text = Svg::Text.new.tap do |t|
91
+ t.x = MARGIN_LEFT + (TIMELINE_WIDTH / 2)
92
+ t.y = TITLE_Y
93
+ t.content = graph[:title]
94
+ t.fill = theme_color(:label_text) || "#000000"
95
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
96
+ t.font_size = (theme_typography(:font_size_large) || 18).to_s
97
+ t.text_anchor = "middle"
98
+ t.font_weight = "bold"
99
+ end
100
+ svg << title_text
101
+ end
102
+
103
+ def render_timeline(graph, svg)
104
+ timeline = graph[:timeline]
105
+ return unless timeline
106
+
107
+ # Timeline background
108
+ timeline_bg = Svg::Rect.new.tap do |r|
109
+ r.x = MARGIN_LEFT
110
+ r.y = MARGIN_TOP
111
+ r.width = TIMELINE_WIDTH
112
+ r.height = TIMELINE_HEIGHT
113
+ r.fill = theme_color(:node_fill) || "#f5f5f5"
114
+ r.stroke = theme_color(:node_stroke) || "#cccccc"
115
+ r.stroke_width = "1"
116
+ end
117
+ svg << timeline_bg
118
+
119
+ # Date labels
120
+ render_date_labels(timeline, svg)
121
+
122
+ # Grid lines (optional)
123
+ render_grid_lines(timeline, svg)
124
+ end
125
+
126
+ def render_date_labels(timeline, svg)
127
+ total_days = timeline[:total_days]
128
+ return if total_days <= 0
129
+
130
+ # Determine label interval based on timeline length
131
+ interval = calculate_label_interval(total_days)
132
+
133
+ (0..total_days).step(interval).each do |day|
134
+ date = timeline[:start_date] + day
135
+ x = MARGIN_LEFT + ((day.to_f / total_days) * TIMELINE_WIDTH)
136
+
137
+ # Date label
138
+ label = Svg::Text.new.tap do |t|
139
+ t.x = x
140
+ t.y = MARGIN_TOP + TIMELINE_HEIGHT - 10
141
+ t.content = format_date(date, @graph[:axis_format])
142
+ t.fill = theme_color(:label_text) || "#000000"
143
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
144
+ t.font_size = (theme_typography(:font_size_small) || 10).to_s
145
+ t.text_anchor = "middle"
146
+ end
147
+ svg << label
148
+ end
149
+ end
150
+
151
+ def render_grid_lines(timeline, svg)
152
+ total_days = timeline[:total_days]
153
+ return if total_days <= 0
154
+
155
+ interval = calculate_label_interval(total_days)
156
+ sections = @graph[:sections] || []
157
+ total_rows = sections.sum { |s| s[:tasks].length + 1 }
158
+ grid_height = total_rows * ROW_HEIGHT
159
+
160
+ (0..total_days).step(interval).each do |day|
161
+ x = MARGIN_LEFT + ((day.to_f / total_days) * TIMELINE_WIDTH)
162
+
163
+ line = Svg::Line.new.tap do |l|
164
+ l.x1 = x
165
+ l.y1 = MARGIN_TOP + TIMELINE_HEIGHT
166
+ l.x2 = x
167
+ l.y2 = MARGIN_TOP + TIMELINE_HEIGHT + grid_height
168
+ l.stroke = theme_color(:node_stroke) || "#e0e0e0"
169
+ l.stroke_width = "1"
170
+ l.stroke_dasharray = "2,2"
171
+ end
172
+ svg << line
173
+ end
174
+ end
175
+
176
+ def render_sections(graph, svg)
177
+ sections = graph[:sections] || []
178
+
179
+ sections.each do |section|
180
+ render_section_header(section, svg)
181
+ @current_y += SECTION_HEIGHT
182
+
183
+ section[:tasks].each do |task|
184
+ render_task(task, svg)
185
+ @current_y += ROW_HEIGHT
186
+ end
187
+ end
188
+ end
189
+
190
+ def render_section_header(section, svg)
191
+ # Section background
192
+ section_bg = Svg::Rect.new.tap do |r|
193
+ r.x = 0
194
+ r.y = @current_y
195
+ r.width = MARGIN_LEFT + TIMELINE_WIDTH + MARGIN_RIGHT
196
+ r.height = SECTION_HEIGHT
197
+ r.fill = theme_color(:section_bg) || "#f0f0f0"
198
+ r.stroke = "none"
199
+ end
200
+ svg << section_bg
201
+
202
+ # Section name
203
+ section_text = Svg::Text.new.tap do |t|
204
+ t.x = 10
205
+ t.y = @current_y + (SECTION_HEIGHT / 2)
206
+ t.content = section[:name]
207
+ t.fill = theme_color(:label_text) || "#000000"
208
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
209
+ t.font_size = (theme_typography(:font_size_base) || 14).to_s
210
+ t.dominant_baseline = "middle"
211
+ t.font_weight = "bold"
212
+ end
213
+ svg << section_text
214
+ end
215
+
216
+ def render_task(task, svg)
217
+ # Task label
218
+ task_label = Svg::Text.new.tap do |t|
219
+ t.x = 10
220
+ t.y = @current_y + (ROW_HEIGHT / 2)
221
+ t.content = task[:description]
222
+ t.fill = theme_color(:label_text) || "#000000"
223
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
224
+ t.font_size = (theme_typography(:font_size_small) || 12).to_s
225
+ t.dominant_baseline = "middle"
226
+ end
227
+ svg << task_label
228
+
229
+ # Task bar (if dates are available)
230
+ if task[:start_x] && task[:width]
231
+ render_task_bar(task, svg)
232
+ end
233
+ end
234
+
235
+ def render_task_bar(task, svg)
236
+ x = MARGIN_LEFT + task[:start_x]
237
+ y = @current_y + ((ROW_HEIGHT - TASK_BAR_HEIGHT) / 2)
238
+ width = task[:width]
239
+
240
+ # Handle milestone (zero duration)
241
+ if task[:milestone] || width < 10
242
+ render_milestone(x, y, task, svg)
243
+ else
244
+ render_normal_task(x, y, width, task, svg)
245
+ end
246
+ end
247
+
248
+ def render_normal_task(x, y, width, task, svg)
249
+ color = get_task_color(task)
250
+
251
+ task_bar = Svg::Rect.new.tap do |r|
252
+ r.x = x
253
+ r.y = y
254
+ r.width = width
255
+ r.height = TASK_BAR_HEIGHT
256
+ r.fill = color
257
+ r.stroke = theme_color(:node_stroke) || "#ffffff"
258
+ r.stroke_width = "1"
259
+ r.rx = "3"
260
+ r.ry = "3"
261
+ end
262
+ svg << task_bar
263
+
264
+ # Add task ID if present
265
+ if task[:id] && width > 40
266
+ id_text = Svg::Text.new.tap do |t|
267
+ t.x = x + (width / 2)
268
+ t.y = y + (TASK_BAR_HEIGHT / 2)
269
+ t.content = task[:id]
270
+ t.fill = "#ffffff"
271
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
272
+ t.font_size = "10"
273
+ t.text_anchor = "middle"
274
+ t.dominant_baseline = "middle"
275
+ end
276
+ svg << id_text
277
+ end
278
+ end
279
+
280
+ def render_milestone(x, y, task, svg)
281
+ # Render as diamond
282
+ center_y = y + (TASK_BAR_HEIGHT / 2)
283
+ size = 12
284
+
285
+ points = [
286
+ [x, center_y], # Left
287
+ [x + size, center_y - size], # Top
288
+ [x + size * 2, center_y], # Right
289
+ [x + size, center_y + size] # Bottom
290
+ ]
291
+
292
+ diamond = Svg::Polygon.new.tap do |p|
293
+ p.points = points.map { |pt| "#{pt[0]},#{pt[1]}" }.join(" ")
294
+ p.fill = get_task_color(task)
295
+ p.stroke = theme_color(:node_stroke) || "#ffffff"
296
+ p.stroke_width = "2"
297
+ end
298
+ svg << diamond
299
+ end
300
+
301
+ def get_task_color(task)
302
+ return TASK_COLORS[:critical] if task[:critical]
303
+ return TASK_COLORS[:done] if task[:done]
304
+ return TASK_COLORS[:active] if task[:active]
305
+
306
+ TASK_COLORS[:default]
307
+ end
308
+
309
+ def calculate_label_interval(total_days)
310
+ return 1 if total_days <= 7
311
+ return 7 if total_days <= 60
312
+ return 14 if total_days <= 120
313
+
314
+ 30
315
+ end
316
+
317
+ def format_date(date, format)
318
+ return date.strftime("%m-%d") unless format
319
+
320
+ # Simple format conversion (extend as needed)
321
+ format_str = format.gsub("%d", "%d")
322
+ .gsub("%m", "%m")
323
+ .gsub("%Y", "%Y")
324
+
325
+ date.strftime(format_str)
326
+ rescue StandardError
327
+ date.strftime("%m-%d")
328
+ end
329
+ end
330
+ end
331
+ end