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,499 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Sirena
6
+ module Renderer
7
+ # Class diagram renderer for converting graphs to SVG.
8
+ #
9
+ # Converts a laid-out graph structure (with computed positions) into
10
+ # SVG using the Svg builder classes. Handles UML class boxes with
11
+ # compartments, various relationship types with appropriate arrow
12
+ # styles, and cardinality labels.
13
+ #
14
+ # @example Render a class diagram
15
+ # renderer = ClassDiagramRenderer.new
16
+ # svg = renderer.render(laid_out_graph)
17
+ class ClassDiagramRenderer < Base
18
+ # Font size for class names
19
+ CLASS_NAME_FONT_SIZE = 16
20
+
21
+ # Font size for attributes and methods
22
+ MEMBER_FONT_SIZE = 12
23
+
24
+ # Font size for stereotypes
25
+ STEREOTYPE_FONT_SIZE = 11
26
+
27
+ # Line height for text
28
+ LINE_HEIGHT = 18
29
+
30
+ # Padding within class boxes
31
+ BOX_PADDING = 10
32
+
33
+ # Arrow/marker dimensions
34
+ ARROW_SIZE = 10
35
+ DIAMOND_SIZE = 12
36
+
37
+ # Renders a laid-out graph to SVG.
38
+ #
39
+ # @param graph [Hash] laid-out graph with node positions
40
+ # @return [Svg::Document] the rendered SVG document
41
+ def render(graph)
42
+ svg = create_document(graph)
43
+
44
+ # Add marker definitions
45
+ add_markers(svg)
46
+
47
+ # Render edges first (so they appear under nodes)
48
+ render_relationships(graph, svg) if graph[:edges]
49
+
50
+ # Render class boxes
51
+ render_classes(graph, svg) if graph[:children]
52
+
53
+ svg
54
+ end
55
+
56
+ protected
57
+
58
+ def calculate_width(graph)
59
+ return 800 unless graph[:children]
60
+
61
+ max_x = graph[:children].map do |node|
62
+ (node[:x] || 0) + (node[:width] || 150)
63
+ end.max || 800
64
+
65
+ max_x + 40
66
+ end
67
+
68
+ def calculate_height(graph)
69
+ return 600 unless graph[:children]
70
+
71
+ max_y = graph[:children].map do |node|
72
+ (node[:y] || 0) + (node[:height] || 100)
73
+ end.max || 600
74
+
75
+ max_y + 40
76
+ end
77
+
78
+ def add_markers(svg)
79
+ # Add definitions for various arrow types
80
+ defs = Svg::Group.new
81
+ defs.id = 'defs'
82
+
83
+ # Inheritance marker (hollow triangle)
84
+ add_inheritance_marker(defs)
85
+
86
+ # Composition marker (filled diamond)
87
+ add_composition_marker(defs)
88
+
89
+ # Aggregation marker (hollow diamond)
90
+ add_aggregation_marker(defs)
91
+
92
+ svg << defs
93
+ end
94
+
95
+ def add_inheritance_marker(defs)
96
+ # This will be rendered as a hollow triangle in render_relationship
97
+ end
98
+
99
+ def add_composition_marker(defs)
100
+ # This will be rendered as a filled diamond in render_relationship
101
+ end
102
+
103
+ def add_aggregation_marker(defs)
104
+ # This will be rendered as a hollow diamond in render_relationship
105
+ end
106
+
107
+ def render_classes(graph, svg)
108
+ graph[:children].each do |node|
109
+ render_class(node, svg)
110
+ end
111
+ end
112
+
113
+ def render_class(node, svg)
114
+ x = node[:x] || 0
115
+ y = node[:y] || 0
116
+ width = node[:width] || 150
117
+ height = node[:height] || 100
118
+
119
+ metadata = node[:metadata] || {}
120
+
121
+ # Create group for the class
122
+ group = Svg::Group.new.tap do |g|
123
+ g.id = "class-#{node[:id]}"
124
+ end
125
+
126
+ # Render outer box
127
+ box = Svg::Rect.new.tap do |r|
128
+ r.x = x
129
+ r.y = y
130
+ r.width = width
131
+ r.height = height
132
+ r.fill = '#ffffff'
133
+ r.stroke = '#000000'
134
+ r.stroke_width = '2'
135
+ r.rx = 3
136
+ r.ry = 3
137
+ end
138
+ group.children << box
139
+
140
+ # Render compartment separators and content
141
+ render_class_content(node, metadata, group)
142
+
143
+ svg << group
144
+ end
145
+
146
+ def render_class_content(node, metadata, group)
147
+ x = node[:x] || 0
148
+ y = node[:y] || 0
149
+ width = node[:width] || 150
150
+
151
+ current_y = y + BOX_PADDING
152
+
153
+ # Render stereotype if present
154
+ stereotype = metadata[:stereotype]
155
+ current_y = render_stereotype(x, current_y, width, stereotype, group) if stereotype && !stereotype.empty?
156
+
157
+ # Render class name
158
+ name = metadata[:name] || node[:id]
159
+ current_y = render_class_name(x, current_y, width, name, group)
160
+
161
+ # Add separator after name
162
+ separator_y = current_y + 5
163
+ separator = Svg::Line.new.tap do |l|
164
+ l.x1 = x
165
+ l.y1 = separator_y
166
+ l.x2 = x + width
167
+ l.y2 = separator_y
168
+ l.stroke = '#000000'
169
+ l.stroke_width = '1'
170
+ end
171
+ group.children << separator
172
+ current_y = separator_y + 10
173
+
174
+ # Render attributes
175
+ attributes = metadata[:attributes] || []
176
+ unless attributes.empty?
177
+ current_y = render_attributes(x, current_y, width, attributes, group)
178
+
179
+ # Add separator after attributes
180
+ separator_y = current_y + 5
181
+ separator = Svg::Line.new.tap do |l|
182
+ l.x1 = x
183
+ l.y1 = separator_y
184
+ l.x2 = x + width
185
+ l.y2 = separator_y
186
+ l.stroke = '#000000'
187
+ l.stroke_width = '1'
188
+ end
189
+ group.children << separator
190
+ current_y = separator_y + 10
191
+ end
192
+
193
+ # Render methods
194
+ methods = metadata[:methods] || []
195
+ render_methods(x, current_y, width, methods, group) unless
196
+ methods.empty?
197
+ end
198
+
199
+ def render_stereotype(x, y, width, stereotype, group)
200
+ text = Svg::Text.new.tap do |t|
201
+ t.x = x + width / 2
202
+ t.y = y
203
+ t.content = "<<#{stereotype}>>"
204
+ t.fill = '#000000'
205
+ t.font_family = 'Arial, sans-serif'
206
+ t.font_size = STEREOTYPE_FONT_SIZE.to_s
207
+ t.text_anchor = 'middle'
208
+ end
209
+ group.children << text
210
+ y + LINE_HEIGHT
211
+ end
212
+
213
+ def render_class_name(x, y, width, name, group)
214
+ text = Svg::Text.new.tap do |t|
215
+ t.x = x + width / 2
216
+ t.y = y
217
+ t.content = name
218
+ t.fill = '#000000'
219
+ t.font_family = 'Arial, sans-serif'
220
+ t.font_size = CLASS_NAME_FONT_SIZE.to_s
221
+ t.text_anchor = 'middle'
222
+ t.font_weight = 'bold'
223
+ end
224
+ group.children << text
225
+ y + LINE_HEIGHT
226
+ end
227
+
228
+ def render_attributes(x, y, _width, attributes, group)
229
+ current_y = y
230
+
231
+ attributes.each do |attr|
232
+ visibility = visibility_symbol(attr[:visibility])
233
+ attr_text = "#{visibility} #{attr[:name]}"
234
+ attr_text += ": #{attr[:type]}" if attr[:type] &&
235
+ !attr[:type].empty?
236
+
237
+ text = Svg::Text.new.tap do |t|
238
+ t.x = x + BOX_PADDING
239
+ t.y = current_y
240
+ t.content = attr_text
241
+ t.fill = '#000000'
242
+ t.font_family = 'monospace'
243
+ t.font_size = MEMBER_FONT_SIZE.to_s
244
+ end
245
+ group.children << text
246
+ current_y += LINE_HEIGHT
247
+ end
248
+
249
+ current_y
250
+ end
251
+
252
+ def render_methods(x, y, _width, methods, group)
253
+ current_y = y
254
+
255
+ methods.each do |method|
256
+ visibility = visibility_symbol(method[:visibility])
257
+ method_text = "#{visibility} #{method[:name]}"
258
+ method_text += "(#{method[:parameters]})" if method[:parameters]
259
+ method_text += ": #{method[:return_type]}" if method[:return_type]
260
+
261
+ text = Svg::Text.new.tap do |t|
262
+ t.x = x + BOX_PADDING
263
+ t.y = current_y
264
+ t.content = method_text
265
+ t.fill = '#000000'
266
+ t.font_family = 'monospace'
267
+ t.font_size = MEMBER_FONT_SIZE.to_s
268
+ end
269
+ group.children << text
270
+ current_y += LINE_HEIGHT
271
+ end
272
+
273
+ current_y
274
+ end
275
+
276
+ def visibility_symbol(visibility)
277
+ case visibility
278
+ when 'public' then '+'
279
+ when 'private' then '-'
280
+ when 'protected' then '#'
281
+ when 'package' then '~'
282
+ else '+'
283
+ end
284
+ end
285
+
286
+ def render_relationships(graph, svg)
287
+ graph[:edges].each do |edge|
288
+ render_relationship(edge, graph, svg)
289
+ end
290
+ end
291
+
292
+ def render_relationship(edge, graph, svg)
293
+ source = find_node(graph, edge[:sources]&.first)
294
+ target = find_node(graph, edge[:targets]&.first)
295
+
296
+ return unless source && target
297
+
298
+ metadata = edge[:metadata] || {}
299
+ rel_type = metadata[:relationship_type] || 'association'
300
+
301
+ # Create group for the relationship
302
+ group = Svg::Group.new.tap do |g|
303
+ g.id = "rel-#{edge[:id]}"
304
+ end
305
+
306
+ # Calculate connection points
307
+ source_point = calculate_connection_point(source, target)
308
+ target_point = calculate_connection_point(target, source)
309
+
310
+ # Render the line
311
+ render_relationship_line(source_point, target_point, rel_type, group)
312
+
313
+ # Render arrow/marker at target
314
+ render_relationship_marker(source_point, target_point, rel_type, group)
315
+
316
+ # Render labels if present
317
+ render_relationship_labels(edge, source_point, target_point, group)
318
+
319
+ svg << group
320
+ end
321
+
322
+ def find_node(graph, node_id)
323
+ return nil unless graph[:children] && node_id
324
+
325
+ graph[:children].find { |n| n[:id] == node_id }
326
+ end
327
+
328
+ def calculate_connection_point(from_node, to_node)
329
+ from_cx = (from_node[:x] || 0) + (from_node[:width] || 150) / 2
330
+ from_cy = (from_node[:y] || 0) + (from_node[:height] || 100) / 2
331
+ to_cx = (to_node[:x] || 0) + (to_node[:width] || 150) / 2
332
+ to_cy = (to_node[:y] || 0) + (to_node[:height] || 100) / 2
333
+
334
+ # Determine which edge of the box to connect to
335
+ from_x = from_node[:x] || 0
336
+ from_y = from_node[:y] || 0
337
+ from_w = from_node[:width] || 150
338
+ from_h = from_node[:height] || 100
339
+
340
+ # Calculate intersection with box edge
341
+ dx = to_cx - from_cx
342
+ dy = to_cy - from_cy
343
+
344
+ # Handle edge cases
345
+ if dx.abs < 0.001 && dy.abs < 0.001
346
+ # Same position - use center
347
+ return { x: from_cx, y: from_cy }
348
+ end
349
+
350
+ # Find intersection point
351
+ if dx.abs > dy.abs
352
+ # Connect left/right edge
353
+ x = dx.positive? ? from_x + from_w : from_x
354
+ y = dy.abs < 0.001 ? from_cy : from_cy + (dy / dx) * (x - from_cx)
355
+ else
356
+ # Connect top/bottom edge
357
+ y = dy.positive? ? from_y + from_h : from_y
358
+ x = dx.abs < 0.001 ? from_cx : from_cx + (dx / dy) * (y - from_cy)
359
+ end
360
+
361
+ { x: x, y: y }
362
+ end
363
+
364
+ def render_relationship_line(from, to, rel_type, group)
365
+ line = Svg::Line.new.tap do |l|
366
+ l.x1 = from[:x]
367
+ l.y1 = from[:y]
368
+ l.x2 = to[:x]
369
+ l.y2 = to[:y]
370
+ l.stroke = '#000000'
371
+ l.stroke_width = '2'
372
+ l.stroke_dasharray = '5,5' if rel_type == 'dependency'
373
+ end
374
+ group.children << line
375
+ end
376
+
377
+ def render_relationship_marker(from, to, rel_type, group)
378
+ case rel_type
379
+ when 'inheritance', 'realization'
380
+ render_triangle_marker(from, to, rel_type == 'inheritance', group)
381
+ when 'composition'
382
+ render_diamond_marker(from, to, true, group)
383
+ when 'aggregation'
384
+ render_diamond_marker(from, to, false, group)
385
+ end
386
+ end
387
+
388
+ def render_triangle_marker(from, to, filled, group)
389
+ dx = to[:x] - from[:x]
390
+ dy = to[:y] - from[:y]
391
+ angle = Math.atan2(dy, dx)
392
+
393
+ # Triangle points
394
+ tip_x = to[:x]
395
+ tip_y = to[:y]
396
+ base_length = ARROW_SIZE
397
+ base1_x = tip_x - base_length * Math.cos(angle + Math::PI / 6)
398
+ base1_y = tip_y - base_length * Math.sin(angle + Math::PI / 6)
399
+ base2_x = tip_x - base_length * Math.cos(angle - Math::PI / 6)
400
+ base2_y = tip_y - base_length * Math.sin(angle - Math::PI / 6)
401
+
402
+ points = "#{tip_x},#{tip_y} #{base1_x},#{base1_y} " \
403
+ "#{base2_x},#{base2_y}"
404
+
405
+ polygon = Svg::Polygon.new.tap do |p|
406
+ p.points = points
407
+ p.fill = filled ? '#000000' : '#ffffff'
408
+ p.stroke = '#000000'
409
+ p.stroke_width = '2'
410
+ end
411
+ group.children << polygon
412
+ end
413
+
414
+ def render_diamond_marker(from, to, filled, group)
415
+ dx = to[:x] - from[:x]
416
+ dy = to[:y] - from[:y]
417
+ angle = Math.atan2(dy, dx)
418
+
419
+ # Diamond center at connection point
420
+ cx = from[:x]
421
+ cy = from[:y]
422
+ size = DIAMOND_SIZE
423
+
424
+ # Diamond points
425
+ tip_x = cx + size * Math.cos(angle)
426
+ tip_y = cy + size * Math.sin(angle)
427
+ left_x = cx + size / 2 * Math.cos(angle + Math::PI / 2)
428
+ left_y = cy + size / 2 * Math.sin(angle + Math::PI / 2)
429
+ back_x = cx - size * Math.cos(angle)
430
+ back_y = cy - size * Math.sin(angle)
431
+ right_x = cx + size / 2 * Math.cos(angle - Math::PI / 2)
432
+ right_y = cy + size / 2 * Math.sin(angle - Math::PI / 2)
433
+
434
+ points = "#{tip_x},#{tip_y} #{left_x},#{left_y} " \
435
+ "#{back_x},#{back_y} #{right_x},#{right_y}"
436
+
437
+ polygon = Svg::Polygon.new.tap do |p|
438
+ p.points = points
439
+ p.fill = filled ? '#000000' : '#ffffff'
440
+ p.stroke = '#000000'
441
+ p.stroke_width = '2'
442
+ end
443
+ group.children << polygon
444
+ end
445
+
446
+ def render_relationship_labels(edge, from, to, group)
447
+ labels = edge[:labels] || []
448
+ return if labels.empty?
449
+
450
+ # Main label in the middle
451
+ main_label = labels.find { |l| !l[:position] }
452
+ if main_label
453
+ mid_x = (from[:x] + to[:x]) / 2
454
+ mid_y = (from[:y] + to[:y]) / 2
455
+
456
+ text = Svg::Text.new.tap do |t|
457
+ t.x = mid_x
458
+ t.y = mid_y - 5
459
+ t.content = main_label[:text]
460
+ t.fill = '#000000'
461
+ t.font_family = 'Arial, sans-serif'
462
+ t.font_size = '11'
463
+ t.text_anchor = 'middle'
464
+ end
465
+ group.children << text
466
+ end
467
+
468
+ # Source cardinality
469
+ source_label = labels.find { |l| l[:position] == 'source' }
470
+ if source_label
471
+ text = Svg::Text.new.tap do |t|
472
+ t.x = from[:x] + 5
473
+ t.y = from[:y] - 5
474
+ t.content = source_label[:text]
475
+ t.fill = '#000000'
476
+ t.font_family = 'Arial, sans-serif'
477
+ t.font_size = '10'
478
+ end
479
+ group.children << text
480
+ end
481
+
482
+ # Target cardinality
483
+ target_label = labels.find { |l| l[:position] == 'target' }
484
+ return unless target_label
485
+
486
+ text = Svg::Text.new.tap do |t|
487
+ t.x = to[:x] - 5
488
+ t.y = to[:y] - 5
489
+ t.content = target_label[:text]
490
+ t.fill = '#000000'
491
+ t.font_family = 'Arial, sans-serif'
492
+ t.font_size = '10'
493
+ t.text_anchor = 'end'
494
+ end
495
+ group.children << text
496
+ end
497
+ end
498
+ end
499
+ end