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,371 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Sirena
6
+ module Renderer
7
+ # Requirement diagram renderer for converting positioned layouts to SVG.
8
+ #
9
+ # Converts a positioned requirement diagram layout into SVG using the Svg
10
+ # builder classes. Handles requirements with properties, elements, and
11
+ # relationships between them.
12
+ #
13
+ # @example Render a requirement diagram
14
+ # renderer = RequirementRenderer.new
15
+ # svg = renderer.render(layout)
16
+ class RequirementRenderer < Base
17
+ # Risk level color mapping
18
+ RISK_COLORS = {
19
+ 'high' => '#ff6b6b',
20
+ 'medium' => '#ffd93d',
21
+ 'low' => '#6bcf7f'
22
+ }.freeze
23
+
24
+ # Requirement type labels
25
+ REQUIREMENT_TYPE_LABELS = {
26
+ 'requirement' => 'Requirement',
27
+ 'functionalRequirement' => 'Functional Req',
28
+ 'interfaceRequirement' => 'Interface Req',
29
+ 'performanceRequirement' => 'Performance Req',
30
+ 'physicalRequirement' => 'Physical Req',
31
+ 'designConstraint' => 'Design Constraint'
32
+ }.freeze
33
+
34
+ # Renders a positioned layout to SVG.
35
+ #
36
+ # @param layout [Hash] positioned layout with requirement and element positions
37
+ # @return [Svg::Document] the rendered SVG document
38
+ def render(layout)
39
+ svg = create_document_from_layout(layout)
40
+
41
+ # Render relationships first (so they appear under nodes)
42
+ render_relationships(layout, svg) if layout[:relationships]
43
+
44
+ # Render requirements
45
+ render_requirements(layout, svg) if layout[:requirements]
46
+
47
+ # Render elements
48
+ render_elements(layout, svg) if layout[:elements]
49
+
50
+ svg
51
+ end
52
+
53
+ protected
54
+
55
+ def create_document_from_layout(layout)
56
+ width = layout[:width] || 800
57
+ height = layout[:height] || 600
58
+
59
+ Svg::Document.new(width: width, height: height)
60
+ end
61
+
62
+ def render_requirements(layout, svg)
63
+ layout[:requirements].each do |_req_name, req_info|
64
+ render_requirement(req_info, svg)
65
+ end
66
+ end
67
+
68
+ def render_requirement(req_info, svg)
69
+ requirement = req_info[:requirement]
70
+ x = req_info[:x]
71
+ y = req_info[:y]
72
+ width = req_info[:width]
73
+ height = req_info[:height]
74
+
75
+ # Create group for requirement
76
+ group = Svg::Group.new.tap do |g|
77
+ g.id = "requirement-#{requirement.name}"
78
+ end
79
+
80
+ # Draw main box
81
+ box = create_requirement_box(x, y, width, height, requirement)
82
+ group.children << box
83
+
84
+ # Draw header section with type
85
+ header_height = 30
86
+ header = create_requirement_header(x, y, width, header_height, requirement)
87
+ group.children << header
88
+
89
+ # Add text content
90
+ text_y = y + header_height + 15
91
+ line_height = 16
92
+
93
+ # ID
94
+ if requirement.id
95
+ id_text = create_property_text(x + 10, text_y, "ID: #{requirement.id}")
96
+ group.children << id_text
97
+ text_y += line_height
98
+ end
99
+
100
+ # Text
101
+ if requirement.text
102
+ text_lines = wrap_text(requirement.text, width - 20, 12)
103
+ text_lines.each do |line|
104
+ text_element = create_property_text(x + 10, text_y, line)
105
+ group.children << text_element
106
+ text_y += line_height
107
+ end
108
+ text_y += 5
109
+ end
110
+
111
+ # Risk
112
+ if requirement.risk
113
+ risk_text = create_property_text(x + 10, text_y, "Risk: #{requirement.risk.capitalize}")
114
+ risk_text.fill = get_risk_color(requirement.risk)
115
+ risk_text.font_weight = 'bold'
116
+ group.children << risk_text
117
+ text_y += line_height
118
+ end
119
+
120
+ # Verify method
121
+ if requirement.verifymethod
122
+ verify_text = create_property_text(x + 10, text_y, "Verify: #{requirement.verifymethod.capitalize}")
123
+ group.children << verify_text
124
+ end
125
+
126
+ svg << group
127
+ end
128
+
129
+ def create_requirement_box(x, y, width, height, requirement)
130
+ Svg::Rect.new.tap do |rect|
131
+ rect.x = x
132
+ rect.y = y
133
+ rect.width = width
134
+ rect.height = height
135
+ rect.fill = theme_color(:node_fill) || '#f9f9f9'
136
+ rect.stroke = get_risk_color(requirement.risk) || theme_color(:border_color) || '#333'
137
+ rect.stroke_width = '2'
138
+ rect.rx = '5'
139
+ rect.ry = '5'
140
+ end
141
+ end
142
+
143
+ def create_requirement_header(x, y, width, height, requirement)
144
+ group = Svg::Group.new
145
+
146
+ # Header background
147
+ header_bg = Svg::Rect.new.tap do |rect|
148
+ rect.x = x
149
+ rect.y = y
150
+ rect.width = width
151
+ rect.height = height
152
+ rect.fill = get_risk_color(requirement.risk) || theme_color(:node_fill) || '#e0e0e0'
153
+ rect.opacity = '0.3'
154
+ end
155
+ group.children << header_bg
156
+
157
+ # Type label
158
+ type_label = REQUIREMENT_TYPE_LABELS[requirement.type] || requirement.type
159
+ type_text = Svg::Text.new.tap do |text|
160
+ text.x = x + 10
161
+ text.y = y + height / 2
162
+ text.content = type_label
163
+ text.fill = theme_color(:text_color) || '#000'
164
+ text.font_size = '12'
165
+ text.font_weight = 'bold'
166
+ text.dominant_baseline = 'middle'
167
+ end
168
+ group.children << type_text
169
+
170
+ # Name on the right
171
+ name_text = Svg::Text.new.tap do |text|
172
+ text.x = x + width - 10
173
+ text.y = y + height / 2
174
+ text.content = requirement.name
175
+ text.fill = theme_color(:text_color) || '#000'
176
+ text.font_size = '11'
177
+ text.text_anchor = 'end'
178
+ text.dominant_baseline = 'middle'
179
+ end
180
+ group.children << name_text
181
+
182
+ group
183
+ end
184
+
185
+ def render_elements(layout, svg)
186
+ layout[:elements].each do |_elem_name, elem_info|
187
+ render_element(elem_info, svg)
188
+ end
189
+ end
190
+
191
+ def render_element(elem_info, svg)
192
+ element = elem_info[:element]
193
+ x = elem_info[:x]
194
+ y = elem_info[:y]
195
+ width = elem_info[:width]
196
+ height = elem_info[:height]
197
+
198
+ # Create group for element
199
+ group = Svg::Group.new.tap do |g|
200
+ g.id = "element-#{element.name}"
201
+ end
202
+
203
+ # Draw hexagon shape for elements
204
+ hexagon = create_hexagon(x, y, width, height)
205
+ group.children << hexagon
206
+
207
+ # Add name
208
+ name_text = Svg::Text.new.tap do |text|
209
+ text.x = x + width / 2
210
+ text.y = y + height / 2 - 10
211
+ text.content = element.name
212
+ text.fill = theme_color(:text_color) || '#000'
213
+ text.font_size = '14'
214
+ text.font_weight = 'bold'
215
+ text.text_anchor = 'middle'
216
+ text.dominant_baseline = 'middle'
217
+ end
218
+ group.children << name_text
219
+
220
+ # Add type if present
221
+ if element.type
222
+ type_text = Svg::Text.new.tap do |text|
223
+ text.x = x + width / 2
224
+ text.y = y + height / 2 + 10
225
+ text.content = "Type: #{element.type}"
226
+ text.fill = theme_color(:text_color) || '#666'
227
+ text.font_size = '11'
228
+ text.text_anchor = 'middle'
229
+ text.dominant_baseline = 'middle'
230
+ end
231
+ group.children << type_text
232
+ end
233
+
234
+ svg << group
235
+ end
236
+
237
+ def create_hexagon(x, y, width, height)
238
+ cx = x + width / 2
239
+ cy = y + height / 2
240
+ w = width / 2
241
+ h = height / 2
242
+
243
+ points = [
244
+ "#{cx - w},#{cy}",
245
+ "#{cx - w/2},#{cy - h}",
246
+ "#{cx + w/2},#{cy - h}",
247
+ "#{cx + w},#{cy}",
248
+ "#{cx + w/2},#{cy + h}",
249
+ "#{cx - w/2},#{cy + h}"
250
+ ].join(' ')
251
+
252
+ Svg::Polygon.new.tap do |polygon|
253
+ polygon.points = points
254
+ polygon.fill = theme_color(:node_fill) || '#e0f2f1'
255
+ polygon.stroke = theme_color(:border_color) || '#00796b'
256
+ polygon.stroke_width = '2'
257
+ end
258
+ end
259
+
260
+ def render_relationships(layout, svg)
261
+ layout[:relationships].each do |rel_info|
262
+ render_relationship(rel_info, svg)
263
+ end
264
+ end
265
+
266
+ def render_relationship(rel_info, svg)
267
+ # Calculate path for relationship
268
+ path_data = calculate_relationship_path(rel_info)
269
+
270
+ # Create path element
271
+ path = Svg::Path.new.tap do |p|
272
+ p.d = path_data
273
+ p.fill = 'none'
274
+ p.stroke = theme_color(:edge_color) || '#666'
275
+ p.stroke_width = '2'
276
+ p.marker_end = 'url(#arrowhead)'
277
+ end
278
+
279
+ # Create group for relationship
280
+ group = Svg::Group.new.tap do |g|
281
+ g.id = "relationship-#{rel_info[:source]}-#{rel_info[:target]}"
282
+ end
283
+
284
+ group.children << path
285
+
286
+ # Add label for relationship type
287
+ if rel_info[:type]
288
+ mid_x = (rel_info[:from_x] + rel_info[:to_x]) / 2
289
+ mid_y = (rel_info[:from_y] + rel_info[:to_y]) / 2
290
+
291
+ label_bg = Svg::Rect.new.tap do |rect|
292
+ label_width = rel_info[:type].length * 7
293
+ rect.x = mid_x - label_width / 2
294
+ rect.y = mid_y - 10
295
+ rect.width = label_width
296
+ rect.height = 18
297
+ rect.fill = '#fff'
298
+ rect.stroke = theme_color(:edge_color) || '#666'
299
+ rect.stroke_width = '1'
300
+ rect.rx = '3'
301
+ end
302
+ group.children << label_bg
303
+
304
+ label = Svg::Text.new.tap do |text|
305
+ text.x = mid_x
306
+ text.y = mid_y
307
+ text.content = rel_info[:type]
308
+ text.fill = theme_color(:text_color) || '#000'
309
+ text.font_size = '10'
310
+ text.text_anchor = 'middle'
311
+ text.dominant_baseline = 'middle'
312
+ end
313
+ group.children << label
314
+ end
315
+
316
+ svg << group
317
+ end
318
+
319
+ def calculate_relationship_path(rel_info)
320
+ from_x = rel_info[:from_x]
321
+ from_y = rel_info[:from_y]
322
+ to_x = rel_info[:to_x]
323
+ to_y = rel_info[:to_y]
324
+
325
+ # Use bezier curve for better aesthetics
326
+ control_offset = (to_y - from_y).abs / 3
327
+
328
+ "M #{from_x} #{from_y} C #{from_x} #{from_y + control_offset}, " \
329
+ "#{to_x} #{to_y - control_offset}, #{to_x} #{to_y}"
330
+ end
331
+
332
+ def create_property_text(x, y, content)
333
+ Svg::Text.new.tap do |text|
334
+ text.x = x
335
+ text.y = y
336
+ text.content = content
337
+ text.fill = theme_color(:text_color) || '#000'
338
+ text.font_size = '12'
339
+ end
340
+ end
341
+
342
+ def get_risk_color(risk)
343
+ return nil unless risk
344
+
345
+ RISK_COLORS[risk.downcase] || theme_color(:border_color) || '#666'
346
+ end
347
+
348
+ def wrap_text(text, max_width, font_size)
349
+ # Simple text wrapping
350
+ words = text.split(' ')
351
+ lines = []
352
+ current_line = []
353
+
354
+ chars_per_line = (max_width / (font_size * 0.6)).to_i
355
+
356
+ words.each do |word|
357
+ test_line = (current_line + [word]).join(' ')
358
+ if test_line.length <= chars_per_line
359
+ current_line << word
360
+ else
361
+ lines << current_line.join(' ') unless current_line.empty?
362
+ current_line = [word]
363
+ end
364
+ end
365
+
366
+ lines << current_line.join(' ') unless current_line.empty?
367
+ lines
368
+ end
369
+ end
370
+ end
371
+ end
@@ -0,0 +1,255 @@
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/path"
8
+ require_relative "../svg/group"
9
+
10
+ module Sirena
11
+ module Renderer
12
+ # Sankey renderer for converting Sankey diagrams to SVG.
13
+ #
14
+ # Converts a Sankey diagram model into SVG with nodes as rectangles
15
+ # and flows as curved paths with width proportional to flow values.
16
+ #
17
+ # @example Render a Sankey diagram
18
+ # renderer = SankeyRenderer.new
19
+ # svg = renderer.render(sankey_graph)
20
+ class SankeyRenderer < Base
21
+ # Sankey dimensions
22
+ MARGIN_LEFT = 60
23
+ MARGIN_TOP = 100
24
+ MARGIN_RIGHT = 60
25
+ MARGIN_BOTTOM = 60
26
+ TITLE_Y = 40
27
+
28
+ # Flow colors (cycle through for visual distinction)
29
+ FLOW_COLORS = [
30
+ "#4472C4", # Blue
31
+ "#ED7D31", # Orange
32
+ "#A5A5A5", # Gray
33
+ "#FFC000", # Yellow
34
+ "#5B9BD5", # Light Blue
35
+ "#70AD47", # Green
36
+ "#C00000", # Red
37
+ "#7030A0" # Purple
38
+ ].freeze
39
+
40
+ # Renders a Sankey diagram to SVG.
41
+ #
42
+ # @param graph [Hash] the sankey graph structure from transform
43
+ # @return [Svg::Document] the rendered SVG document
44
+ def render(graph)
45
+ @graph = graph
46
+ @flow_color_index = 0
47
+
48
+ svg = create_document_for_sankey(graph)
49
+
50
+ # Render title if present
51
+ render_title(graph, svg) if graph[:title]
52
+
53
+ # Render flows first (so they appear behind nodes)
54
+ render_flows(graph, svg)
55
+
56
+ # Render nodes on top
57
+ render_nodes(graph, svg)
58
+
59
+ svg
60
+ end
61
+
62
+ protected
63
+
64
+ def create_document_for_sankey(graph)
65
+ width = calculate_width_for_sankey(graph)
66
+ height = calculate_height_for_sankey(graph)
67
+
68
+ Svg::Document.new.tap do |doc|
69
+ doc.width = width
70
+ doc.height = height
71
+ doc.view_box = "0 0 #{width} #{height}"
72
+ end
73
+ end
74
+
75
+ def calculate_width_for_sankey(graph)
76
+ nodes = graph[:nodes] || []
77
+ return MARGIN_LEFT + MARGIN_RIGHT + 400 if nodes.empty?
78
+
79
+ max_x = nodes.map { |n| n[:x] + n[:width] }.max || 400
80
+ MARGIN_LEFT + max_x + MARGIN_RIGHT + 100
81
+ end
82
+
83
+ def calculate_height_for_sankey(graph)
84
+ nodes = graph[:nodes] || []
85
+ return MARGIN_TOP + MARGIN_BOTTOM + 300 if nodes.empty?
86
+
87
+ max_y = nodes.map { |n| n[:y] + n[:height] }.max || 300
88
+ MARGIN_TOP + max_y + MARGIN_BOTTOM
89
+ end
90
+
91
+ def render_title(graph, svg)
92
+ title_text = Svg::Text.new.tap do |t|
93
+ t.x = (svg.width.to_i / 2)
94
+ t.y = TITLE_Y
95
+ t.content = graph[:title]
96
+ t.fill = theme_color(:label_text) || "#000000"
97
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
98
+ t.font_size = (theme_typography(:font_size_large) || 18).to_s
99
+ t.text_anchor = "middle"
100
+ t.font_weight = "bold"
101
+ end
102
+ svg << title_text
103
+ end
104
+
105
+ def render_nodes(graph, svg)
106
+ nodes = graph[:nodes] || []
107
+
108
+ nodes.each do |node|
109
+ render_node(node, svg)
110
+ end
111
+ end
112
+
113
+ def render_node(node, svg)
114
+ x = MARGIN_LEFT + node[:x]
115
+ y = MARGIN_TOP + node[:y]
116
+ width = node[:width]
117
+ height = node[:height]
118
+
119
+ # Node rectangle
120
+ node_rect = Svg::Rect.new.tap do |r|
121
+ r.x = x
122
+ r.y = y
123
+ r.width = width
124
+ r.height = height
125
+ r.fill = theme_color(:node_fill) || "#2E86AB"
126
+ r.stroke = theme_color(:node_stroke) || "#1A5276"
127
+ r.stroke_width = "2"
128
+ r.rx = "3"
129
+ r.ry = "3"
130
+ end
131
+ svg << node_rect
132
+
133
+ # Node label
134
+ label_x = x + (width / 2)
135
+ label_y = y + (height / 2) + 5
136
+
137
+ node_text = Svg::Text.new.tap do |t|
138
+ t.x = label_x
139
+ t.y = label_y
140
+ t.content = node[:label]
141
+ t.fill = theme_color(:node_text) || "#FFFFFF"
142
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
143
+ t.font_size = (theme_typography(:font_size_small) || 11).to_s
144
+ t.text_anchor = "middle"
145
+ t.font_weight = "bold"
146
+ end
147
+ svg << node_text
148
+ end
149
+
150
+ def render_flows(graph, svg)
151
+ flows = graph[:flows] || []
152
+
153
+ flows.each do |flow|
154
+ render_flow(flow, svg)
155
+ end
156
+ end
157
+
158
+ def render_flow(flow, svg)
159
+ return if flow[:self_loop] # Skip self-loops for now
160
+
161
+ source_x = MARGIN_LEFT + flow[:source_x]
162
+ source_y = MARGIN_TOP + flow[:source_y]
163
+ target_x = MARGIN_LEFT + flow[:target_x]
164
+ target_y = MARGIN_TOP + flow[:target_y]
165
+ flow_width = flow[:width]
166
+
167
+ # Get color for this flow
168
+ color = get_flow_color
169
+
170
+ # Create curved path for flow
171
+ path_d = create_flow_path(source_x, source_y, target_x, target_y, flow_width)
172
+
173
+ flow_path = Svg::Path.new.tap do |p|
174
+ p.d = path_d
175
+ p.fill = color
176
+ p.opacity = "0.4"
177
+ p.stroke = "none"
178
+ end
179
+ svg << flow_path
180
+
181
+ # Optional: render flow value label
182
+ if flow[:value] && flow[:value] > 0
183
+ render_flow_label(flow, source_x, source_y, target_x, target_y, svg)
184
+ end
185
+ end
186
+
187
+ def create_flow_path(x1, y1, x2, y2, width)
188
+ # Calculate control points for bezier curve
189
+ dx = x2 - x1
190
+ control_offset = dx * 0.5
191
+
192
+ # Calculate perpendicular offset for width
193
+ half_width = width / 2.0
194
+
195
+ # Top curve points
196
+ top_y1 = y1 - half_width
197
+ top_y2 = y2 - half_width
198
+
199
+ # Bottom curve points
200
+ bottom_y1 = y1 + half_width
201
+ bottom_y2 = y2 + half_width
202
+
203
+ # Control points for bezier curves
204
+ cp1_x = x1 + control_offset
205
+ cp2_x = x2 - control_offset
206
+
207
+ # Build path: start at top-left, curve to top-right,
208
+ # line down right side, curve back to bottom-left, close
209
+ path_commands = [
210
+ "M #{x1} #{top_y1}",
211
+ "C #{cp1_x} #{top_y1}, #{cp2_x} #{top_y2}, #{x2} #{top_y2}",
212
+ "L #{x2} #{bottom_y2}",
213
+ "C #{cp2_x} #{bottom_y2}, #{cp1_x} #{bottom_y1}, #{x1} #{bottom_y1}",
214
+ "Z"
215
+ ]
216
+
217
+ path_commands.join(" ")
218
+ end
219
+
220
+ def render_flow_label(flow, x1, y1, x2, y2, svg)
221
+ # Position label at midpoint of flow
222
+ mid_x = (x1 + x2) / 2.0
223
+ mid_y = (y1 + y2) / 2.0
224
+
225
+ # Format value (show up to 2 decimal places)
226
+ value_text = format_flow_value(flow[:value])
227
+
228
+ label = Svg::Text.new.tap do |t|
229
+ t.x = mid_x
230
+ t.y = mid_y
231
+ t.content = value_text
232
+ t.fill = theme_color(:label_text) || "#333333"
233
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
234
+ t.font_size = (theme_typography(:font_size_small) || 10).to_s
235
+ t.text_anchor = "middle"
236
+ end
237
+ svg << label
238
+ end
239
+
240
+ def format_flow_value(value)
241
+ if value == value.to_i
242
+ value.to_i.to_s
243
+ else
244
+ format("%.2f", value).gsub(/\.?0+$/, "")
245
+ end
246
+ end
247
+
248
+ def get_flow_color
249
+ color = FLOW_COLORS[@flow_color_index % FLOW_COLORS.length]
250
+ @flow_color_index += 1
251
+ color
252
+ end
253
+ end
254
+ end
255
+ end