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,152 @@
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/group'
8
+
9
+ module Sirena
10
+ module Renderer
11
+ # Renderer for treemap diagrams
12
+ class Treemap < Base
13
+ def render(layout)
14
+ doc = Svg::Document.new(
15
+ width: layout[:width],
16
+ height: layout[:height]
17
+ )
18
+
19
+ # Add title if present
20
+ if layout[:title]
21
+ add_title(doc, layout[:title], layout[:width])
22
+ end
23
+
24
+ # Render all cells
25
+ layout[:cells].each do |cell|
26
+ render_cell(doc, cell, layout[:class_defs])
27
+ end
28
+
29
+ doc.to_xml
30
+ end
31
+
32
+ private
33
+
34
+ def add_title(doc, title, width)
35
+ text = Svg::Text.new.tap do |t|
36
+ t.content = title
37
+ t.x = width / 2
38
+ t.y = 25
39
+ t.fill = theme_color(:label_text) || '#333'
40
+ t.font_family = theme_typography(:font_family) || 'Arial, sans-serif'
41
+ t.font_size = '18'
42
+ t.font_weight = 'bold'
43
+ t.text_anchor = 'middle'
44
+ end
45
+ doc << text
46
+ end
47
+
48
+ def render_cell(doc, cell, class_defs, parent_group = nil)
49
+ group = Svg::Group.new
50
+
51
+ # Determine fill color based on depth and CSS class
52
+ fill_color = cell_fill_color(cell, class_defs)
53
+ stroke_color = cell_stroke_color(cell, class_defs)
54
+
55
+ # Draw cell rectangle
56
+ rect = Svg::Rect.new.tap do |r|
57
+ r.x = cell[:x]
58
+ r.y = cell[:y]
59
+ r.width = cell[:width]
60
+ r.height = cell[:height]
61
+ r.fill = fill_color
62
+ r.stroke = stroke_color
63
+ r.stroke_width = '2'
64
+ r.rx = 4
65
+ r.ry = 4
66
+ end
67
+ group << rect
68
+
69
+ # Add label
70
+ label_y = cell[:y] + 15
71
+ label = Svg::Text.new.tap do |t|
72
+ t.content = truncate_label(cell[:label], cell[:width] - 10)
73
+ t.x = cell[:x] + 5
74
+ t.y = label_y
75
+ t.fill = theme_color(:label_text) || '#333'
76
+ t.font_family = theme_typography(:font_family) || 'Arial, sans-serif'
77
+ t.font_size = '12'
78
+ t.font_weight = 'bold'
79
+ end
80
+ group << label
81
+
82
+ # Add value if it's a leaf
83
+ if cell[:value] && cell[:children].empty?
84
+ value_text = format_value(cell[:value])
85
+ value_y = label_y + 15
86
+ value_label = Svg::Text.new.tap do |t|
87
+ t.content = value_text
88
+ t.x = cell[:x] + 5
89
+ t.y = value_y
90
+ t.fill = theme_color(:label_text) || '#666'
91
+ t.font_family = theme_typography(:font_family) || 'Arial, sans-serif'
92
+ t.font_size = '10'
93
+ end
94
+ group << value_label
95
+ end
96
+
97
+ # Render children recursively
98
+ cell[:children].each do |child|
99
+ render_cell(group, child, class_defs, group)
100
+ end
101
+
102
+ if parent_group
103
+ parent_group << group
104
+ else
105
+ doc << group
106
+ end
107
+ end
108
+
109
+ def cell_fill_color(cell, class_defs)
110
+ # If cell has a CSS class, try to extract fill from class_defs
111
+ if cell[:css_class] && class_defs[cell[:css_class]]
112
+ styles = class_defs[cell[:css_class]]
113
+ if styles =~ /fill:\s*([^;,]+)/
114
+ return $1.strip
115
+ end
116
+ end
117
+
118
+ # Otherwise use default colors based on depth
119
+ depth_colors = %w[#8dd3c7 #ffffb3 #bebada #fb8072]
120
+ depth_colors[cell[:depth] % depth_colors.length]
121
+ end
122
+
123
+ def cell_stroke_color(cell, class_defs)
124
+ # If cell has a CSS class, try to extract stroke from class_defs
125
+ if cell[:css_class] && class_defs[cell[:css_class]]
126
+ styles = class_defs[cell[:css_class]]
127
+ if styles =~ /stroke:\s*([^;,]+)/
128
+ return $1.strip
129
+ end
130
+ end
131
+
132
+ theme_color(:node_stroke) || '#333'
133
+ end
134
+
135
+ def truncate_label(label, max_width)
136
+ # Simple truncation - could be improved with actual text measurement
137
+ max_chars = (max_width / 7).to_i
138
+ return label if label.length <= max_chars
139
+
140
+ "#{label[0...max_chars - 3]}..."
141
+ end
142
+
143
+ def format_value(value)
144
+ if value == value.to_i
145
+ value.to_i.to_s
146
+ else
147
+ format('%.1f', value)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,331 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Sirena
6
+ module Renderer
7
+ # User Journey 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 journey title, section
11
+ # swimlanes, task boxes with score-based colors, actor lists, and
12
+ # sequential timeline flow.
13
+ #
14
+ # @example Render a user journey
15
+ # renderer = UserJourneyRenderer.new
16
+ # svg = renderer.render(laid_out_graph)
17
+ class UserJourneyRenderer < Base
18
+ # Font size for title
19
+ TITLE_FONT_SIZE = 20
20
+
21
+ # Font size for section headers
22
+ SECTION_FONT_SIZE = 16
23
+
24
+ # Font size for task names
25
+ TASK_NAME_FONT_SIZE = 14
26
+
27
+ # Font size for score
28
+ SCORE_FONT_SIZE = 18
29
+
30
+ # Font size for actors
31
+ ACTOR_FONT_SIZE = 11
32
+
33
+ # Padding for various elements
34
+ TITLE_PADDING = 20
35
+ SECTION_PADDING = 10
36
+ TASK_PADDING = 10
37
+
38
+ # Score-based color mapping
39
+ SCORE_COLORS = {
40
+ red: '#ff6b6b',
41
+ yellow: '#feca57',
42
+ green: '#48dbfb'
43
+ }.freeze
44
+
45
+ # Renders a laid-out graph to SVG.
46
+ #
47
+ # @param graph [Hash] laid-out graph with node positions
48
+ # @return [Svg::Document] the rendered SVG document
49
+ def render(graph)
50
+ svg = create_document(graph)
51
+
52
+ metadata = graph[:metadata] || {}
53
+
54
+ # Render title if present
55
+ current_y = TITLE_PADDING
56
+ current_y = render_title(svg, metadata[:title], current_y) if metadata[:title] && !metadata[:title].empty?
57
+
58
+ # Render section headers and tasks
59
+ render_sections_and_tasks(svg, graph, current_y)
60
+
61
+ # Render timeline connections
62
+ render_timeline(graph, svg) if graph[:edges]
63
+
64
+ svg
65
+ end
66
+
67
+ protected
68
+
69
+ def calculate_width(graph)
70
+ return 800 unless graph[:children]
71
+
72
+ max_x = graph[:children].map do |node|
73
+ (node[:x] || 0) + (node[:width] || 120)
74
+ end.max || 800
75
+
76
+ max_x + 40
77
+ end
78
+
79
+ def calculate_height(graph)
80
+ return 600 unless graph[:children]
81
+
82
+ max_y = graph[:children].map do |node|
83
+ (node[:y] || 0) + (node[:height] || 80)
84
+ end.max || 600
85
+
86
+ # Add space for title and sections
87
+ metadata = graph[:metadata] || {}
88
+ title_height = if metadata[:title]
89
+ TITLE_FONT_SIZE +
90
+ TITLE_PADDING * 2
91
+ else
92
+ 0
93
+ end
94
+ max_y + title_height + 60
95
+ end
96
+
97
+ def render_title(svg, title, y_pos)
98
+ text = Svg::Text.new.tap do |t|
99
+ t.x = 20
100
+ t.y = y_pos + TITLE_FONT_SIZE
101
+ t.content = title
102
+ t.fill = '#000000'
103
+ t.font_family = 'Arial, sans-serif'
104
+ t.font_size = TITLE_FONT_SIZE.to_s
105
+ t.font_weight = 'bold'
106
+ end
107
+ svg << text
108
+
109
+ y_pos + TITLE_FONT_SIZE + TITLE_PADDING
110
+ end
111
+
112
+ def render_sections_and_tasks(svg, graph, start_y)
113
+ return start_y unless graph[:children]
114
+
115
+ # Group tasks by section
116
+ sections = group_tasks_by_section(graph[:children])
117
+
118
+ current_y = start_y
119
+
120
+ sections.each do |section_name, tasks|
121
+ # Render section header
122
+ current_y = render_section_header(svg, section_name, current_y)
123
+
124
+ # Render tasks in this section
125
+ tasks.each do |task|
126
+ render_task(svg, task)
127
+ end
128
+
129
+ current_y += 20
130
+ end
131
+
132
+ current_y
133
+ end
134
+
135
+ def group_tasks_by_section(nodes)
136
+ grouped = {}
137
+
138
+ nodes.each do |node|
139
+ metadata = node[:metadata] || {}
140
+ section_name = metadata[:section_name] || 'Default'
141
+
142
+ grouped[section_name] ||= []
143
+ grouped[section_name] << node
144
+ end
145
+
146
+ grouped
147
+ end
148
+
149
+ def render_section_header(svg, section_name, y_pos)
150
+ text = Svg::Text.new.tap do |t|
151
+ t.x = 20
152
+ t.y = y_pos + SECTION_FONT_SIZE
153
+ t.content = section_name
154
+ t.fill = '#666666'
155
+ t.font_family = 'Arial, sans-serif'
156
+ t.font_size = SECTION_FONT_SIZE.to_s
157
+ t.font_weight = 'bold'
158
+ end
159
+ svg << text
160
+
161
+ y_pos + SECTION_FONT_SIZE + SECTION_PADDING
162
+ end
163
+
164
+ def render_task(svg, node)
165
+ x = node[:x] || 0
166
+ y = node[:y] || 0
167
+ width = node[:width] || 120
168
+ height = node[:height] || 80
169
+
170
+ metadata = node[:metadata] || {}
171
+ score_color = metadata[:score_color] || :yellow
172
+
173
+ # Create group for the task
174
+ group = Svg::Group.new.tap do |g|
175
+ g.id = "task-#{node[:id]}"
176
+ end
177
+
178
+ # Render task box with score-based color
179
+ box = Svg::Rect.new.tap do |r|
180
+ r.x = x
181
+ r.y = y
182
+ r.width = width
183
+ r.height = height
184
+ r.fill = SCORE_COLORS[score_color]
185
+ r.stroke = '#333333'
186
+ r.stroke_width = '2'
187
+ r.rx = '5'
188
+ r.ry = '5'
189
+ end
190
+ group.children << box
191
+
192
+ # Render task content
193
+ render_task_content(node, metadata, group)
194
+
195
+ svg << group
196
+ end
197
+
198
+ def render_task_content(node, metadata, group)
199
+ x = node[:x] || 0
200
+ y = node[:y] || 0
201
+ width = node[:width] || 120
202
+ node[:height] || 80
203
+
204
+ current_y = y + TASK_PADDING
205
+
206
+ # Render task name at top
207
+ name = metadata[:name] || 'Task'
208
+ name_text = Svg::Text.new.tap do |t|
209
+ t.x = x + width / 2
210
+ t.y = current_y + TASK_NAME_FONT_SIZE
211
+ t.content = name
212
+ t.fill = '#000000'
213
+ t.font_family = 'Arial, sans-serif'
214
+ t.font_size = TASK_NAME_FONT_SIZE.to_s
215
+ t.text_anchor = 'middle'
216
+ t.font_weight = 'bold'
217
+ end
218
+ group.children << name_text
219
+
220
+ current_y += TASK_NAME_FONT_SIZE + 10
221
+
222
+ # Render score in center
223
+ score = metadata[:score] || 3
224
+ score_text = Svg::Text.new.tap do |t|
225
+ t.x = x + width / 2
226
+ t.y = current_y + SCORE_FONT_SIZE
227
+ t.content = score.to_s
228
+ t.fill = '#000000'
229
+ t.font_family = 'Arial, sans-serif'
230
+ t.font_size = SCORE_FONT_SIZE.to_s
231
+ t.text_anchor = 'middle'
232
+ t.font_weight = 'bold'
233
+ end
234
+ group.children << score_text
235
+
236
+ current_y += SCORE_FONT_SIZE + 10
237
+
238
+ # Render actors at bottom
239
+ actors = metadata[:actors] || []
240
+ actors_text = actors.join(', ')
241
+ actor_text = Svg::Text.new.tap do |t|
242
+ t.x = x + width / 2
243
+ t.y = current_y + ACTOR_FONT_SIZE
244
+ t.content = actors_text
245
+ t.fill = '#333333'
246
+ t.font_family = 'Arial, sans-serif'
247
+ t.font_size = ACTOR_FONT_SIZE.to_s
248
+ t.text_anchor = 'middle'
249
+ end
250
+ group.children << actor_text
251
+ end
252
+
253
+ def render_timeline(graph, svg)
254
+ return unless graph[:edges]
255
+
256
+ graph[:edges].each do |edge|
257
+ render_timeline_arrow(edge, graph, svg)
258
+ end
259
+ end
260
+
261
+ def render_timeline_arrow(edge, graph, svg)
262
+ source = find_node(graph, edge[:sources]&.first)
263
+ target = find_node(graph, edge[:targets]&.first)
264
+
265
+ return unless source && target
266
+
267
+ # Create arrow group
268
+ group = Svg::Group.new.tap do |g|
269
+ g.id = "arrow-#{edge[:id]}"
270
+ end
271
+
272
+ # Calculate connection points (right side of source to
273
+ # left side of target)
274
+ from_x = (source[:x] || 0) + (source[:width] || 120)
275
+ from_y = (source[:y] || 0) + (source[:height] || 80) / 2
276
+ to_x = target[:x] || 0
277
+ to_y = (target[:y] || 0) + (target[:height] || 80) / 2
278
+
279
+ # Render line
280
+ line = Svg::Line.new.tap do |l|
281
+ l.x1 = from_x
282
+ l.y1 = from_y
283
+ l.x2 = to_x
284
+ l.y2 = to_y
285
+ l.stroke = '#666666'
286
+ l.stroke_width = '2'
287
+ end
288
+ group.children << line
289
+
290
+ # Render arrowhead
291
+ render_arrowhead(to_x, to_y, from_x, from_y, group)
292
+
293
+ svg << group
294
+ end
295
+
296
+ def render_arrowhead(to_x, to_y, from_x, from_y, group)
297
+ # Calculate angle
298
+ dx = to_x - from_x
299
+ dy = to_y - from_y
300
+ angle = Math.atan2(dy, dx)
301
+
302
+ # Arrow size
303
+ arrow_length = 10
304
+
305
+ # Calculate arrowhead points
306
+ point1_x = to_x - arrow_length * Math.cos(angle - Math::PI / 6)
307
+ point1_y = to_y - arrow_length * Math.sin(angle - Math::PI / 6)
308
+ point2_x = to_x - arrow_length * Math.cos(angle + Math::PI / 6)
309
+ point2_y = to_y - arrow_length * Math.sin(angle + Math::PI / 6)
310
+
311
+ # Create arrowhead path
312
+ path_d = "M #{to_x},#{to_y} L #{point1_x},#{point1_y} " \
313
+ "M #{to_x},#{to_y} L #{point2_x},#{point2_y}"
314
+
315
+ path = Svg::Path.new.tap do |p|
316
+ p.d = path_d
317
+ p.stroke = '#666666'
318
+ p.stroke_width = '2'
319
+ p.fill = 'none'
320
+ end
321
+ group.children << path
322
+ end
323
+
324
+ def find_node(graph, node_id)
325
+ return nil unless graph[:children] && node_id
326
+
327
+ graph[:children].find { |n| n[:id] == node_id }
328
+ end
329
+ end
330
+ end
331
+ end