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,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Sirena
6
+ module Diagram
7
+ # Represents a treemap diagram showing hierarchical data
8
+ class TreemapDiagram < Base
9
+ attr_accessor :title, :root_nodes, :class_defs
10
+
11
+ def initialize
12
+ super
13
+ @root_nodes = []
14
+ @class_defs = {}
15
+ end
16
+
17
+ def type
18
+ :treemap
19
+ end
20
+
21
+ # Add a root-level node
22
+ def add_root_node(node)
23
+ @root_nodes << node
24
+ end
25
+
26
+ # Add a class definition
27
+ def add_class_def(name, styles)
28
+ @class_defs[name] = styles
29
+ end
30
+
31
+ # Calculate total value of all nodes
32
+ def total_value
33
+ @root_nodes.sum(&:total_value)
34
+ end
35
+ end
36
+
37
+ # Represents a node in a treemap (can be a branch or leaf)
38
+ class TreemapNode
39
+ attr_accessor :label, :value, :children, :css_class, :parent
40
+
41
+ def initialize(label, value = nil)
42
+ @label = label
43
+ @value = value&.to_f
44
+ @children = []
45
+ @css_class = nil
46
+ @parent = nil
47
+ end
48
+
49
+ # Add a child node
50
+ def add_child(node)
51
+ node.parent = self
52
+ @children << node
53
+ end
54
+
55
+ # Check if this is a leaf node (has a value)
56
+ def leaf?
57
+ !@value.nil?
58
+ end
59
+
60
+ # Check if this is a branch node (has children)
61
+ def branch?
62
+ !@children.empty?
63
+ end
64
+
65
+ # Calculate total value including children
66
+ def total_value
67
+ if leaf?
68
+ @value
69
+ elsif branch?
70
+ @children.sum(&:total_value)
71
+ else
72
+ 0.0
73
+ end
74
+ end
75
+
76
+ # Get the depth level of this node (root = 0)
77
+ def depth
78
+ return 0 unless @parent
79
+
80
+ 1 + @parent.depth
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+ require_relative 'base'
5
+
6
+ module Sirena
7
+ module Diagram
8
+ # Represents a task in a user journey.
9
+ #
10
+ # A task has a name, score (1-5 indicating satisfaction), and a
11
+ # collection of actors involved in performing the task.
12
+ class JourneyTask < Lutaml::Model::Serializable
13
+ # Task name/description
14
+ attribute :name, :string
15
+
16
+ # Task satisfaction score (1-5 range)
17
+ attribute :score, :integer
18
+
19
+ # Collection of actor names involved in this task
20
+ attribute :actors, :string, collection: true, default: -> { [] }
21
+
22
+ # Validates the task has required fields.
23
+ #
24
+ # @return [Boolean] true if task is valid
25
+ def valid?
26
+ !name.nil? && !name.empty? &&
27
+ !score.nil? &&
28
+ score >= 1 && score <= 5 &&
29
+ !actors.nil? && !actors.empty?
30
+ end
31
+
32
+ # Returns the color for this task based on score.
33
+ #
34
+ # @return [String] color indicator (:red, :yellow, :green)
35
+ def score_color
36
+ case score
37
+ when 1..2
38
+ :red
39
+ when 3
40
+ :yellow
41
+ when 4..5
42
+ :green
43
+ else
44
+ :yellow
45
+ end
46
+ end
47
+ end
48
+
49
+ # Represents a section grouping in a user journey.
50
+ #
51
+ # A section groups related tasks together, representing a phase
52
+ # or stage in the user journey.
53
+ class JourneySection < Lutaml::Model::Serializable
54
+ # Section name/title
55
+ attribute :name, :string
56
+
57
+ # Collection of tasks in this section
58
+ attribute :tasks, JourneyTask, collection: true,
59
+ default: -> { [] }
60
+
61
+ # Validates the section has required fields.
62
+ #
63
+ # @return [Boolean] true if section is valid
64
+ def valid?
65
+ !name.nil? && !name.empty? && tasks.all?(&:valid?)
66
+ end
67
+ end
68
+
69
+ # User Journey diagram model.
70
+ #
71
+ # Represents a complete user journey diagram showing the tasks users
72
+ # perform, grouped into sections, with satisfaction scores and actor
73
+ # involvement.
74
+ #
75
+ # @example Creating a simple user journey
76
+ # diagram = UserJourney.new
77
+ # diagram.title = "Customer Shopping Journey"
78
+ # section = JourneySection.new.tap do |s|
79
+ # s.name = "Shopping"
80
+ # end
81
+ # task = JourneyTask.new.tap do |t|
82
+ # t.name = "Browse products"
83
+ # t.score = 5
84
+ # t.actors = ["Customer"]
85
+ # end
86
+ # section.tasks << task
87
+ # diagram.sections << section
88
+ class UserJourney < Base
89
+ # Optional diagram title
90
+ attribute :title, :string
91
+
92
+ # Collection of journey sections
93
+ attribute :sections, JourneySection, collection: true,
94
+ default: -> { [] }
95
+
96
+ # Returns the diagram type identifier.
97
+ #
98
+ # @return [Symbol] :user_journey
99
+ def diagram_type
100
+ :user_journey
101
+ end
102
+
103
+ # Validates the user journey structure.
104
+ #
105
+ # A user journey is valid if:
106
+ # - It has at least one section
107
+ # - All sections are valid
108
+ # - All tasks within sections are valid
109
+ #
110
+ # @return [Boolean] true if user journey is valid
111
+ def valid?
112
+ return false if sections.nil? || sections.empty?
113
+ return false unless sections.all?(&:valid?)
114
+
115
+ true
116
+ end
117
+
118
+ # Returns all tasks across all sections.
119
+ #
120
+ # @return [Array<JourneyTask>] all tasks in the journey
121
+ def all_tasks
122
+ sections.flat_map(&:tasks)
123
+ end
124
+
125
+ # Returns all unique actors involved in the journey.
126
+ #
127
+ # @return [Array<String>] unique actor names
128
+ def all_actors
129
+ all_tasks.flat_map(&:actors).uniq
130
+ end
131
+
132
+ # Finds tasks by score.
133
+ #
134
+ # @param score [Integer] the score to filter by (1-5)
135
+ # @return [Array<JourneyTask>] tasks with the given score
136
+ def tasks_by_score(score)
137
+ all_tasks.select { |t| t.score == score }
138
+ end
139
+
140
+ # Finds tasks by actor.
141
+ #
142
+ # @param actor [String] the actor name
143
+ # @return [Array<JourneyTask>] tasks involving the actor
144
+ def tasks_by_actor(actor)
145
+ all_tasks.select { |t| t.actors.include?(actor) }
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sirena
4
+ module Diagram
5
+ # Represents an XY chart diagram (scatter/line/bar chart)
6
+ class XYChart < Base
7
+ attr_accessor :title, :x_axis, :y_axis, :datasets, :options
8
+
9
+ def initialize
10
+ super
11
+ @datasets = []
12
+ @options = {}
13
+ end
14
+
15
+ def type
16
+ :xychart
17
+ end
18
+
19
+ # Add a dataset to the chart
20
+ def add_dataset(dataset)
21
+ @datasets << dataset
22
+ end
23
+ end
24
+
25
+ # Represents an axis configuration
26
+ class XYAxis
27
+ attr_accessor :label, :values, :min, :max, :type
28
+
29
+ def initialize
30
+ @type = :numeric # :numeric or :categorical
31
+ @values = []
32
+ end
33
+
34
+ # Check if axis has categorical values
35
+ def categorical?
36
+ @type == :categorical
37
+ end
38
+
39
+ # Check if axis has numeric range
40
+ def numeric?
41
+ @type == :numeric
42
+ end
43
+
44
+ # Get the range of the axis
45
+ def range
46
+ if numeric?
47
+ [@min || 0, @max || 100]
48
+ else
49
+ [0, values.length - 1]
50
+ end
51
+ end
52
+ end
53
+
54
+ # Represents a dataset in an XY chart
55
+ class XYDataset
56
+ attr_accessor :id, :label, :chart_type, :values, :color
57
+
58
+ def initialize(id, label = nil, chart_type = :line)
59
+ @id = id
60
+ @label = label || id
61
+ @chart_type = chart_type # :line, :bar, :scatter
62
+ @values = []
63
+ end
64
+
65
+ # Add a value to the dataset
66
+ def add_value(value)
67
+ @values << value.to_f
68
+ end
69
+
70
+ # Set all values at once
71
+ def values=(vals)
72
+ @values = vals.map(&:to_f)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'diagram/base'
4
+
5
+ module Sirena
6
+ module Diagram
7
+ end
8
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sirena
4
+ # Registry for diagram type handlers.
5
+ #
6
+ # This class implements the registry pattern to manage diagram type
7
+ # handlers without hardcoding type checks. Each diagram type registers
8
+ # its parser, transform, and renderer components, which can be retrieved
9
+ # dynamically during diagram processing.
10
+ #
11
+ # @example Registering a diagram type
12
+ # DiagramRegistry.register(
13
+ # :flowchart,
14
+ # parser: Parser::FlowchartGrammar,
15
+ # transform: Transform::FlowchartTransform,
16
+ # renderer: Renderer::FlowchartRenderer
17
+ # )
18
+ #
19
+ # @example Retrieving handlers for a type
20
+ # handlers = DiagramRegistry.get(:flowchart)
21
+ # # => { parser: Parser::FlowchartGrammar,
22
+ # # transform: Transform::FlowchartTransform,
23
+ # # renderer: Renderer::FlowchartRenderer }
24
+ #
25
+ # @example Listing registered types
26
+ # DiagramRegistry.types
27
+ # # => [:flowchart, :sequence, :class_diagram]
28
+ class DiagramRegistry
29
+ @handlers = {}
30
+
31
+ class << self
32
+ # Registers handlers for a diagram type.
33
+ #
34
+ # @param type [Symbol] the diagram type identifier
35
+ # @param parser [Class] the parser class for this diagram type
36
+ # @param transform [Class] the transform class for this diagram type
37
+ # @param renderer [Class] the renderer class for this diagram type
38
+ # @return [Hash] the registered handler hash
39
+ #
40
+ # @example Register a new diagram type
41
+ # DiagramRegistry.register(
42
+ # :flowchart,
43
+ # parser: Parser::FlowchartGrammar,
44
+ # transform: Transform::FlowchartTransform,
45
+ # renderer: Renderer::FlowchartRenderer
46
+ # )
47
+ def register(type, parser:, transform:, renderer:)
48
+ @handlers[type] = {
49
+ parser: parser,
50
+ transform: transform,
51
+ renderer: renderer
52
+ }
53
+ end
54
+
55
+ # Retrieves handlers for a diagram type.
56
+ #
57
+ # @param type [Symbol] the diagram type identifier
58
+ # @return [Hash, nil] hash with :parser, :transform, and :renderer
59
+ # keys, or nil if type not registered
60
+ #
61
+ # @example Get handlers for a type
62
+ # handlers = DiagramRegistry.get(:flowchart)
63
+ # parser_class = handlers[:parser]
64
+ def get(type)
65
+ @handlers[type]
66
+ end
67
+
68
+ # Returns all registered diagram types.
69
+ #
70
+ # @return [Array<Symbol>] list of registered diagram type identifiers
71
+ #
72
+ # @example List all types
73
+ # DiagramRegistry.types
74
+ # # => [:flowchart, :sequence, :class_diagram]
75
+ def types
76
+ @handlers.keys
77
+ end
78
+
79
+ # Checks if a diagram type is registered.
80
+ #
81
+ # @param type [Symbol] the diagram type identifier
82
+ # @return [Boolean] true if type is registered
83
+ #
84
+ # @example Check if type is registered
85
+ # DiagramRegistry.registered?(:flowchart)
86
+ # # => true
87
+ def registered?(type)
88
+ @handlers.key?(type)
89
+ end
90
+
91
+ # Clears all registered handlers.
92
+ #
93
+ # This method is primarily useful for testing purposes.
94
+ #
95
+ # @return [Hash] empty handler hash
96
+ def clear
97
+ @handlers = {}
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,292 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sirena
4
+ # Orchestrates the complete diagram rendering pipeline.
5
+ #
6
+ # The Engine class coordinates the parse → transform → layout → render
7
+ # pipeline for converting Mermaid source code into SVG output. It handles
8
+ # diagram type detection, retrieves appropriate handlers from the registry,
9
+ # and manages error handling throughout the process.
10
+ #
11
+ # @example Render a flowchart
12
+ # engine = Sirena::Engine.new
13
+ # svg = engine.render("graph TD\nA-->B")
14
+ # puts svg
15
+ #
16
+ # @example Render with options
17
+ # engine = Sirena::Engine.new
18
+ # svg = engine.render(source, verbose: true)
19
+ class Engine
20
+ # Error raised when diagram type cannot be detected
21
+ class DiagramTypeError < Error; end
22
+
23
+ # Error raised during pipeline execution
24
+ class PipelineError < Error; end
25
+
26
+ # Mapping of diagram syntax prefixes to diagram types
27
+ DIAGRAM_TYPE_PATTERNS = {
28
+ flowchart: /\A\s*(graph|flowchart)\s+/i,
29
+ sequence: /\A\s*sequenceDiagram/i,
30
+ class_diagram: /\A\s*classDiagram/i,
31
+ state_diagram: /\A\s*stateDiagram(-v2)?/i,
32
+ er_diagram: /\A\s*erDiagram/i,
33
+ user_journey: /\A\s*journey/i,
34
+ gantt: /\A\s*gantt\s/i,
35
+ pie: /\A\s*pie\s/i,
36
+ timeline: /\A\s*timeline(\s|$)/i,
37
+ quadrant: /\A\s*quadrantChart/i,
38
+ git_graph: /\A\s*gitGraph/i,
39
+ mindmap: /\A\s*mindmap/i,
40
+ kanban: /\A\s*kanban/i,
41
+ radar: /\A\s*radar-beta/i,
42
+ block: /\A\s*block-beta/i,
43
+ requirement: /\A\s*requirementDiagram/i,
44
+ xychart: /\A\s*xychart-beta/i,
45
+ architecture: /\A\s*architecture-beta/i,
46
+ sankey: /\A\s*sankey-beta/i,
47
+ packet: /\A\s*packet-beta/i,
48
+ treemap: /\A\s*treemap(-beta)?/i,
49
+ c4: /\A\s*(C4Context|C4Container|C4Component|C4Dynamic|C4Deployment|C4\s+diagram)/i,
50
+ info: /\A\s*info/i,
51
+ error: /\A\s*(error|Error)/i
52
+ }.freeze
53
+
54
+ attr_reader :verbose, :theme
55
+
56
+ # Creates a new Engine instance.
57
+ #
58
+ # @param verbose [Boolean] enable verbose output for debugging
59
+ # @param theme [String, Theme, Hash, nil] theme specification
60
+ def initialize(verbose: false, theme: nil)
61
+ @verbose = verbose
62
+ @theme = load_theme(theme)
63
+ end
64
+
65
+ # Renders Mermaid source code to SVG.
66
+ #
67
+ # @param mermaid_source [String] Mermaid diagram source code
68
+ # @param options [Hash] rendering options
69
+ # @option options [Boolean] :verbose enable verbose output
70
+ # @option options [String, Theme, Hash, nil] :theme theme override
71
+ # @return [String] SVG XML string
72
+ # @raise [DiagramTypeError] if diagram type cannot be detected
73
+ # @raise [PipelineError] if any pipeline stage fails
74
+ def render(mermaid_source, options = {})
75
+ @verbose = options[:verbose] if options.key?(:verbose)
76
+
77
+ # Override theme if specified in options
78
+ theme = options[:theme] ? load_theme(options[:theme]) : @theme
79
+
80
+ log 'Starting render pipeline...'
81
+
82
+ # Detect diagram type
83
+ diagram_type = detect_diagram_type(mermaid_source)
84
+ log "Detected diagram type: #{diagram_type}"
85
+
86
+ # Retrieve handlers
87
+ handlers = retrieve_handlers(diagram_type)
88
+ log "Retrieved handlers for #{diagram_type}"
89
+
90
+ # Execute pipeline
91
+ diagram = parse_diagram(mermaid_source, handlers[:parser])
92
+ graph = transform_diagram(diagram, handlers[:transform])
93
+ laid_out_graph = layout_graph(graph)
94
+ svg_document = render_svg(laid_out_graph, handlers[:renderer], theme)
95
+
96
+ # Return XML string
97
+ svg_xml = svg_document.to_xml
98
+ log "Render complete, #{svg_xml.length} bytes"
99
+
100
+ svg_xml
101
+ rescue DiagramTypeError
102
+ # Re-raise diagram type errors without wrapping
103
+ raise
104
+ rescue StandardError => e
105
+ raise PipelineError,
106
+ "Rendering failed: #{e.message}\n#{e.backtrace.join("\n")}"
107
+ end
108
+
109
+ private
110
+
111
+ # Detects diagram type from source code syntax.
112
+ #
113
+ # @param source [String] Mermaid source code
114
+ # @return [Symbol] diagram type identifier
115
+ # @raise [DiagramTypeError] if type cannot be detected
116
+ def detect_diagram_type(source)
117
+ DIAGRAM_TYPE_PATTERNS.each do |type, pattern|
118
+ return type if source.match?(pattern)
119
+ end
120
+
121
+ raise DiagramTypeError,
122
+ 'Unable to detect diagram type from source. ' \
123
+ 'Source must start with one of: graph, flowchart, ' \
124
+ 'sequenceDiagram, classDiagram, stateDiagram, ' \
125
+ 'erDiagram, journey, gantt, or pie'
126
+ end
127
+
128
+ # Retrieves handlers for a diagram type.
129
+ #
130
+ # @param type [Symbol] diagram type identifier
131
+ # @return [Hash] hash with :parser, :transform, :renderer keys
132
+ # @raise [DiagramTypeError] if type is not registered
133
+ def retrieve_handlers(type)
134
+ handlers = DiagramRegistry.get(type)
135
+
136
+ unless handlers
137
+ raise DiagramTypeError,
138
+ "No handlers registered for diagram type: #{type}"
139
+ end
140
+
141
+ handlers
142
+ end
143
+
144
+ # Parses source code into diagram model.
145
+ #
146
+ # @param source [String] Mermaid source code
147
+ # @param parser_class [Class] parser class
148
+ # @return [Diagram::Base] parsed diagram model
149
+ def parse_diagram(source, parser_class)
150
+ log 'Parsing diagram...'
151
+ parser = parser_class.new
152
+ diagram = parser.parse(source)
153
+ log "Parse complete: #{diagram.class.name}"
154
+ diagram
155
+ end
156
+
157
+ # Transforms diagram model to graph structure.
158
+ #
159
+ # @param diagram [Diagram::Base] diagram model
160
+ # @param transform_class [Class] transform class
161
+ # @return [Object] graph structure
162
+ def transform_diagram(diagram, transform_class)
163
+ log 'Transforming diagram to graph...'
164
+ transform = transform_class.new
165
+ graph = transform.to_graph(diagram)
166
+ log 'Transform complete'
167
+ graph
168
+ end
169
+
170
+ # Computes layout for graph.
171
+ #
172
+ # Currently uses a simple fallback layout since elkrb may not be
173
+ # available. In the future, this will attempt to use elkrb for
174
+ # proper graph layout computation.
175
+ #
176
+ # @param graph [Object] graph structure
177
+ # @return [Object] graph with computed positions
178
+ def layout_graph(graph)
179
+ log 'Computing layout...'
180
+
181
+ # TODO: Attempt to use elkrb when available
182
+ # For now, use simple fallback positioning
183
+ apply_fallback_layout(graph)
184
+
185
+ log 'Layout complete (using fallback positioning)'
186
+ graph
187
+ end
188
+
189
+ # Applies simple grid-based fallback layout.
190
+ #
191
+ # This is a placeholder for actual elkrb layout. It arranges
192
+ # nodes in a simple grid pattern. Handles both object-based
193
+ # and hash-based graph structures.
194
+ #
195
+ # @param graph [Object, Hash] graph structure
196
+ # @return [Object, Hash] graph with positions
197
+ def apply_fallback_layout(graph)
198
+ # Handle hash-based graph structure (elkrb-compatible)
199
+ if graph.is_a?(Hash) && graph[:children]
200
+ apply_fallback_layout_to_hash(graph)
201
+ # Handle object-based graph structure
202
+ elsif graph.respond_to?(:nodes)
203
+ nodes = graph.nodes
204
+ nodes.each_with_index do |node, index|
205
+ next unless node.respond_to?(:x=) && node.respond_to?(:y=)
206
+
207
+ # Simple grid layout: 3 columns
208
+ col = index % 3
209
+ row = index / 3
210
+
211
+ node.x = 50 + (col * 200)
212
+ node.y = 50 + (row * 150)
213
+ end
214
+ end
215
+
216
+ graph
217
+ end
218
+
219
+ # Applies fallback layout to hash-based graph structure.
220
+ #
221
+ # @param graph [Hash] graph hash with :children
222
+ # @return [Hash] graph with positions added
223
+ def apply_fallback_layout_to_hash(graph)
224
+ children = graph[:children] || []
225
+
226
+ # Apply layout to immediate children
227
+ children.each_with_index do |child, index|
228
+ # Skip if already has position
229
+ next if child[:x] && child[:y]
230
+
231
+ # Simple grid layout: 3 columns
232
+ col = index % 3
233
+ row = index / 3
234
+
235
+ child[:x] = 50 + (col * 250)
236
+ child[:y] = 50 + (row * 200)
237
+
238
+ # Recursively apply to nested children
239
+ apply_fallback_layout_to_hash(child) if child[:children]
240
+ end
241
+
242
+ graph
243
+ end
244
+
245
+ # Renders graph to SVG document.
246
+ #
247
+ # @param graph [Object] laid-out graph
248
+ # @param renderer_class [Class] renderer class
249
+ # @param theme [Theme] theme to use for rendering
250
+ # @return [Svg::Document] SVG document
251
+ def render_svg(graph, renderer_class, theme)
252
+ log 'Rendering to SVG...'
253
+ renderer = renderer_class.new(theme: theme)
254
+ svg = renderer.render(graph)
255
+ log 'SVG render complete'
256
+ svg
257
+ end
258
+
259
+ # Loads a theme from various specifications.
260
+ #
261
+ # @param theme_spec [String, Theme, Hash, nil] theme specification
262
+ # @return [Theme] loaded theme
263
+ def load_theme(theme_spec)
264
+ return Theme::Registry.get(:default) if theme_spec.nil?
265
+
266
+ case theme_spec
267
+ when String
268
+ # Could be theme name or path to file
269
+ if File.exist?(theme_spec)
270
+ Theme.load(theme_spec)
271
+ else
272
+ Theme::Registry.get(theme_spec.to_sym) ||
273
+ Theme::Registry.get(:default)
274
+ end
275
+ when Theme
276
+ theme_spec
277
+ when Hash
278
+ Theme.new(**theme_spec)
279
+ else
280
+ Theme::Registry.get(:default)
281
+ end
282
+ end
283
+
284
+ # Logs a message if verbose mode is enabled.
285
+ #
286
+ # @param message [String] message to log
287
+ # @return [void]
288
+ def log(message)
289
+ puts "[Sirena::Engine] #{message}" if verbose
290
+ end
291
+ end
292
+ end