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,490 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Sirena
6
+ module Renderer
7
+ # C4 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 C4 elements, boundaries,
11
+ # and relationships with proper C4 visual conventions.
12
+ #
13
+ # @example Render a C4 diagram
14
+ # renderer = C4Renderer.new
15
+ # svg = renderer.render(laid_out_graph)
16
+ class C4Renderer < Base
17
+ # C4 standard colors (from C4-PlantUML)
18
+ C4_COLORS = {
19
+ person: { bg: '#08427B', border: '#073B6F', text: '#FFFFFF' },
20
+ person_ext: { bg: '#6C6477', border: '#4A4552', text: '#FFFFFF' },
21
+ system: { bg: '#1168BD', border: '#0B4884', text: '#FFFFFF' },
22
+ system_ext: { bg: '#8F8F8F', border: '#6B6B6B', text: '#FFFFFF' },
23
+ container: { bg: '#438DD5', border: '#2E6295', text: '#FFFFFF' },
24
+ component: { bg: '#85BBF0', border: '#5D8FB9', text: '#000000' },
25
+ boundary: { bg: '#FFFFFF', border: '#9BA7B4', text: '#000000' }
26
+ }.freeze
27
+
28
+ # Element dimensions
29
+ PERSON_WIDTH = 140
30
+ PERSON_HEIGHT = 180
31
+ PERSON_ICON_SIZE = 48
32
+ SYSTEM_WIDTH = 160
33
+ SYSTEM_HEIGHT = 120
34
+ CONTAINER_WIDTH = 160
35
+ CONTAINER_HEIGHT = 120
36
+ COMPONENT_WIDTH = 160
37
+ COMPONENT_HEIGHT = 100
38
+
39
+ # Text spacing
40
+ TEXT_PADDING = 10
41
+ LINE_HEIGHT = 16
42
+
43
+ # Arrow size
44
+ ARROW_SIZE = 8
45
+
46
+ # Renders a laid-out graph to SVG.
47
+ #
48
+ # @param graph [Hash] laid-out graph with node positions
49
+ # @return [Svg::Document] the rendered SVG document
50
+ def render(graph)
51
+ svg = create_document(graph)
52
+
53
+ # Render boundaries first (under elements)
54
+ render_boundaries(graph[:children], svg) if graph[:children]
55
+
56
+ # Render relationships (under elements but over boundaries)
57
+ render_relationships(graph[:edges], graph[:children], svg) if
58
+ graph[:edges]
59
+
60
+ # Render elements (on top)
61
+ render_elements(graph[:children], svg) if graph[:children]
62
+
63
+ svg
64
+ end
65
+
66
+ protected
67
+
68
+ def calculate_width(graph)
69
+ return 800 unless graph[:children]
70
+
71
+ # Find rightmost element
72
+ max_x = 0
73
+ traverse_children(graph[:children]) do |child|
74
+ next unless child[:x] && child[:width]
75
+
76
+ right_edge = child[:x] + child[:width]
77
+ max_x = [max_x, right_edge].max
78
+ end
79
+
80
+ max_x + 40 # Add padding
81
+ end
82
+
83
+ def calculate_height(graph)
84
+ return 600 unless graph[:children]
85
+
86
+ # Find bottommost element
87
+ max_y = 0
88
+ traverse_children(graph[:children]) do |child|
89
+ next unless child[:y] && child[:height]
90
+
91
+ bottom_edge = child[:y] + child[:height]
92
+ max_y = [max_y, bottom_edge].max
93
+ end
94
+
95
+ max_y + 40 # Add padding
96
+ end
97
+
98
+ def traverse_children(children, &block)
99
+ children.each do |child|
100
+ block.call(child)
101
+ traverse_children(child[:children], &block) if child[:children]
102
+ end
103
+ end
104
+
105
+ def render_boundaries(children, svg)
106
+ children.each do |child|
107
+ next unless child[:metadata] && child[:metadata][:boundary_type]
108
+
109
+ render_boundary(child, svg)
110
+ end
111
+ end
112
+
113
+ def render_boundary(boundary, svg)
114
+ return unless boundary[:x] && boundary[:y]
115
+
116
+ group = Svg::Group.new.tap do |g|
117
+ g.id = "boundary-#{boundary[:id]}"
118
+ end
119
+
120
+ # Boundary rectangle with dashed border
121
+ rect = Svg::Rect.new.tap do |r|
122
+ r.x = boundary[:x]
123
+ r.y = boundary[:y]
124
+ r.width = boundary[:width]
125
+ r.height = boundary[:height]
126
+ r.fill = C4_COLORS[:boundary][:bg]
127
+ r.stroke = C4_COLORS[:boundary][:border]
128
+ r.stroke_width = '2'
129
+ r.stroke_dasharray = '10,5'
130
+ r.rx = 8
131
+ r.ry = 8
132
+ end
133
+ group.children << rect
134
+
135
+ # Boundary label
136
+ label = boundary[:labels]&.first
137
+ if label
138
+ text = Svg::Text.new.tap do |t|
139
+ t.x = boundary[:x] + TEXT_PADDING
140
+ t.y = boundary[:y] + 20
141
+ t.content = label[:text]
142
+ t.fill = C4_COLORS[:boundary][:text]
143
+ t.font_family = 'Arial, sans-serif'
144
+ t.font_size = '16'
145
+ t.font_weight = 'bold'
146
+ end
147
+ group.children << text
148
+ end
149
+
150
+ svg << group
151
+
152
+ # Render nested boundaries recursively
153
+ if boundary[:children]
154
+ render_boundaries(boundary[:children], svg)
155
+ end
156
+ end
157
+
158
+ def render_elements(children, svg)
159
+ children.each do |child|
160
+ metadata = child[:metadata]
161
+ next unless metadata
162
+
163
+ if metadata[:boundary_type]
164
+ # Skip boundaries (already rendered)
165
+ # But render their child elements
166
+ render_elements(child[:children], svg) if child[:children]
167
+ else
168
+ # Render element
169
+ render_element(child, svg)
170
+ end
171
+ end
172
+ end
173
+
174
+ def render_element(element, svg)
175
+ return unless element[:x] && element[:y]
176
+
177
+ metadata = element[:metadata]
178
+ return unless metadata
179
+
180
+ group = Svg::Group.new.tap do |g|
181
+ g.id = "element-#{element[:id]}"
182
+ end
183
+
184
+ # Determine element type and render accordingly
185
+ if metadata[:person]
186
+ render_person_element(element, group)
187
+ elsif metadata[:system]
188
+ render_system_element(element, group)
189
+ elsif metadata[:container]
190
+ render_container_element(element, group)
191
+ elsif metadata[:component]
192
+ render_component_element(element, group)
193
+ else
194
+ # Default to system rendering
195
+ render_system_element(element, group)
196
+ end
197
+
198
+ svg << group
199
+ end
200
+
201
+ def render_person_element(element, group)
202
+ x = element[:x]
203
+ y = element[:y]
204
+ w = element[:width] || PERSON_WIDTH
205
+ h = element[:height] || PERSON_HEIGHT
206
+
207
+ # Determine colors based on external flag
208
+ colors = if element[:metadata][:external]
209
+ C4_COLORS[:person_ext]
210
+ else
211
+ C4_COLORS[:person]
212
+ end
213
+
214
+ # Person box with rounded corners
215
+ rect = Svg::Rect.new.tap do |r|
216
+ r.x = x
217
+ r.y = y
218
+ r.width = w
219
+ r.height = h
220
+ r.fill = colors[:bg]
221
+ r.stroke = colors[:border]
222
+ r.stroke_width = '2'
223
+ r.rx = 10
224
+ r.ry = 10
225
+ end
226
+ group.children << rect
227
+
228
+ # Person icon (simple circle with smaller circle as head)
229
+ icon_cx = x + w / 2
230
+ icon_y = y + 30
231
+
232
+ # Head
233
+ head = Svg::Circle.new.tap do |c|
234
+ c.cx = icon_cx
235
+ c.cy = icon_y
236
+ c.r = 16
237
+ c.fill = colors[:text]
238
+ end
239
+ group.children << head
240
+
241
+ # Body (simplified)
242
+ body = Svg::Ellipse.new.tap do |e|
243
+ e.cx = icon_cx
244
+ e.cy = icon_y + 35
245
+ e.rx = 24
246
+ e.ry = 28
247
+ e.fill = colors[:text]
248
+ end
249
+ group.children << body
250
+
251
+ # Render labels below icon
252
+ render_element_labels(element, x, y + 95, w, colors[:text], group)
253
+ end
254
+
255
+ def render_system_element(element, group)
256
+ x = element[:x]
257
+ y = element[:y]
258
+ w = element[:width] || SYSTEM_WIDTH
259
+ h = element[:height] || SYSTEM_HEIGHT
260
+
261
+ # Determine colors based on external flag
262
+ colors = if element[:metadata][:external]
263
+ C4_COLORS[:system_ext]
264
+ else
265
+ C4_COLORS[:system]
266
+ end
267
+
268
+ # System box
269
+ rect = Svg::Rect.new.tap do |r|
270
+ r.x = x
271
+ r.y = y
272
+ r.width = w
273
+ r.height = h
274
+ r.fill = colors[:bg]
275
+ r.stroke = colors[:border]
276
+ r.stroke_width = '2'
277
+ r.rx = 5
278
+ r.ry = 5
279
+ end
280
+ group.children << rect
281
+
282
+ # Render labels
283
+ render_element_labels(element, x, y + 20, w, colors[:text], group)
284
+ end
285
+
286
+ def render_container_element(element, group)
287
+ x = element[:x]
288
+ y = element[:y]
289
+ w = element[:width] || CONTAINER_WIDTH
290
+ h = element[:height] || CONTAINER_HEIGHT
291
+
292
+ colors = C4_COLORS[:container]
293
+
294
+ # Container box
295
+ rect = Svg::Rect.new.tap do |r|
296
+ r.x = x
297
+ r.y = y
298
+ r.width = w
299
+ r.height = h
300
+ r.fill = colors[:bg]
301
+ r.stroke = colors[:border]
302
+ r.stroke_width = '2'
303
+ r.rx = 5
304
+ r.ry = 5
305
+ end
306
+ group.children << rect
307
+
308
+ # Render labels
309
+ render_element_labels(element, x, y + 20, w, colors[:text], group)
310
+ end
311
+
312
+ def render_component_element(element, group)
313
+ x = element[:x]
314
+ y = element[:y]
315
+ w = element[:width] || COMPONENT_WIDTH
316
+ h = element[:height] || COMPONENT_HEIGHT
317
+
318
+ colors = C4_COLORS[:component]
319
+
320
+ # Component box
321
+ rect = Svg::Rect.new.tap do |r|
322
+ r.x = x
323
+ r.y = y
324
+ r.width = w
325
+ r.height = h
326
+ r.fill = colors[:bg]
327
+ r.stroke = colors[:border]
328
+ r.stroke_width = '2'
329
+ r.rx = 3
330
+ r.ry = 3
331
+ end
332
+ group.children << rect
333
+
334
+ # Render labels
335
+ render_element_labels(element, x, y + 15, w, colors[:text], group)
336
+ end
337
+
338
+ def render_element_labels(element, x, y, width, text_color, group)
339
+ labels = element[:labels] || []
340
+ current_y = y
341
+
342
+ labels.each_with_index do |label, index|
343
+ font_size = case index
344
+ when 0 # Main label
345
+ '14'
346
+ when 1 # Description
347
+ '11'
348
+ else # Technology
349
+ '10'
350
+ end
351
+
352
+ font_weight = index.zero? ? 'bold' : 'normal'
353
+ font_style = index == 2 ? 'italic' : 'normal'
354
+
355
+ text = Svg::Text.new.tap do |t|
356
+ t.x = x + width / 2
357
+ t.y = current_y
358
+ t.content = label[:text]
359
+ t.fill = text_color
360
+ t.font_family = 'Arial, sans-serif'
361
+ t.font_size = font_size
362
+ t.font_weight = font_weight
363
+ t.font_style = font_style
364
+ t.text_anchor = 'middle'
365
+ end
366
+ group.children << text
367
+
368
+ current_y += LINE_HEIGHT
369
+ end
370
+ end
371
+
372
+ def render_relationships(edges, nodes, svg)
373
+ # Build node position map
374
+ node_positions = {}
375
+ traverse_children(nodes) do |node|
376
+ next if node[:metadata]&.[](:boundary_type)
377
+
378
+ node_positions[node[:id]] = {
379
+ x: node[:x],
380
+ y: node[:y],
381
+ width: node[:width],
382
+ height: node[:height]
383
+ }
384
+ end
385
+
386
+ edges.each do |edge|
387
+ render_relationship(edge, node_positions, svg)
388
+ end
389
+ end
390
+
391
+ def render_relationship(edge, positions, svg)
392
+ source_id = edge[:sources]&.first
393
+ target_id = edge[:targets]&.first
394
+
395
+ return unless source_id && target_id
396
+
397
+ source_pos = positions[source_id]
398
+ target_pos = positions[target_id]
399
+
400
+ return unless source_pos && target_pos
401
+
402
+ # Calculate connection points (center of elements)
403
+ x1 = source_pos[:x] + source_pos[:width] / 2
404
+ y1 = source_pos[:y] + source_pos[:height] / 2
405
+ x2 = target_pos[:x] + target_pos[:width] / 2
406
+ y2 = target_pos[:y] + target_pos[:height] / 2
407
+
408
+ group = Svg::Group.new.tap do |g|
409
+ g.id = edge[:id]
410
+ end
411
+
412
+ # Draw relationship line
413
+ line = Svg::Line.new.tap do |l|
414
+ l.x1 = x1
415
+ l.y1 = y1
416
+ l.x2 = x2 - ARROW_SIZE
417
+ l.y2 = y2
418
+ l.stroke = '#707070'
419
+ l.stroke_width = '2'
420
+ end
421
+ group.children << line
422
+
423
+ # Draw arrowhead
424
+ render_arrowhead(x1, y1, x2, y2, group)
425
+
426
+ # Draw bidirectional arrow if needed
427
+ if edge[:metadata]&.[](:bidirectional)
428
+ render_arrowhead(x2, y2, x1, y1, group)
429
+ end
430
+
431
+ # Render relationship labels
432
+ if edge[:labels] && !edge[:labels].empty?
433
+ render_relationship_labels(x1, y1, x2, y2, edge[:labels], group)
434
+ end
435
+
436
+ svg << group
437
+ end
438
+
439
+ def render_arrowhead(x1, _y1, x2, y2, group)
440
+ # Calculate arrow direction
441
+ dx = x2 - x1
442
+ angle = dx.positive? ? 0 : 180
443
+
444
+ # Arrowhead points
445
+ points = if angle.zero?
446
+ [
447
+ "#{x2},#{y2}",
448
+ "#{x2 - ARROW_SIZE},#{y2 - ARROW_SIZE / 2}",
449
+ "#{x2 - ARROW_SIZE},#{y2 + ARROW_SIZE / 2}"
450
+ ].join(' ')
451
+ else
452
+ [
453
+ "#{x2},#{y2}",
454
+ "#{x2 + ARROW_SIZE},#{y2 - ARROW_SIZE / 2}",
455
+ "#{x2 + ARROW_SIZE},#{y2 + ARROW_SIZE / 2}"
456
+ ].join(' ')
457
+ end
458
+
459
+ polygon = Svg::Polygon.new.tap do |p|
460
+ p.points = points
461
+ p.fill = '#707070'
462
+ p.stroke = '#707070'
463
+ end
464
+ group.children << polygon
465
+ end
466
+
467
+ def render_relationship_labels(x1, y1, x2, y2, labels, group)
468
+ # Position label above the line
469
+ label_x = (x1 + x2) / 2
470
+ label_y = (y1 + y2) / 2 - 15
471
+
472
+ labels.each_with_index do |label, index|
473
+ font_size = index.zero? ? '12' : '10'
474
+ offset_y = index * 14
475
+
476
+ text = Svg::Text.new.tap do |t|
477
+ t.x = label_x
478
+ t.y = label_y + offset_y
479
+ t.content = label[:text]
480
+ t.fill = '#000000'
481
+ t.font_family = 'Arial, sans-serif'
482
+ t.font_size = font_size
483
+ t.text_anchor = 'middle'
484
+ end
485
+ group.children << text
486
+ end
487
+ end
488
+ end
489
+ end
490
+ end