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,295 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../svg/document"
4
+ require_relative "../svg/rect"
5
+ require_relative "../svg/text"
6
+ require_relative "../svg/group"
7
+ require_relative "../svg/line"
8
+
9
+ module Sirena
10
+ module Renderer
11
+ # Renders a Kanban board layout to SVG.
12
+ #
13
+ # The renderer converts the positioned layout structure from
14
+ # Transform::Kanban into an SVG visualization showing:
15
+ # - Columns with headers
16
+ # - Cards stacked vertically within columns
17
+ # - Card metadata (assigned, ticket, priority, etc.)
18
+ # - Professional kanban board styling
19
+ #
20
+ # @example Render a kanban board
21
+ # renderer = Renderer::Kanban.new(theme: my_theme)
22
+ # svg = renderer.render(layout)
23
+ class Kanban < Base
24
+ # Renders the layout structure to SVG.
25
+ #
26
+ # @param layout [Hash] layout data from Transform::Kanban
27
+ # @return [Svg::Document] rendered SVG document
28
+ def render(layout)
29
+ svg = create_document_from_layout(layout)
30
+
31
+ # Render columns then cards
32
+ render_columns(layout, svg)
33
+ render_cards(layout, svg)
34
+
35
+ svg
36
+ end
37
+
38
+ protected
39
+
40
+ # Creates an SVG document with dimensions from layout.
41
+ #
42
+ # @param layout [Hash] layout data
43
+ # @return [Svg::Document] new SVG document
44
+ def create_document_from_layout(layout)
45
+ padding = 40
46
+
47
+ Svg::Document.new.tap do |doc|
48
+ doc.width = layout[:width] + (padding * 2)
49
+ doc.height = layout[:height] + (padding * 2)
50
+ doc.view_box = "0 0 #{doc.width} #{doc.height}"
51
+
52
+ # Add offset for padding
53
+ @offset_x = padding
54
+ @offset_y = padding
55
+ end
56
+ end
57
+
58
+ # Renders all columns
59
+ #
60
+ # @param layout [Hash] layout data
61
+ # @param svg [Svg::Document] SVG document
62
+ # @return [void]
63
+ def render_columns(layout, svg)
64
+ layout[:columns].each do |column|
65
+ render_column(column, svg)
66
+ end
67
+ end
68
+
69
+ # Renders a single column with header
70
+ #
71
+ # @param column [Hash] column data
72
+ # @param svg [Svg::Document] SVG document
73
+ # @return [void]
74
+ def render_column(column, svg)
75
+ x = column[:x] + @offset_x
76
+ y = column[:y] + @offset_y
77
+
78
+ # Column background
79
+ column_bg = Svg::Rect.new.tap do |r|
80
+ r.x = x
81
+ r.y = y
82
+ r.width = column[:width]
83
+ r.height = column[:height]
84
+ r.rx = 8
85
+ r.ry = 8
86
+ r.fill = theme_color(:background) || "#f3f4f6"
87
+ r.stroke = theme_color(:border) || "#d1d5db"
88
+ r.stroke_width = "1"
89
+ end
90
+
91
+ svg.add_element(column_bg)
92
+
93
+ # Column header
94
+ render_column_header(column, x, y, svg)
95
+ end
96
+
97
+ # Renders column header
98
+ #
99
+ # @param column [Hash] column data
100
+ # @param x [Numeric] X position
101
+ # @param y [Numeric] Y position
102
+ # @param svg [Svg::Document] SVG document
103
+ # @return [void]
104
+ def render_column_header(column, x, y, svg)
105
+ header_height = 50
106
+
107
+ # Header background
108
+ header_bg = Svg::Rect.new.tap do |r|
109
+ r.x = x
110
+ r.y = y
111
+ r.width = column[:width]
112
+ r.height = header_height
113
+ r.rx = 8
114
+ r.ry = 8
115
+ r.fill = theme_color(:primary) || "#3b82f6"
116
+ end
117
+
118
+ svg.add_element(header_bg)
119
+
120
+ # Header text
121
+ header_text = Svg::Text.new.tap do |t|
122
+ t.x = x + column[:width] / 2
123
+ t.y = y + header_height / 2 + 5
124
+ t.text_anchor = "middle"
125
+ t.fill = "#ffffff"
126
+ t.font_size = (theme_typography(:font_size) || 14).to_s
127
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
128
+ t.font_weight = "bold"
129
+ t.content = column[:title]
130
+ end
131
+
132
+ svg.add_element(header_text)
133
+
134
+ # Card count badge (optional)
135
+ if column[:card_count] > 0
136
+ badge_x = x + column[:width] - 25
137
+ badge_y = y + 15
138
+
139
+ badge_circle = Svg::Rect.new.tap do |r|
140
+ r.x = badge_x
141
+ r.y = badge_y
142
+ r.width = 20
143
+ r.height = 20
144
+ r.rx = 10
145
+ r.ry = 10
146
+ r.fill = "#ffffff"
147
+ r.opacity = "0.3"
148
+ end
149
+
150
+ svg.add_element(badge_circle)
151
+
152
+ badge_text = Svg::Text.new.tap do |t|
153
+ t.x = badge_x + 10
154
+ t.y = badge_y + 14
155
+ t.text_anchor = "middle"
156
+ t.fill = "#ffffff"
157
+ t.font_size = "11"
158
+ t.font_weight = "bold"
159
+ t.content = column[:card_count].to_s
160
+ end
161
+
162
+ svg.add_element(badge_text)
163
+ end
164
+ end
165
+
166
+ # Renders all cards
167
+ #
168
+ # @param layout [Hash] layout data
169
+ # @param svg [Svg::Document] SVG document
170
+ # @return [void]
171
+ def render_cards(layout, svg)
172
+ layout[:cards].each do |card|
173
+ render_card(card, svg)
174
+ end
175
+ end
176
+
177
+ # Renders a single card
178
+ #
179
+ # @param card [Hash] card data
180
+ # @param svg [Svg::Document] SVG document
181
+ # @return [void]
182
+ def render_card(card, svg)
183
+ x = card[:x] + @offset_x
184
+ y = card[:y] + @offset_y
185
+
186
+ # Card background
187
+ card_bg = Svg::Rect.new.tap do |r|
188
+ r.x = x
189
+ r.y = y
190
+ r.width = card[:width]
191
+ r.height = card[:height]
192
+ r.rx = 6
193
+ r.ry = 6
194
+ r.fill = "#ffffff"
195
+ r.stroke = theme_color(:border) || "#d1d5db"
196
+ r.stroke_width = "1"
197
+ end
198
+
199
+ svg.add_element(card_bg)
200
+
201
+ # Card text
202
+ render_card_text(card, x, y, svg)
203
+
204
+ # Metadata if present
205
+ if card[:has_metadata]
206
+ render_card_metadata(card, x, y, svg)
207
+ end
208
+ end
209
+
210
+ # Renders card text
211
+ #
212
+ # @param card [Hash] card data
213
+ # @param x [Numeric] X position
214
+ # @param y [Numeric] Y position
215
+ # @param svg [Svg::Document] SVG document
216
+ # @return [void]
217
+ def render_card_text(card, x, y, svg)
218
+ text_y = y + 25
219
+
220
+ text = Svg::Text.new.tap do |t|
221
+ t.x = x + 10
222
+ t.y = text_y
223
+ t.fill = theme_color(:text) || "#1f2937"
224
+ t.font_size = (theme_typography(:font_size) || 13).to_s
225
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
226
+ t.content = truncate_text(card[:text], 25)
227
+ end
228
+
229
+ svg.add_element(text)
230
+ end
231
+
232
+ # Renders card metadata
233
+ #
234
+ # @param card [Hash] card data
235
+ # @param x [Numeric] X position
236
+ # @param y [Numeric] Y position
237
+ # @param svg [Svg::Document] SVG document
238
+ # @return [void]
239
+ def render_card_metadata(card, x, y, svg)
240
+ metadata_y = y + 50
241
+ line_height = 18
242
+
243
+ card[:metadata].each_with_index do |(key, value), index|
244
+ next if value.nil? || value.to_s.empty?
245
+
246
+ current_y = metadata_y + (index * line_height)
247
+
248
+ # Metadata label
249
+ label = Svg::Text.new.tap do |t|
250
+ t.x = x + 10
251
+ t.y = current_y
252
+ t.fill = theme_color(:secondary) || "#6b7280"
253
+ t.font_size = "10"
254
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
255
+ t.content = "#{format_metadata_key(key)}:"
256
+ end
257
+
258
+ svg.add_element(label)
259
+
260
+ # Metadata value
261
+ value_text = Svg::Text.new.tap do |t|
262
+ t.x = x + 70
263
+ t.y = current_y
264
+ t.fill = theme_color(:text) || "#1f2937"
265
+ t.font_size = "10"
266
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
267
+ t.font_weight = "bold"
268
+ t.content = value.to_s
269
+ end
270
+
271
+ svg.add_element(value_text)
272
+ end
273
+ end
274
+
275
+ # Formats metadata key for display
276
+ #
277
+ # @param key [Symbol, String] metadata key
278
+ # @return [String] formatted key
279
+ def format_metadata_key(key)
280
+ key.to_s.capitalize
281
+ end
282
+
283
+ # Truncates text to a maximum length
284
+ #
285
+ # @param text [String] text to truncate
286
+ # @param max_length [Integer] maximum length
287
+ # @return [String] truncated text
288
+ def truncate_text(text, max_length)
289
+ return text if text.length <= max_length
290
+
291
+ "#{text[0...max_length - 3]}..."
292
+ end
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,396 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../svg/document"
4
+ require_relative "../svg/circle"
5
+ require_relative "../svg/rect"
6
+ require_relative "../svg/path"
7
+ require_relative "../svg/line"
8
+ require_relative "../svg/text"
9
+ require_relative "../svg/group"
10
+ require_relative "../svg/polygon"
11
+
12
+ module Sirena
13
+ module Renderer
14
+ # Renders a Mindmap layout to SVG.
15
+ #
16
+ # The renderer converts the positioned layout structure from
17
+ # Transform::Mindmap into an SVG visualization showing:
18
+ # - Nodes with different shapes (circle, cloud, hexagon, etc.)
19
+ # - Connections between parent and child nodes
20
+ # - Icons and custom classes
21
+ # - Level-based or branch-based coloring
22
+ #
23
+ # @example Render a mindmap
24
+ # renderer = Renderer::Mindmap.new(theme: my_theme)
25
+ # svg = renderer.render(layout)
26
+ class Mindmap < Base
27
+ # Renders the layout structure to SVG.
28
+ #
29
+ # @param layout [Hash] layout data from Transform::Mindmap
30
+ # @return [Svg::Document] rendered SVG document
31
+ def render(layout)
32
+ svg = create_document_from_layout(layout)
33
+
34
+ # Render in order: connections, then nodes, then labels
35
+ render_connections(layout, svg)
36
+ render_nodes(layout, svg)
37
+
38
+ svg
39
+ end
40
+
41
+ protected
42
+
43
+ # Creates an SVG document with dimensions from layout.
44
+ #
45
+ # @param layout [Hash] layout data
46
+ # @return [Svg::Document] new SVG document
47
+ def create_document_from_layout(layout)
48
+ padding = 40
49
+
50
+ Svg::Document.new.tap do |doc|
51
+ doc.width = layout[:width] + (padding * 2)
52
+ doc.height = layout[:height] + (padding * 2)
53
+ doc.view_box = "0 0 #{doc.width} #{doc.height}"
54
+
55
+ # Add a group with padding offset
56
+ @offset_x = padding
57
+ @offset_y = padding
58
+ end
59
+ end
60
+
61
+ # Renders all connections between nodes.
62
+ #
63
+ # @param layout [Hash] layout data
64
+ # @param svg [Svg::Document] SVG document
65
+ # @return [void]
66
+ def render_connections(layout, svg)
67
+ layout[:connections].each do |connection|
68
+ render_connection(connection, layout, svg)
69
+ end
70
+ end
71
+
72
+ # Renders a single connection line.
73
+ #
74
+ # @param connection [Hash] connection data
75
+ # @param layout [Hash] layout data
76
+ # @param svg [Svg::Document] SVG document
77
+ # @return [void]
78
+ def render_connection(connection, layout, svg)
79
+ from_node = layout[:nodes].find { |n| n[:id] == connection[:from] }
80
+ to_node = layout[:nodes].find { |n| n[:id] == connection[:to] }
81
+
82
+ return unless from_node && to_node
83
+
84
+ from_x = from_node[:x] + @offset_x
85
+ from_y = from_node[:y] + @offset_y + (from_node[:height] / 2)
86
+ to_x = to_node[:x] + @offset_x
87
+ to_y = to_node[:y] + @offset_y
88
+
89
+ # Use bezier curve for connections
90
+ control_y = from_y + (to_y - from_y) / 2
91
+
92
+ path_data = "M #{from_x} #{from_y} " \
93
+ "C #{from_x} #{control_y}, " \
94
+ "#{to_x} #{control_y}, " \
95
+ "#{to_x} #{to_y}"
96
+
97
+ color = get_node_color(from_node)
98
+
99
+ path = Svg::Path.new.tap do |p|
100
+ p.d = path_data
101
+ p.stroke = color
102
+ p.stroke_width = "2"
103
+ p.fill = "none"
104
+ end
105
+
106
+ svg.add_element(path)
107
+ end
108
+
109
+ # Renders all nodes with their shapes.
110
+ #
111
+ # @param layout [Hash] layout data
112
+ # @param svg [Svg::Document] SVG document
113
+ # @return [void]
114
+ def render_nodes(layout, svg)
115
+ layout[:nodes].each do |node|
116
+ render_node(node, svg)
117
+ end
118
+ end
119
+
120
+ # Renders a single node with appropriate shape.
121
+ #
122
+ # @param node [Hash] node data
123
+ # @param svg [Svg::Document] SVG document
124
+ # @return [void]
125
+ def render_node(node, svg)
126
+ x = node[:x] + @offset_x
127
+ y = node[:y] + @offset_y
128
+
129
+ case node[:shape]
130
+ when "circle"
131
+ render_circle_node(node, x, y, svg)
132
+ when "cloud"
133
+ render_cloud_node(node, x, y, svg)
134
+ when "bang"
135
+ render_bang_node(node, x, y, svg)
136
+ when "hexagon"
137
+ render_hexagon_node(node, x, y, svg)
138
+ when "square"
139
+ render_square_node(node, x, y, svg)
140
+ else
141
+ render_default_node(node, x, y, svg)
142
+ end
143
+ end
144
+
145
+ # Renders a default rounded rectangle node.
146
+ #
147
+ # @param node [Hash] node data
148
+ # @param x [Numeric] X position
149
+ # @param y [Numeric] Y position
150
+ # @param svg [Svg::Document] SVG document
151
+ # @return [void]
152
+ def render_default_node(node, x, y, svg)
153
+ width = node[:width]
154
+ height = node[:height]
155
+ color = get_node_color(node)
156
+
157
+ # Rounded rectangle background
158
+ rect = Svg::Rect.new.tap do |r|
159
+ r.x = x - width / 2
160
+ r.y = y
161
+ r.width = width
162
+ r.height = height
163
+ r.rx = 5
164
+ r.ry = 5
165
+ r.fill = lighten_color(color, 0.9)
166
+ r.stroke = color
167
+ r.stroke_width = "2"
168
+ end
169
+
170
+ svg.add_element(rect)
171
+
172
+ # Text
173
+ render_node_text(node, x, y + height / 2, svg)
174
+ end
175
+
176
+ # Renders a circle node.
177
+ #
178
+ # @param node [Hash] node data
179
+ # @param x [Numeric] X position
180
+ # @param y [Numeric] Y position
181
+ # @param svg [Svg::Document] SVG document
182
+ # @return [void]
183
+ def render_circle_node(node, x, y, svg)
184
+ radius = [node[:width], node[:height]].max / 2
185
+ color = get_node_color(node)
186
+
187
+ circle = Svg::Circle.new.tap do |c|
188
+ c.cx = x
189
+ c.cy = y + radius
190
+ c.r = radius
191
+ c.fill = lighten_color(color, 0.9)
192
+ c.stroke = color
193
+ c.stroke_width = "2"
194
+ end
195
+
196
+ svg.add_element(circle)
197
+
198
+ # Text
199
+ render_node_text(node, x, y + radius, svg)
200
+ end
201
+
202
+ # Renders a square node.
203
+ #
204
+ # @param node [Hash] node data
205
+ # @param x [Numeric] X position
206
+ # @param y [Numeric] Y position
207
+ # @param svg [Svg::Document] SVG document
208
+ # @return [void]
209
+ def render_square_node(node, x, y, svg)
210
+ width = node[:width]
211
+ height = node[:height]
212
+ color = get_node_color(node)
213
+
214
+ rect = Svg::Rect.new.tap do |r|
215
+ r.x = x - width / 2
216
+ r.y = y
217
+ r.width = width
218
+ r.height = height
219
+ r.fill = lighten_color(color, 0.9)
220
+ r.stroke = color
221
+ r.stroke_width = "2"
222
+ end
223
+
224
+ svg.add_element(rect)
225
+
226
+ # Text
227
+ render_node_text(node, x, y + height / 2, svg)
228
+ end
229
+
230
+ # Renders a hexagon node.
231
+ #
232
+ # @param node [Hash] node data
233
+ # @param x [Numeric] X position
234
+ # @param y [Numeric] Y position
235
+ # @param svg [Svg::Document] SVG document
236
+ # @return [void]
237
+ def render_hexagon_node(node, x, y, svg)
238
+ width = node[:width]
239
+ height = node[:height]
240
+ color = get_node_color(node)
241
+
242
+ # Hexagon points
243
+ offset = width * 0.2
244
+ points = [
245
+ [x - width / 2 + offset, y],
246
+ [x + width / 2 - offset, y],
247
+ [x + width / 2, y + height / 2],
248
+ [x + width / 2 - offset, y + height],
249
+ [x - width / 2 + offset, y + height],
250
+ [x - width / 2, y + height / 2]
251
+ ]
252
+
253
+ points_str = points.map { |p| p.join(",") }.join(" ")
254
+
255
+ polygon = Svg::Polygon.new.tap do |p|
256
+ p.points = points_str
257
+ p.fill = lighten_color(color, 0.9)
258
+ p.stroke = color
259
+ p.stroke_width = "2"
260
+ end
261
+
262
+ svg.add_element(polygon)
263
+
264
+ # Text
265
+ render_node_text(node, x, y + height / 2, svg)
266
+ end
267
+
268
+ # Renders a cloud node.
269
+ #
270
+ # @param node [Hash] node data
271
+ # @param x [Numeric] X position
272
+ # @param y [Numeric] Y position
273
+ # @param svg [Svg::Document] SVG document
274
+ # @return [void]
275
+ def render_cloud_node(node, x, y, svg)
276
+ width = node[:width]
277
+ height = node[:height]
278
+ color = get_node_color(node)
279
+
280
+ # Cloud shape using path
281
+ cloud_path = create_cloud_path(x, y, width, height)
282
+
283
+ path = Svg::Path.new.tap do |p|
284
+ p.d = cloud_path
285
+ p.fill = lighten_color(color, 0.9)
286
+ p.stroke = color
287
+ p.stroke_width = "2"
288
+ end
289
+
290
+ svg.add_element(path)
291
+
292
+ # Text
293
+ render_node_text(node, x, y + height / 2, svg)
294
+ end
295
+
296
+ # Renders a bang node (cloud with emphasis).
297
+ #
298
+ # @param node [Hash] node data
299
+ # @param x [Numeric] X position
300
+ # @param y [Numeric] Y position
301
+ # @param svg [Svg::Document] SVG document
302
+ # @return [void]
303
+ def render_bang_node(node, x, y, svg)
304
+ # Similar to cloud but with different styling
305
+ render_cloud_node(node, x, y, svg)
306
+ end
307
+
308
+ # Creates a cloud path.
309
+ #
310
+ # @param x [Numeric] center X
311
+ # @param y [Numeric] top Y
312
+ # @param width [Numeric] width
313
+ # @param height [Numeric] height
314
+ # @return [String] SVG path data
315
+ def create_cloud_path(x, y, width, height)
316
+ # Simplified cloud shape
317
+ w2 = width / 2
318
+ h = height
319
+
320
+ "M #{x - w2} #{y + h * 0.6} " \
321
+ "Q #{x - w2} #{y + h * 0.3}, #{x - w2 * 0.6} #{y + h * 0.2} " \
322
+ "Q #{x - w2 * 0.6} #{y}, #{x - w2 * 0.2} #{y + h * 0.1} " \
323
+ "Q #{x} #{y}, #{x + w2 * 0.2} #{y + h * 0.1} " \
324
+ "Q #{x + w2 * 0.6} #{y}, #{x + w2 * 0.6} #{y + h * 0.2} " \
325
+ "Q #{x + w2} #{y + h * 0.3}, #{x + w2} #{y + h * 0.6} " \
326
+ "Q #{x + w2} #{y + h}, #{x} #{y + h} " \
327
+ "Q #{x - w2} #{y + h}, #{x - w2} #{y + h * 0.6} Z"
328
+ end
329
+
330
+ # Renders text content for a node.
331
+ #
332
+ # @param node [Hash] node data
333
+ # @param x [Numeric] X position
334
+ # @param y [Numeric] Y position
335
+ # @param svg [Svg::Document] SVG document
336
+ # @return [void]
337
+ def render_node_text(node, x, y, svg)
338
+ return unless node[:content]
339
+
340
+ text = Svg::Text.new.tap do |t|
341
+ t.x = x
342
+ t.y = y + 5
343
+ t.text_anchor = "middle"
344
+ t.fill = theme_color(:text) || "#000000"
345
+ t.font_size = (theme_typography(:font_size) || 12).to_s
346
+ t.font_family = theme_typography(:font_family) || "Arial, sans-serif"
347
+ t.content = node[:content]
348
+ end
349
+
350
+ svg.add_element(text)
351
+ end
352
+
353
+ # Gets the color for a node based on its level.
354
+ #
355
+ # @param node [Hash] node data
356
+ # @return [String] color value
357
+ def get_node_color(node)
358
+ level = node[:level] || 0
359
+
360
+ colors = [
361
+ theme_color(:primary) || "#2563eb",
362
+ theme_color(:secondary) || "#7c3aed",
363
+ theme_color(:accent) || "#db2777",
364
+ "#ea580c",
365
+ "#16a34a",
366
+ "#0891b2"
367
+ ]
368
+
369
+ colors[level % colors.length]
370
+ end
371
+
372
+ # Lightens a color by a given factor.
373
+ #
374
+ # @param color [String] hex color
375
+ # @param factor [Float] lightening factor (0-1)
376
+ # @return [String] lightened hex color
377
+ def lighten_color(color, factor)
378
+ # Remove # if present
379
+ color = color.sub(/^#/, "")
380
+
381
+ # Parse RGB
382
+ r = color[0..1].to_i(16)
383
+ g = color[2..3].to_i(16)
384
+ b = color[4..5].to_i(16)
385
+
386
+ # Lighten
387
+ r = (r + (255 - r) * factor).round
388
+ g = (g + (255 - g) * factor).round
389
+ b = (b + (255 - b) * factor).round
390
+
391
+ # Return hex
392
+ format("#%02x%02x%02x", r, g, b)
393
+ end
394
+ end
395
+ end
396
+ end