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,328 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Sirena
6
+ module Renderer
7
+ # State diagram 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 state types,
11
+ # transition routing, and label positioning.
12
+ #
13
+ # @example Render a state diagram
14
+ # renderer = StateDiagramRenderer.new
15
+ # svg = renderer.render(laid_out_graph)
16
+ class StateDiagramRenderer < Base
17
+ # Renders a laid-out graph to SVG.
18
+ #
19
+ # @param graph [Hash] laid-out graph with state positions
20
+ # @return [Svg::Document] the rendered SVG document
21
+ def render(graph)
22
+ svg = create_document(graph)
23
+
24
+ # Render transitions first (so they appear under states)
25
+ render_transitions(graph, svg) if graph[:edges]
26
+
27
+ # Render states
28
+ render_states(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 |state|
39
+ (state[:x] || 0) + (state[:width] || 100)
40
+ end.max || 800
41
+
42
+ max_x + 60 # Add padding
43
+ end
44
+
45
+ def calculate_height(graph)
46
+ return 600 unless graph[:children]
47
+
48
+ max_y = graph[:children].map do |state|
49
+ (state[:y] || 0) + (state[:height] || 50)
50
+ end.max || 600
51
+
52
+ max_y + 60 # Add padding
53
+ end
54
+
55
+ def render_states(graph, svg)
56
+ graph[:children].each do |state|
57
+ render_state(state, svg)
58
+ end
59
+ end
60
+
61
+ def render_state(state, svg)
62
+ state_type = state.dig(:metadata, :state_type) || 'normal'
63
+
64
+ # Create group for state and its label
65
+ group = Svg::Group.new.tap do |g|
66
+ g.id = "state-#{state[:id]}"
67
+ end
68
+
69
+ # Render state shape based on type
70
+ shape_element = create_state_shape(state, state_type)
71
+ group.children << shape_element if shape_element
72
+
73
+ # Render state label
74
+ if state[:labels] && !state[:labels].empty?
75
+ state[:labels].each_with_index do |label, index|
76
+ text_element = create_state_label(state, label, index)
77
+ group.children << text_element if text_element
78
+ end
79
+ end
80
+
81
+ svg << group
82
+ end
83
+
84
+ def create_state_shape(state, state_type)
85
+ x = state[:x] || 0
86
+ y = state[:y] || 0
87
+ width = state[:width] || 100
88
+ height = state[:height] || 50
89
+
90
+ case state_type
91
+ when 'start'
92
+ create_start_state(x, y, width, height)
93
+ when 'end'
94
+ create_end_state(x, y, width, height)
95
+ when 'choice'
96
+ create_choice_state(x, y, width, height)
97
+ when 'fork', 'join'
98
+ create_fork_join_state(x, y, width, height)
99
+ else
100
+ create_normal_state(x, y, width, height)
101
+ end
102
+ end
103
+
104
+ def create_normal_state(x, y, width, height)
105
+ Svg::Rect.new.tap do |rect|
106
+ rect.x = x
107
+ rect.y = y
108
+ rect.width = width
109
+ rect.height = height
110
+ rect.rx = 10
111
+ rect.ry = 10
112
+ rect.fill = '#ffffff'
113
+ rect.stroke = '#000000'
114
+ rect.stroke_width = '2'
115
+ end
116
+ end
117
+
118
+ def create_start_state(x, y, width, height)
119
+ # Start state is a filled circle
120
+ cx = x + width / 2
121
+ cy = y + height / 2
122
+ r = [width, height].min / 2
123
+
124
+ Svg::Circle.new.tap do |circle|
125
+ circle.cx = cx
126
+ circle.cy = cy
127
+ circle.r = r
128
+ circle.fill = '#000000'
129
+ circle.stroke = 'none'
130
+ end
131
+ end
132
+
133
+ def create_end_state(x, y, width, height)
134
+ # End state is a double circle (outer hollow, inner filled)
135
+ cx = x + width / 2
136
+ cy = y + height / 2
137
+ r = [width, height].min / 2
138
+
139
+ group = Svg::Group.new
140
+
141
+ # Outer circle
142
+ outer = Svg::Circle.new.tap do |circle|
143
+ circle.cx = cx
144
+ circle.cy = cy
145
+ circle.r = r
146
+ circle.fill = 'none'
147
+ circle.stroke = '#000000'
148
+ circle.stroke_width = '2'
149
+ end
150
+ group.children << outer
151
+
152
+ # Inner filled circle
153
+ inner = Svg::Circle.new.tap do |circle|
154
+ circle.cx = cx
155
+ circle.cy = cy
156
+ circle.r = r - 5
157
+ circle.fill = '#000000'
158
+ circle.stroke = 'none'
159
+ end
160
+ group.children << inner
161
+
162
+ group
163
+ end
164
+
165
+ def create_choice_state(x, y, width, height)
166
+ # Choice state is a diamond
167
+ cx = x + width / 2
168
+ cy = y + height / 2
169
+
170
+ points = [
171
+ "#{cx},#{y}",
172
+ "#{x + width},#{cy}",
173
+ "#{cx},#{y + height}",
174
+ "#{x},#{cy}"
175
+ ].join(' ')
176
+
177
+ Svg::Polygon.new.tap do |polygon|
178
+ polygon.points = points
179
+ polygon.fill = '#ffffff'
180
+ polygon.stroke = '#000000'
181
+ polygon.stroke_width = '2'
182
+ end
183
+ end
184
+
185
+ def create_fork_join_state(x, y, width, height)
186
+ # Fork/Join is a thick horizontal bar
187
+ Svg::Rect.new.tap do |rect|
188
+ rect.x = x
189
+ rect.y = y + height / 2 - 5
190
+ rect.width = width
191
+ rect.height = 10
192
+ rect.fill = '#000000'
193
+ rect.stroke = 'none'
194
+ end
195
+ end
196
+
197
+ def create_state_label(state, label, index)
198
+ x = state[:x] || 0
199
+ y = state[:y] || 0
200
+ width = state[:width] || 100
201
+ height = state[:height] || 50
202
+
203
+ # Center text in state
204
+ text_x = x + width / 2
205
+ # Adjust y position based on label index (for multiple labels)
206
+ text_y = if index.zero?
207
+ y + height / 2
208
+ else
209
+ y + height / 2 + (index * 20)
210
+ end
211
+
212
+ font_size = index.zero? ? '14' : '12'
213
+
214
+ Svg::Text.new.tap do |text|
215
+ text.x = text_x
216
+ text.y = text_y
217
+ text.content = label[:text]
218
+ text.fill = '#000000'
219
+ text.font_family = 'Arial, sans-serif'
220
+ text.font_size = font_size
221
+ text.text_anchor = 'middle'
222
+ text.dominant_baseline = 'middle'
223
+ end
224
+ end
225
+
226
+ def render_transitions(graph, svg)
227
+ graph[:edges].each do |transition|
228
+ render_transition(transition, graph, svg)
229
+ end
230
+ end
231
+
232
+ def render_transition(transition, graph, svg)
233
+ source = find_state(graph, transition[:sources]&.first)
234
+ target = find_state(graph, transition[:targets]&.first)
235
+
236
+ return unless source && target
237
+
238
+ # Calculate transition path
239
+ path_data = calculate_transition_path(source, target, transition)
240
+
241
+ # Create path element
242
+ path = Svg::Path.new.tap do |p|
243
+ p.d = path_data
244
+ p.fill = 'none'
245
+ p.stroke = '#000000'
246
+ p.stroke_width = '2'
247
+ p.marker_end = 'url(#arrowhead)'
248
+ end
249
+
250
+ # Create group for transition and label
251
+ group = Svg::Group.new.tap do |g|
252
+ g.id = "transition-#{transition[:id]}"
253
+ end
254
+
255
+ group.children << path
256
+
257
+ # Render transition label if present
258
+ if transition[:labels] && !transition[:labels].empty?
259
+ label = transition[:labels].first
260
+ text = create_transition_label(source, target, label)
261
+ group.children << text if text
262
+ end
263
+
264
+ svg << group
265
+ end
266
+
267
+ def find_state(graph, state_id)
268
+ return nil unless graph[:children] && state_id
269
+
270
+ graph[:children].find { |s| s[:id] == state_id }
271
+ end
272
+
273
+ def calculate_transition_path(source, target, transition)
274
+ # Calculate center points
275
+ sx = (source[:x] || 0) + (source[:width] || 100) / 2
276
+ sy = (source[:y] || 0) + (source[:height] || 50) / 2
277
+ tx = (target[:x] || 0) + (target[:width] || 100) / 2
278
+ ty = (target[:y] || 0) + (target[:height] || 50) / 2
279
+
280
+ # Use sections if available (from elkrb layout)
281
+ if transition[:sections] && !transition[:sections].empty?
282
+ section = transition[:sections].first
283
+ if section[:bendPoints] && !section[:bendPoints].empty?
284
+ return create_path_with_bends(
285
+ sx, sy, tx, ty,
286
+ section[:bendPoints]
287
+ )
288
+ end
289
+ end
290
+
291
+ # Simple straight line path
292
+ "M #{sx} #{sy} L #{tx} #{ty}"
293
+ end
294
+
295
+ def create_path_with_bends(sx, sy, tx, ty, bend_points)
296
+ path_parts = ["M #{sx} #{sy}"]
297
+
298
+ bend_points.each do |point|
299
+ path_parts << "L #{point[:x]} #{point[:y]}"
300
+ end
301
+
302
+ path_parts << "L #{tx} #{ty}"
303
+ path_parts.join(' ')
304
+ end
305
+
306
+ def create_transition_label(source, target, label)
307
+ # Position label at midpoint of transition
308
+ sx = (source[:x] || 0) + (source[:width] || 100) / 2
309
+ sy = (source[:y] || 0) + (source[:height] || 50) / 2
310
+ tx = (target[:x] || 0) + (target[:width] || 100) / 2
311
+ ty = (target[:y] || 0) + (target[:height] || 50) / 2
312
+
313
+ mid_x = (sx + tx) / 2
314
+ mid_y = (sy + ty) / 2
315
+
316
+ Svg::Text.new.tap do |text|
317
+ text.x = mid_x
318
+ text.y = mid_y - 8 # Offset slightly above line
319
+ text.content = label[:text]
320
+ text.fill = '#000000'
321
+ text.font_family = 'Arial, sans-serif'
322
+ text.font_size = '12'
323
+ text.text_anchor = 'middle'
324
+ end
325
+ end
326
+ end
327
+ end
328
+ end
@@ -0,0 +1,304 @@
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/circle"
9
+ require_relative "../svg/group"
10
+
11
+ module Sirena
12
+ module Renderer
13
+ # Timeline renderer for converting timeline diagrams to SVG.
14
+ #
15
+ # Converts a Timeline diagram model into SVG with horizontal axis,
16
+ # event markers, descriptions, and optional section grouping.
17
+ #
18
+ # @example Render a timeline
19
+ # renderer = TimelineRenderer.new
20
+ # svg = renderer.render(timeline_graph)
21
+ class TimelineRenderer < Base
22
+ # Timeline dimensions
23
+ MARGIN_LEFT = 80
24
+ MARGIN_TOP = 100
25
+ MARGIN_RIGHT = 80
26
+ MARGIN_BOTTOM = 60
27
+ TIMELINE_WIDTH = 800
28
+ TIMELINE_HEIGHT = 6
29
+ EVENT_MARKER_RADIUS = 8
30
+ EVENT_LABEL_OFFSET_Y = 35
31
+ SECTION_HEIGHT = 40
32
+ SECTION_SPACING = 20
33
+ TITLE_Y = 40
34
+
35
+ # Section colors (cycle through for visual distinction)
36
+ SECTION_COLORS = [
37
+ "#4472C4", # Blue
38
+ "#ED7D31", # Orange
39
+ "#A5A5A5", # Gray
40
+ "#FFC000", # Yellow
41
+ "#5B9BD5", # Light Blue
42
+ "#70AD47" # Green
43
+ ].freeze
44
+
45
+ # Renders a timeline diagram to SVG.
46
+ #
47
+ # @param graph [Hash] the timeline graph structure from transform
48
+ # @return [Svg::Document] the rendered SVG document
49
+ def render(graph)
50
+ @graph = graph
51
+ @current_y = MARGIN_TOP
52
+
53
+ svg = create_document_for_timeline(graph)
54
+
55
+ # Render title if present
56
+ render_title(graph, svg) if graph[:title]
57
+
58
+ # Render sections or single timeline
59
+ if graph[:metadata][:has_sections]
60
+ render_sections_timeline(graph, svg)
61
+ else
62
+ render_simple_timeline(graph, svg)
63
+ end
64
+
65
+ svg
66
+ end
67
+
68
+ protected
69
+
70
+ def create_document_for_timeline(graph)
71
+ width = calculate_width_for_timeline(graph)
72
+ height = calculate_height_for_timeline(graph)
73
+
74
+ Svg::Document.new.tap do |doc|
75
+ doc.width = width
76
+ doc.height = height
77
+ doc.view_box = "0 0 #{width} #{height}"
78
+ end
79
+ end
80
+
81
+ def calculate_width_for_timeline(_graph)
82
+ MARGIN_LEFT + TIMELINE_WIDTH + MARGIN_RIGHT
83
+ end
84
+
85
+ def calculate_height_for_timeline(graph)
86
+ base_height = MARGIN_TOP + MARGIN_BOTTOM + 100
87
+
88
+ if graph[:metadata][:has_sections]
89
+ section_count = graph[:sections].length
90
+ base_height += section_count * (SECTION_HEIGHT + SECTION_SPACING)
91
+ end
92
+
93
+ base_height
94
+ end
95
+
96
+ def render_title(graph, svg)
97
+ title_text = Svg::Text.new.tap do |t|
98
+ t.x = MARGIN_LEFT + (TIMELINE_WIDTH / 2)
99
+ t.y = TITLE_Y
100
+ t.content = graph[:title]
101
+ t.fill = theme_color(:label_text) || "#000000"
102
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
103
+ t.font_size = (theme_typography(:font_size_large) || 18).to_s
104
+ t.text_anchor = "middle"
105
+ t.font_weight = "bold"
106
+ end
107
+ svg << title_text
108
+ end
109
+
110
+ def render_simple_timeline(graph, svg)
111
+ # Render timeline axis
112
+ render_timeline_axis(svg, @current_y)
113
+
114
+ # Render events
115
+ events = graph[:events] || []
116
+ render_events(events, svg, @current_y, 0)
117
+
118
+ # Render time labels
119
+ render_time_labels(graph[:timeline], svg, @current_y)
120
+ end
121
+
122
+ def render_sections_timeline(graph, svg)
123
+ sections = graph[:sections] || []
124
+
125
+ sections.each_with_index do |section, index|
126
+ # Section header
127
+ render_section_header(section, svg, @current_y, index)
128
+ @current_y += SECTION_HEIGHT
129
+
130
+ # Render timeline axis for this section
131
+ render_timeline_axis(svg, @current_y)
132
+
133
+ # Render events or tasks
134
+ if section[:has_events]
135
+ render_events(section[:events], svg, @current_y, index)
136
+ elsif section[:has_tasks]
137
+ render_tasks(section[:tasks], svg, @current_y, index)
138
+ end
139
+
140
+ # Render time labels (only for first section)
141
+ if index.zero?
142
+ render_time_labels(graph[:timeline], svg, @current_y)
143
+ end
144
+
145
+ @current_y += SECTION_SPACING
146
+ end
147
+ end
148
+
149
+ def render_section_header(section, svg, y, index)
150
+ color = SECTION_COLORS[index % SECTION_COLORS.length]
151
+
152
+ # Section label
153
+ section_text = Svg::Text.new.tap do |t|
154
+ t.x = MARGIN_LEFT
155
+ t.y = y
156
+ t.content = section[:name]
157
+ t.fill = color
158
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
159
+ t.font_size = (theme_typography(:font_size_base) || 14).to_s
160
+ t.font_weight = "bold"
161
+ end
162
+ svg << section_text
163
+ end
164
+
165
+ def render_timeline_axis(svg, y)
166
+ axis_line = Svg::Rect.new.tap do |r|
167
+ r.x = MARGIN_LEFT
168
+ r.y = y
169
+ r.width = TIMELINE_WIDTH
170
+ r.height = TIMELINE_HEIGHT
171
+ r.fill = theme_color(:node_fill) || "#cccccc"
172
+ r.stroke = "none"
173
+ r.rx = TIMELINE_HEIGHT / 2
174
+ r.ry = TIMELINE_HEIGHT / 2
175
+ end
176
+ svg << axis_line
177
+ end
178
+
179
+ def render_events(events, svg, y, section_index)
180
+ color = SECTION_COLORS[section_index % SECTION_COLORS.length]
181
+
182
+ events.each do |event|
183
+ x = calculate_event_x(event[:x_position])
184
+
185
+ # Event marker (circle)
186
+ marker = Svg::Circle.new.tap do |c|
187
+ c.cx = x
188
+ c.cy = y + (TIMELINE_HEIGHT / 2)
189
+ c.r = EVENT_MARKER_RADIUS
190
+ c.fill = color
191
+ c.stroke = theme_color(:node_stroke) || "#ffffff"
192
+ c.stroke_width = "2"
193
+ end
194
+ svg << marker
195
+
196
+ # Event descriptions
197
+ render_event_descriptions(event, x, y, svg)
198
+ end
199
+ end
200
+
201
+ def render_event_descriptions(event, x, y, svg)
202
+ descriptions = event[:descriptions] || []
203
+
204
+ descriptions.each_with_index do |desc, idx|
205
+ label_y = y + EVENT_LABEL_OFFSET_Y + (idx * 16)
206
+
207
+ desc_text = Svg::Text.new.tap do |t|
208
+ t.x = x
209
+ t.y = label_y
210
+ t.content = desc.to_s.strip
211
+ t.fill = theme_color(:label_text) || "#000000"
212
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
213
+ t.font_size = (theme_typography(:font_size_small) || 11).to_s
214
+ t.text_anchor = "middle"
215
+ end
216
+ svg << desc_text
217
+ end
218
+
219
+ # Event time label (above marker)
220
+ time_label = Svg::Text.new.tap do |t|
221
+ t.x = x
222
+ t.y = y - 15
223
+ t.content = event[:time].to_s
224
+ t.fill = theme_color(:label_text) || "#666666"
225
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
226
+ t.font_size = (theme_typography(:font_size_small) || 10).to_s
227
+ t.text_anchor = "middle"
228
+ t.font_weight = "bold"
229
+ end
230
+ svg << time_label
231
+ end
232
+
233
+ def render_tasks(tasks, svg, y, section_index)
234
+ # For sections with tasks (no timestamps), distribute evenly
235
+ return if tasks.empty?
236
+
237
+ color = SECTION_COLORS[section_index % SECTION_COLORS.length]
238
+ spacing = TIMELINE_WIDTH / (tasks.length + 1).to_f
239
+
240
+ tasks.each_with_index do |task, index|
241
+ x = MARGIN_LEFT + (spacing * (index + 1))
242
+
243
+ # Task marker
244
+ marker = Svg::Circle.new.tap do |c|
245
+ c.cx = x
246
+ c.cy = y + (TIMELINE_HEIGHT / 2)
247
+ c.r = EVENT_MARKER_RADIUS
248
+ c.fill = color
249
+ c.stroke = theme_color(:node_stroke) || "#ffffff"
250
+ c.stroke_width = "2"
251
+ end
252
+ svg << marker
253
+
254
+ # Task label
255
+ task_text = Svg::Text.new.tap do |t|
256
+ t.x = x
257
+ t.y = y + EVENT_LABEL_OFFSET_Y
258
+ t.content = task.to_s
259
+ t.fill = theme_color(:label_text) || "#000000"
260
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
261
+ t.font_size = (theme_typography(:font_size_small) || 11).to_s
262
+ t.text_anchor = "middle"
263
+ end
264
+ svg << task_text
265
+ end
266
+ end
267
+
268
+ def render_time_labels(timeline, svg, y)
269
+ return unless timeline
270
+
271
+ min_time = timeline[:min]
272
+ max_time = timeline[:max]
273
+
274
+ # Render min and max labels
275
+ render_time_label(min_time.to_s, MARGIN_LEFT, y + TIMELINE_HEIGHT + 20, svg)
276
+ render_time_label(max_time.to_s, MARGIN_LEFT + TIMELINE_WIDTH,
277
+ y + TIMELINE_HEIGHT + 20, svg)
278
+
279
+ # Optional: render mid-point
280
+ mid_time = ((min_time + max_time) / 2.0).round
281
+ render_time_label(mid_time.to_s, MARGIN_LEFT + (TIMELINE_WIDTH / 2),
282
+ y + TIMELINE_HEIGHT + 20, svg)
283
+ end
284
+
285
+ def render_time_label(label, x, y, svg)
286
+ time_text = Svg::Text.new.tap do |t|
287
+ t.x = x
288
+ t.y = y
289
+ t.content = label
290
+ t.fill = theme_color(:label_text) || "#666666"
291
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
292
+ t.font_size = (theme_typography(:font_size_small) || 10).to_s
293
+ t.text_anchor = "middle"
294
+ end
295
+ svg << time_text
296
+ end
297
+
298
+ def calculate_event_x(x_position)
299
+ # x_position is a percentage (0-100) from transform
300
+ MARGIN_LEFT + ((x_position / 100.0) * TIMELINE_WIDTH)
301
+ end
302
+ end
303
+ end
304
+ end