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,424 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Sirena
6
+ module Renderer
7
+ # Sequence 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 participants, lifelines,
11
+ # messages with various arrow types, and notes.
12
+ #
13
+ # @example Render a sequence diagram
14
+ # renderer = SequenceRenderer.new
15
+ # svg = renderer.render(laid_out_graph)
16
+ class SequenceRenderer < Base
17
+ # Participant box dimensions
18
+ PARTICIPANT_WIDTH = 120
19
+ PARTICIPANT_HEIGHT = 40
20
+ PARTICIPANT_MARGIN = 20
21
+
22
+ # Lifeline styling
23
+ LIFELINE_DASH = '5,5'
24
+
25
+ # Message arrow dimensions
26
+ ARROW_SIZE = 8
27
+ MESSAGE_Y_OFFSET = 60
28
+
29
+ # Renders a laid-out graph to SVG.
30
+ #
31
+ # @param graph [Hash] laid-out graph with node positions
32
+ # @return [Svg::Document] the rendered SVG document
33
+ def render(graph)
34
+ svg = create_document(graph)
35
+
36
+ metadata = graph[:metadata] || {}
37
+ metadata[:participants] || []
38
+ message_count = metadata[:message_count] || 0
39
+
40
+ # Calculate positions
41
+ participant_positions = calculate_participant_positions(
42
+ graph[:children]
43
+ )
44
+
45
+ # Render lifelines first (under everything)
46
+ render_lifelines(participant_positions, message_count, svg)
47
+
48
+ # Render messages
49
+ render_messages(graph, participant_positions, svg) if graph[:edges]
50
+
51
+ # Render participants (on top)
52
+ render_participants(graph[:children], participant_positions, svg)
53
+
54
+ # Render notes if present
55
+ render_notes(metadata[:notes], participant_positions, svg) if
56
+ metadata[:notes]
57
+
58
+ svg
59
+ end
60
+
61
+ protected
62
+
63
+ def calculate_width(graph)
64
+ return 800 unless graph[:children]
65
+
66
+ # Calculate width based on participant count and spacing
67
+ participant_count = graph[:children].length
68
+ total_width = participant_count * (PARTICIPANT_WIDTH +
69
+ PARTICIPANT_MARGIN)
70
+
71
+ total_width + 80 # Add padding
72
+ end
73
+
74
+ def calculate_height(graph)
75
+ metadata = graph[:metadata] || {}
76
+ message_count = metadata[:message_count] || 0
77
+
78
+ # Base height: participants + messages + padding
79
+ PARTICIPANT_HEIGHT * 2 + # Top and bottom participants
80
+ (message_count * MESSAGE_Y_OFFSET) +
81
+ 100 # Padding
82
+ end
83
+
84
+ def calculate_participant_positions(participants)
85
+ positions = {}
86
+ participants.each_with_index do |participant, index|
87
+ x = PARTICIPANT_MARGIN + (index * (PARTICIPANT_WIDTH +
88
+ PARTICIPANT_MARGIN))
89
+ y = PARTICIPANT_MARGIN
90
+
91
+ positions[participant[:id]] = {
92
+ x: x,
93
+ y: y,
94
+ center_x: x + PARTICIPANT_WIDTH / 2
95
+ }
96
+ end
97
+ positions
98
+ end
99
+
100
+ def render_participants(participants, positions, svg)
101
+ participants.each do |participant|
102
+ render_participant(participant, positions, svg)
103
+ end
104
+ end
105
+
106
+ def render_participant(participant, positions, svg)
107
+ pos = positions[participant[:id]]
108
+ return unless pos
109
+
110
+ metadata = participant[:metadata] || {}
111
+ actor_type = metadata[:actor_type] || 'participant'
112
+
113
+ # Create group for participant
114
+ group = Svg::Group.new.tap do |g|
115
+ g.id = "participant-#{participant[:id]}"
116
+ end
117
+
118
+ # Render participant shape
119
+ if actor_type == 'actor'
120
+ render_actor(pos[:x], pos[:y], participant, group)
121
+ else
122
+ render_participant_box(pos[:x], pos[:y], participant, group)
123
+ end
124
+
125
+ svg << group
126
+ end
127
+
128
+ def render_participant_box(x, y, participant, group)
129
+ # Participant box
130
+ rect = Svg::Rect.new.tap do |r|
131
+ r.x = x
132
+ r.y = y
133
+ r.width = PARTICIPANT_WIDTH
134
+ r.height = PARTICIPANT_HEIGHT
135
+ r.fill = '#ffffff'
136
+ r.stroke = '#000000'
137
+ r.stroke_width = '2'
138
+ r.rx = 5
139
+ r.ry = 5
140
+ end
141
+ group.children << rect
142
+
143
+ # Participant label
144
+ label = participant[:labels]&.first
145
+ return unless label
146
+
147
+ text = Svg::Text.new.tap do |t|
148
+ t.x = x + PARTICIPANT_WIDTH / 2
149
+ t.y = y + PARTICIPANT_HEIGHT / 2
150
+ t.content = label[:text]
151
+ t.fill = '#000000'
152
+ t.font_family = 'Arial, sans-serif'
153
+ t.font_size = '14'
154
+ t.text_anchor = 'middle'
155
+ t.dominant_baseline = 'middle'
156
+ end
157
+ group.children << text
158
+ end
159
+
160
+ def render_actor(x, y, _participant, group)
161
+ # Simple actor representation (stick figure)
162
+ center_x = x + PARTICIPANT_WIDTH / 2
163
+ head_y = y + 10
164
+
165
+ # Head
166
+ circle = Svg::Circle.new.tap do |c|
167
+ c.cx = center_x
168
+ c.cy = head_y
169
+ c.r = 8
170
+ c.fill = 'none'
171
+ c.stroke = '#000000'
172
+ c.stroke_width = '2'
173
+ end
174
+ group.children << circle
175
+
176
+ # Body and limbs
177
+ body_top = head_y + 8
178
+ body_bottom = body_top + 15
179
+
180
+ # Body line
181
+ body = Svg::Line.new.tap do |l|
182
+ l.x1 = center_x
183
+ l.y1 = body_top
184
+ l.x2 = center_x
185
+ l.y2 = body_bottom
186
+ l.stroke = '#000000'
187
+ l.stroke_width = '2'
188
+ end
189
+ group.children << body
190
+
191
+ # Arms
192
+ arms = Svg::Line.new.tap do |l|
193
+ l.x1 = center_x - 10
194
+ l.y1 = body_top + 7
195
+ l.x2 = center_x + 10
196
+ l.y2 = body_top + 7
197
+ l.stroke = '#000000'
198
+ l.stroke_width = '2'
199
+ end
200
+ group.children << arms
201
+
202
+ # Legs
203
+ left_leg = Svg::Line.new.tap do |l|
204
+ l.x1 = center_x
205
+ l.y1 = body_bottom
206
+ l.x2 = center_x - 8
207
+ l.y2 = body_bottom + 10
208
+ l.stroke = '#000000'
209
+ l.stroke_width = '2'
210
+ end
211
+ group.children << left_leg
212
+
213
+ right_leg = Svg::Line.new.tap do |l|
214
+ l.x1 = center_x
215
+ l.y1 = body_bottom
216
+ l.x2 = center_x + 8
217
+ l.y2 = body_bottom + 10
218
+ l.stroke = '#000000'
219
+ l.stroke_width = '2'
220
+ end
221
+ group.children << right_leg
222
+ end
223
+
224
+ def render_lifelines(positions, message_count, svg)
225
+ lifeline_length = (message_count * MESSAGE_Y_OFFSET) + 100
226
+
227
+ positions.each_value do |pos|
228
+ line = Svg::Line.new.tap do |l|
229
+ l.x1 = pos[:center_x]
230
+ l.y1 = PARTICIPANT_MARGIN + PARTICIPANT_HEIGHT
231
+ l.x2 = pos[:center_x]
232
+ l.y2 = PARTICIPANT_MARGIN + PARTICIPANT_HEIGHT + lifeline_length
233
+ l.stroke = '#000000'
234
+ l.stroke_width = '1'
235
+ l.stroke_dasharray = LIFELINE_DASH
236
+ end
237
+ svg << line
238
+ end
239
+ end
240
+
241
+ def render_messages(graph, positions, svg)
242
+ graph[:edges].each_with_index do |edge, index|
243
+ render_message(edge, positions, index, svg)
244
+ end
245
+ end
246
+
247
+ def render_message(edge, positions, index, svg)
248
+ metadata = edge[:metadata] || {}
249
+ arrow_type = metadata[:arrow_type] || 'solid'
250
+ message_text = metadata[:message_text]
251
+
252
+ source_id = edge[:sources]&.first
253
+ target_id = edge[:targets]&.first
254
+
255
+ return unless source_id && target_id
256
+
257
+ source_pos = positions[source_id]
258
+ target_pos = positions[target_id]
259
+
260
+ return unless source_pos && target_pos
261
+
262
+ # Calculate message Y position
263
+ message_y = PARTICIPANT_MARGIN + PARTICIPANT_HEIGHT +
264
+ ((index + 1) * MESSAGE_Y_OFFSET)
265
+
266
+ # Create group for message
267
+ group = Svg::Group.new.tap do |g|
268
+ g.id = "message-#{index}"
269
+ end
270
+
271
+ # Render arrow
272
+ render_arrow(
273
+ source_pos[:center_x],
274
+ message_y,
275
+ target_pos[:center_x],
276
+ message_y,
277
+ arrow_type,
278
+ group
279
+ )
280
+
281
+ # Render message label if present
282
+ if message_text && !message_text.empty?
283
+ render_message_label(
284
+ source_pos[:center_x],
285
+ target_pos[:center_x],
286
+ message_y,
287
+ message_text,
288
+ group
289
+ )
290
+ end
291
+
292
+ svg << group
293
+ end
294
+
295
+ def render_arrow(x1, y1, x2, y2, arrow_type, group)
296
+ # Determine line style
297
+ stroke_dasharray = arrow_type.include?('dotted') ? '5,5' : nil
298
+ has_arrowhead = !arrow_type.include?('cross')
299
+
300
+ # Draw line
301
+ line = Svg::Line.new.tap do |l|
302
+ l.x1 = x1
303
+ l.y1 = y1
304
+ l.x2 = x2 - (has_arrowhead ? ARROW_SIZE : 0)
305
+ l.y2 = y2
306
+ l.stroke = '#000000'
307
+ l.stroke_width = '2'
308
+ l.stroke_dasharray = stroke_dasharray if stroke_dasharray
309
+ end
310
+ group.children << line
311
+
312
+ # Draw arrowhead or cross
313
+ if arrow_type.include?('cross')
314
+ render_cross(x2, y2, group)
315
+ elsif arrow_type.include?('async')
316
+ render_open_arrowhead(x1, y1, x2, y2, group)
317
+ else
318
+ render_filled_arrowhead(x1, y1, x2, y2, group)
319
+ end
320
+ end
321
+
322
+ def render_filled_arrowhead(x1, _y1, x2, y2, group)
323
+ # Calculate arrow direction
324
+ dx = x2 - x1
325
+ angle = dx.positive? ? 0 : 180
326
+
327
+ # Arrowhead points
328
+ points = if angle.zero?
329
+ [
330
+ "#{x2},#{y2}",
331
+ "#{x2 - ARROW_SIZE},#{y2 - ARROW_SIZE / 2}",
332
+ "#{x2 - ARROW_SIZE},#{y2 + ARROW_SIZE / 2}"
333
+ ].join(' ')
334
+ else
335
+ [
336
+ "#{x2},#{y2}",
337
+ "#{x2 + ARROW_SIZE},#{y2 - ARROW_SIZE / 2}",
338
+ "#{x2 + ARROW_SIZE},#{y2 + ARROW_SIZE / 2}"
339
+ ].join(' ')
340
+ end
341
+
342
+ polygon = Svg::Polygon.new.tap do |p|
343
+ p.points = points
344
+ p.fill = '#000000'
345
+ p.stroke = '#000000'
346
+ end
347
+ group.children << polygon
348
+ end
349
+
350
+ def render_open_arrowhead(x1, _y1, x2, y2, group)
351
+ dx = x2 - x1
352
+ direction = dx.positive? ? 1 : -1
353
+
354
+ # Open arrowhead (two lines)
355
+ line1 = Svg::Line.new.tap do |l|
356
+ l.x1 = x2
357
+ l.y1 = y2
358
+ l.x2 = x2 - (direction * ARROW_SIZE)
359
+ l.y2 = y2 - ARROW_SIZE / 2
360
+ l.stroke = '#000000'
361
+ l.stroke_width = '2'
362
+ end
363
+ group.children << line1
364
+
365
+ line2 = Svg::Line.new.tap do |l|
366
+ l.x1 = x2
367
+ l.y1 = y2
368
+ l.x2 = x2 - (direction * ARROW_SIZE)
369
+ l.y2 = y2 + ARROW_SIZE / 2
370
+ l.stroke = '#000000'
371
+ l.stroke_width = '2'
372
+ end
373
+ group.children << line2
374
+ end
375
+
376
+ def render_cross(x, y, group)
377
+ size = ARROW_SIZE / 2
378
+
379
+ # Diagonal cross
380
+ line1 = Svg::Line.new.tap do |l|
381
+ l.x1 = x - size
382
+ l.y1 = y - size
383
+ l.x2 = x + size
384
+ l.y2 = y + size
385
+ l.stroke = '#000000'
386
+ l.stroke_width = '2'
387
+ end
388
+ group.children << line1
389
+
390
+ line2 = Svg::Line.new.tap do |l|
391
+ l.x1 = x - size
392
+ l.y1 = y + size
393
+ l.x2 = x + size
394
+ l.y2 = y - size
395
+ l.stroke = '#000000'
396
+ l.stroke_width = '2'
397
+ end
398
+ group.children << line2
399
+ end
400
+
401
+ def render_message_label(x1, x2, y, text, group)
402
+ # Position label above the arrow
403
+ label_x = (x1 + x2) / 2
404
+ label_y = y - 10
405
+
406
+ text_element = Svg::Text.new.tap do |t|
407
+ t.x = label_x
408
+ t.y = label_y
409
+ t.content = text
410
+ t.fill = '#000000'
411
+ t.font_family = 'Arial, sans-serif'
412
+ t.font_size = '12'
413
+ t.text_anchor = 'middle'
414
+ end
415
+ group.children << text_element
416
+ end
417
+
418
+ def render_notes(_notes, _positions, _svg)
419
+ # Note rendering can be implemented if needed
420
+ # For now, this is a placeholder
421
+ end
422
+ end
423
+ end
424
+ end