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,417 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Sirena
6
+ module Renderer
7
+ # ER 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 ER entity boxes with
11
+ # attributes, PK/FK markers, and relationship lines with crow's foot
12
+ # cardinality notation.
13
+ #
14
+ # @example Render an ER diagram
15
+ # renderer = ErDiagramRenderer.new
16
+ # svg = renderer.render(laid_out_graph)
17
+ class ErDiagramRenderer < Base
18
+ # Font size for entity names
19
+ ENTITY_NAME_FONT_SIZE = 16
20
+
21
+ # Font size for attributes
22
+ ATTRIBUTE_FONT_SIZE = 12
23
+
24
+ # Line height for text
25
+ LINE_HEIGHT = 18
26
+
27
+ # Padding within entity boxes
28
+ BOX_PADDING = 10
29
+
30
+ # Cardinality symbol size
31
+ CARDINALITY_SIZE = 15
32
+
33
+ # Renders a laid-out graph to SVG.
34
+ #
35
+ # @param graph [Hash] laid-out graph with node positions
36
+ # @return [Svg::Document] the rendered SVG document
37
+ def render(graph)
38
+ svg = create_document(graph)
39
+
40
+ # Render edges first (so they appear under nodes)
41
+ render_relationships(graph, svg) if graph[:edges]
42
+
43
+ # Render entity boxes
44
+ render_entities(graph, svg) if graph[:children]
45
+
46
+ svg
47
+ end
48
+
49
+ protected
50
+
51
+ def calculate_width(graph)
52
+ return 800 unless graph[:children]
53
+
54
+ max_x = graph[:children].map do |node|
55
+ (node[:x] || 0) + (node[:width] || 150)
56
+ end.max || 800
57
+
58
+ max_x + 40
59
+ end
60
+
61
+ def calculate_height(graph)
62
+ return 600 unless graph[:children]
63
+
64
+ max_y = graph[:children].map do |node|
65
+ (node[:y] || 0) + (node[:height] || 100)
66
+ end.max || 600
67
+
68
+ max_y + 40
69
+ end
70
+
71
+ def render_entities(graph, svg)
72
+ graph[:children].each do |node|
73
+ render_entity(node, svg)
74
+ end
75
+ end
76
+
77
+ def render_entity(node, svg)
78
+ x = node[:x] || 0
79
+ y = node[:y] || 0
80
+ width = node[:width] || 150
81
+ height = node[:height] || 100
82
+
83
+ metadata = node[:metadata] || {}
84
+
85
+ # Create group for the entity
86
+ group = Svg::Group.new.tap do |g|
87
+ g.id = "entity-#{node[:id]}"
88
+ end
89
+
90
+ # Render outer box
91
+ box = Svg::Rect.new.tap do |r|
92
+ r.x = x
93
+ r.y = y
94
+ r.width = width
95
+ r.height = height
96
+ r.fill = '#f9f9f9'
97
+ r.stroke = '#333333'
98
+ r.stroke_width = '2'
99
+ end
100
+ group.children << box
101
+
102
+ # Render entity content
103
+ render_entity_content(node, metadata, group)
104
+
105
+ svg << group
106
+ end
107
+
108
+ def render_entity_content(node, metadata, group)
109
+ x = node[:x] || 0
110
+ y = node[:y] || 0
111
+ width = node[:width] || 150
112
+
113
+ current_y = y + BOX_PADDING + ENTITY_NAME_FONT_SIZE
114
+
115
+ # Render entity name
116
+ name = metadata[:name] || node[:id]
117
+ text = Svg::Text.new.tap do |t|
118
+ t.x = x + width / 2
119
+ t.y = current_y
120
+ t.content = name
121
+ t.fill = '#000000'
122
+ t.font_family = 'Arial, sans-serif'
123
+ t.font_size = ENTITY_NAME_FONT_SIZE.to_s
124
+ t.text_anchor = 'middle'
125
+ t.font_weight = 'bold'
126
+ end
127
+ group.children << text
128
+
129
+ # Add separator after name
130
+ current_y += LINE_HEIGHT
131
+ separator = Svg::Line.new.tap do |l|
132
+ l.x1 = x
133
+ l.y1 = current_y
134
+ l.x2 = x + width
135
+ l.y2 = current_y
136
+ l.stroke = '#333333'
137
+ l.stroke_width = '1'
138
+ end
139
+ group.children << separator
140
+
141
+ # Render attributes
142
+ attributes = metadata[:attributes] || []
143
+ current_y += BOX_PADDING
144
+ attributes.each do |attr|
145
+ current_y = render_attribute(x, current_y, width, attr, group)
146
+ end
147
+ end
148
+
149
+ def render_attribute(x, y, _width, attribute, group)
150
+ # Build attribute text with key type marker
151
+ parts = []
152
+ parts << attribute[:key_type] if attribute[:key_type] &&
153
+ !attribute[:key_type].empty?
154
+ parts << attribute[:name]
155
+ parts << attribute[:attribute_type] if attribute[:attribute_type] &&
156
+ !attribute[:attribute_type]
157
+ .empty?
158
+
159
+ attr_text = parts.join(' ')
160
+
161
+ text = Svg::Text.new.tap do |t|
162
+ t.x = x + BOX_PADDING
163
+ t.y = y + ATTRIBUTE_FONT_SIZE
164
+ t.content = attr_text
165
+ t.fill = '#000000'
166
+ t.font_family = 'monospace'
167
+ t.font_size = ATTRIBUTE_FONT_SIZE.to_s
168
+ end
169
+ group.children << text
170
+
171
+ y + LINE_HEIGHT
172
+ end
173
+
174
+ def render_relationships(graph, svg)
175
+ graph[:edges].each do |edge|
176
+ render_relationship(edge, graph, svg)
177
+ end
178
+ end
179
+
180
+ def render_relationship(edge, graph, svg)
181
+ source = find_node(graph, edge[:sources]&.first)
182
+ target = find_node(graph, edge[:targets]&.first)
183
+
184
+ return unless source && target
185
+
186
+ metadata = edge[:metadata] || {}
187
+
188
+ # Create group for the relationship
189
+ group = Svg::Group.new.tap do |g|
190
+ g.id = "rel-#{edge[:id]}"
191
+ end
192
+
193
+ # Calculate connection points
194
+ source_point = calculate_connection_point(source, target)
195
+ target_point = calculate_connection_point(target, source)
196
+
197
+ # Render the line
198
+ rel_type = metadata[:relationship_type] || 'non-identifying'
199
+ render_relationship_line(
200
+ source_point,
201
+ target_point,
202
+ rel_type,
203
+ group
204
+ )
205
+
206
+ # Render cardinality markers
207
+ card_from = metadata[:cardinality_from]
208
+ card_to = metadata[:cardinality_to]
209
+
210
+ if card_from
211
+ render_cardinality(
212
+ source_point,
213
+ target_point,
214
+ card_from,
215
+ :source,
216
+ group
217
+ )
218
+ end
219
+ if card_to
220
+ render_cardinality(
221
+ target_point,
222
+ source_point,
223
+ card_to,
224
+ :target,
225
+ group
226
+ )
227
+ end
228
+
229
+ # Render label if present
230
+ render_relationship_label(edge, source_point, target_point, group)
231
+
232
+ svg << group
233
+ end
234
+
235
+ def find_node(graph, node_id)
236
+ return nil unless graph[:children] && node_id
237
+
238
+ graph[:children].find { |n| n[:id] == node_id }
239
+ end
240
+
241
+ def calculate_connection_point(from_node, to_node)
242
+ from_cx = (from_node[:x] || 0) + (from_node[:width] || 150) / 2
243
+ from_cy = (from_node[:y] || 0) + (from_node[:height] || 100) / 2
244
+ to_cx = (to_node[:x] || 0) + (to_node[:width] || 150) / 2
245
+ to_cy = (to_node[:y] || 0) + (to_node[:height] || 100) / 2
246
+
247
+ # Determine which edge of the box to connect to
248
+ from_x = from_node[:x] || 0
249
+ from_y = from_node[:y] || 0
250
+ from_w = from_node[:width] || 150
251
+ from_h = from_node[:height] || 100
252
+
253
+ # Calculate intersection with box edge
254
+ dx = to_cx - from_cx
255
+ dy = to_cy - from_cy
256
+
257
+ # Handle edge cases
258
+ return { x: from_cx, y: from_cy } if dx.abs < 0.001 && dy.abs < 0.001
259
+
260
+ # Find intersection point
261
+ if dx.abs > dy.abs
262
+ # Connect left/right edge
263
+ x = dx.positive? ? from_x + from_w : from_x
264
+ y = dy.abs < 0.001 ? from_cy : from_cy + (dy / dx) * (x - from_cx)
265
+ else
266
+ # Connect top/bottom edge
267
+ y = dy.positive? ? from_y + from_h : from_y
268
+ x = dx.abs < 0.001 ? from_cx : from_cx + (dx / dy) * (y - from_cy)
269
+ end
270
+
271
+ { x: x, y: y }
272
+ end
273
+
274
+ def render_relationship_line(from, to, rel_type, group)
275
+ line = Svg::Line.new.tap do |l|
276
+ l.x1 = from[:x]
277
+ l.y1 = from[:y]
278
+ l.x2 = to[:x]
279
+ l.y2 = to[:y]
280
+ l.stroke = '#333333'
281
+ l.stroke_width = '2'
282
+ l.stroke_dasharray = '5,5' if rel_type == 'non-identifying'
283
+ end
284
+ group.children << line
285
+ end
286
+
287
+ def render_cardinality(point, opposite_point, cardinality, _side, group)
288
+ case cardinality
289
+ when 'one'
290
+ render_one_marker(point, opposite_point, group)
291
+ when 'zero_or_more'
292
+ render_zero_or_more_marker(point, opposite_point, group)
293
+ when 'one_or_more'
294
+ render_one_or_more_marker(point, opposite_point, group)
295
+ when 'zero_or_one'
296
+ render_zero_or_one_marker(point, opposite_point, group)
297
+ end
298
+ end
299
+
300
+ def render_one_marker(point, opposite_point, group)
301
+ # Single perpendicular line (|)
302
+ dx = opposite_point[:x] - point[:x]
303
+ dy = opposite_point[:y] - point[:y]
304
+ angle = Math.atan2(dy, dx)
305
+
306
+ # Perpendicular angle
307
+ perp_angle = angle + Math::PI / 2
308
+
309
+ # Calculate perpendicular line endpoints
310
+ half_size = CARDINALITY_SIZE / 2
311
+ x1 = point[:x] + half_size * Math.cos(perp_angle)
312
+ y1 = point[:y] + half_size * Math.sin(perp_angle)
313
+ x2 = point[:x] - half_size * Math.cos(perp_angle)
314
+ y2 = point[:y] - half_size * Math.sin(perp_angle)
315
+
316
+ line = Svg::Line.new.tap do |l|
317
+ l.x1 = x1
318
+ l.y1 = y1
319
+ l.x2 = x2
320
+ l.y2 = y2
321
+ l.stroke = '#333333'
322
+ l.stroke_width = '2'
323
+ end
324
+ group.children << line
325
+ end
326
+
327
+ def render_zero_or_more_marker(point, opposite_point, group)
328
+ # Circle + crow's foot (o<)
329
+ render_circle_marker(point, opposite_point, group)
330
+ render_crows_foot(point, opposite_point, group)
331
+ end
332
+
333
+ def render_one_or_more_marker(point, opposite_point, group)
334
+ # Line + crow's foot (|<)
335
+ render_one_marker(point, opposite_point, group)
336
+ render_crows_foot(point, opposite_point, group)
337
+ end
338
+
339
+ def render_zero_or_one_marker(point, opposite_point, group)
340
+ # Circle + line (o|)
341
+ render_circle_marker(point, opposite_point, group)
342
+ render_one_marker(point, opposite_point, group)
343
+ end
344
+
345
+ def render_circle_marker(point, opposite_point, group)
346
+ dx = opposite_point[:x] - point[:x]
347
+ dy = opposite_point[:y] - point[:y]
348
+ angle = Math.atan2(dy, dx)
349
+
350
+ # Offset the circle along the line
351
+ offset = CARDINALITY_SIZE / 2
352
+ cx = point[:x] + offset * Math.cos(angle)
353
+ cy = point[:y] + offset * Math.sin(angle)
354
+
355
+ circle = Svg::Circle.new.tap do |c|
356
+ c.cx = cx
357
+ c.cy = cy
358
+ c.r = CARDINALITY_SIZE / 3
359
+ c.fill = 'none'
360
+ c.stroke = '#333333'
361
+ c.stroke_width = '2'
362
+ end
363
+ group.children << circle
364
+ end
365
+
366
+ def render_crows_foot(point, opposite_point, group)
367
+ # Three lines forming crow's foot (< shape)
368
+ dx = opposite_point[:x] - point[:x]
369
+ dy = opposite_point[:y] - point[:y]
370
+ angle = Math.atan2(dy, dx)
371
+
372
+ # Offset the crow's foot along the line
373
+ offset = CARDINALITY_SIZE
374
+ base_x = point[:x] + offset * Math.cos(angle)
375
+ base_y = point[:y] + offset * Math.sin(angle)
376
+
377
+ # Create three lines at angles
378
+ angles = [-Math::PI / 4, 0, Math::PI / 4]
379
+ angles.each do |angle_offset|
380
+ line_angle = angle + Math::PI + angle_offset
381
+ end_x = base_x + CARDINALITY_SIZE * Math.cos(line_angle)
382
+ end_y = base_y + CARDINALITY_SIZE * Math.sin(line_angle)
383
+
384
+ line = Svg::Line.new.tap do |l|
385
+ l.x1 = base_x
386
+ l.y1 = base_y
387
+ l.x2 = end_x
388
+ l.y2 = end_y
389
+ l.stroke = '#333333'
390
+ l.stroke_width = '2'
391
+ end
392
+ group.children << line
393
+ end
394
+ end
395
+
396
+ def render_relationship_label(edge, from, to, group)
397
+ labels = edge[:labels] || []
398
+ main_label = labels.find { |l| !l[:position] }
399
+ return unless main_label
400
+
401
+ mid_x = (from[:x] + to[:x]) / 2
402
+ mid_y = (from[:y] + to[:y]) / 2
403
+
404
+ text = Svg::Text.new.tap do |t|
405
+ t.x = mid_x
406
+ t.y = mid_y - 5
407
+ t.content = main_label[:text]
408
+ t.fill = '#000000'
409
+ t.font_family = 'Arial, sans-serif'
410
+ t.font_size = '11'
411
+ t.text_anchor = 'middle'
412
+ end
413
+ group.children << text
414
+ end
415
+ end
416
+ end
417
+ end
@@ -0,0 +1,131 @@
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/circle'
8
+ require_relative '../svg/path'
9
+
10
+ module Sirena
11
+ module Renderer
12
+ # Error diagram renderer for converting error diagrams to SVG.
13
+ #
14
+ # Renders an error message box with a warning icon and error text.
15
+ #
16
+ # @example Render an error diagram
17
+ # renderer = ErrorRenderer.new
18
+ # svg = renderer.render(error_diagram)
19
+ class ErrorRenderer < Base
20
+ # Error box dimensions
21
+ BOX_WIDTH = 400
22
+ BOX_HEIGHT = 120
23
+ BOX_X = 50
24
+ BOX_Y = 50
25
+ ICON_CENTER_X = 100
26
+ ICON_CENTER_Y = 110
27
+ ICON_RADIUS = 20
28
+ TEXT_X = 140
29
+ TEXT_Y = 105
30
+
31
+ # Renders an error diagram to SVG.
32
+ #
33
+ # @param graph [Hash] the error diagram graph structure from transform
34
+ # @return [Svg::Document] the rendered SVG document
35
+ def render(graph)
36
+ svg = create_document_for_error(graph)
37
+
38
+ # Render error box
39
+ render_error_box(graph, svg)
40
+
41
+ # Render error icon
42
+ render_error_icon(svg)
43
+
44
+ # Render error text
45
+ render_error_text(graph, svg)
46
+
47
+ svg
48
+ end
49
+
50
+ protected
51
+
52
+ def create_document_for_error(_graph)
53
+ width = 500
54
+ height = 220
55
+
56
+ Svg::Document.new.tap do |doc|
57
+ doc.width = width
58
+ doc.height = height
59
+ doc.view_box = "0 0 #{width} #{height}"
60
+ end
61
+ end
62
+
63
+ def render_error_box(_graph, svg)
64
+ box = Svg::Rect.new.tap do |r|
65
+ r.x = BOX_X
66
+ r.y = BOX_Y
67
+ r.width = BOX_WIDTH
68
+ r.height = BOX_HEIGHT
69
+ r.fill = '#FFEBEE'
70
+ r.stroke = '#D32F2F'
71
+ r.stroke_width = '2'
72
+ r.rx = '8'
73
+ r.ry = '8'
74
+ end
75
+
76
+ svg << box
77
+ end
78
+
79
+ def render_error_icon(svg)
80
+ # Error icon circle
81
+ circle = Svg::Circle.new.tap do |c|
82
+ c.cx = ICON_CENTER_X
83
+ c.cy = ICON_CENTER_Y
84
+ c.r = ICON_RADIUS
85
+ c.fill = '#D32F2F'
86
+ c.stroke = '#B71C1C'
87
+ c.stroke_width = '2'
88
+ end
89
+ svg << circle
90
+
91
+ # Exclamation mark - vertical line
92
+ line = Svg::Rect.new.tap do |r|
93
+ r.x = ICON_CENTER_X - 2
94
+ r.y = ICON_CENTER_Y - 10
95
+ r.width = 4
96
+ r.height = 12
97
+ r.fill = '#FFFFFF'
98
+ r.rx = '2'
99
+ end
100
+ svg << line
101
+
102
+ # Exclamation mark - dot
103
+ dot = Svg::Circle.new.tap do |c|
104
+ c.cx = ICON_CENTER_X
105
+ c.cy = ICON_CENTER_Y + 6
106
+ c.r = 2
107
+ c.fill = '#FFFFFF'
108
+ end
109
+ svg << dot
110
+ end
111
+
112
+ def render_error_text(graph, svg)
113
+ message = graph[:message] || 'Error'
114
+
115
+ text = Svg::Text.new.tap do |t|
116
+ t.x = TEXT_X
117
+ t.y = TEXT_Y
118
+ t.content = message
119
+ t.fill = '#C62828'
120
+ t.font_family = theme_typography(:font_family) ||
121
+ 'Arial, sans-serif'
122
+ t.font_size = (theme_typography(:font_size_base) || 16).to_s
123
+ t.text_anchor = 'start'
124
+ t.font_weight = 'bold'
125
+ end
126
+
127
+ svg << text
128
+ end
129
+ end
130
+ end
131
+ end