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,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+ require_relative 'base'
5
+
6
+ module Sirena
7
+ module Diagram
8
+ # Represents a slice in a pie chart.
9
+ #
10
+ # A slice represents a data point with a label and numeric value.
11
+ class PieSlice < Lutaml::Model::Serializable
12
+ # Label/name for this slice
13
+ attribute :label, :string
14
+
15
+ # Numeric value for this slice (can be integer or decimal)
16
+ attribute :value, :float
17
+
18
+ # Validates the slice has required attributes.
19
+ #
20
+ # @return [Boolean] true if slice is valid
21
+ def valid?
22
+ !label.nil? && !label.empty? && !value.nil?
23
+ end
24
+
25
+ # Calculate percentage of total.
26
+ #
27
+ # @param total [Float] the total value of all slices
28
+ # @return [Float] percentage (0-100)
29
+ def percentage(total)
30
+ return 0.0 if total.zero?
31
+
32
+ (value.to_f / total) * 100.0
33
+ end
34
+ end
35
+
36
+ # Pie chart diagram model.
37
+ #
38
+ # Represents a complete pie chart with labeled slices showing
39
+ # proportional data distribution. Pie charts are useful for showing
40
+ # part-to-whole relationships.
41
+ #
42
+ # @example Creating a simple pie chart
43
+ # pie = Pie.new
44
+ # pie.title = 'Sales Report'
45
+ # pie.slices << PieSlice.new(label: 'Apples', value: 42.5)
46
+ # pie.slices << PieSlice.new(label: 'Oranges', value: 30.2)
47
+ # pie.slices << PieSlice.new(label: 'Bananas', value: 27.3)
48
+ class Pie < Base
49
+ # Collection of data slices in the pie chart
50
+ attribute :slices, PieSlice, collection: true, default: -> { [] }
51
+
52
+ # Whether to show data values on the chart
53
+ attribute :show_data, :boolean, default: -> { false }
54
+
55
+ # Accessibility title (for screen readers)
56
+ attribute :acc_title, :string
57
+
58
+ # Accessibility description (for screen readers)
59
+ attribute :acc_description, :string
60
+
61
+ # Returns the diagram type identifier.
62
+ #
63
+ # @return [Symbol] :pie
64
+ def diagram_type
65
+ :pie
66
+ end
67
+
68
+ # Validates the pie chart structure.
69
+ #
70
+ # A pie chart is valid if:
71
+ # - All slices (if any) are valid
72
+ # - All slice values are numeric
73
+ #
74
+ # Note: Empty pie charts are allowed for parsing tests
75
+ #
76
+ # @return [Boolean] true if pie chart is valid
77
+ def valid?
78
+ return true if slices.nil? || slices.empty?
79
+
80
+ slices.all?(&:valid?)
81
+ end
82
+
83
+ # Calculate total value of all slices.
84
+ #
85
+ # @return [Float] sum of all slice values
86
+ def total_value
87
+ slices.sum(&:value)
88
+ end
89
+
90
+ # Get slices with their calculated percentages.
91
+ #
92
+ # @return [Array<Hash>] array of hashes with :slice and :percentage
93
+ def slices_with_percentages
94
+ total = total_value
95
+ slices.map do |slice|
96
+ {
97
+ slice: slice,
98
+ percentage: slice.percentage(total)
99
+ }
100
+ end
101
+ end
102
+
103
+ # Calculate angle in degrees for a slice.
104
+ #
105
+ # @param slice [PieSlice] the slice to calculate angle for
106
+ # @return [Float] angle in degrees (0-360)
107
+ def slice_angle(slice)
108
+ total = total_value
109
+ return 0.0 if total.zero?
110
+
111
+ (slice.value / total) * 360.0
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+ require_relative 'base'
5
+
6
+ module Sirena
7
+ module Diagram
8
+ # Represents a data point in a quadrant chart.
9
+ #
10
+ # A point has a label, normalized x/y coordinates (0-1 range),
11
+ # and optional styling parameters.
12
+ class QuadrantPoint < Lutaml::Model::Serializable
13
+ # Label/name for this point
14
+ attribute :label, :string
15
+
16
+ # X coordinate (normalized 0-1)
17
+ attribute :x, :float
18
+
19
+ # Y coordinate (normalized 0-1)
20
+ attribute :y, :float
21
+
22
+ # Optional radius for the point
23
+ attribute :radius, :float
24
+
25
+ # Optional fill color
26
+ attribute :color, :string
27
+
28
+ # Optional stroke color
29
+ attribute :stroke_color, :string
30
+
31
+ # Optional stroke width
32
+ attribute :stroke_width, :float
33
+
34
+ # Validates the point has required attributes.
35
+ #
36
+ # @return [Boolean] true if point is valid
37
+ def valid?
38
+ !label.nil? && !label.empty? &&
39
+ !x.nil? && !y.nil? &&
40
+ x >= 0.0 && x <= 1.0 &&
41
+ y >= 0.0 && y <= 1.0
42
+ end
43
+
44
+ # Determine which quadrant this point is in (1-4).
45
+ #
46
+ # Quadrants are numbered:
47
+ # - Quadrant 1: top-right (x >= 0.5, y >= 0.5)
48
+ # - Quadrant 2: top-left (x < 0.5, y >= 0.5)
49
+ # - Quadrant 3: bottom-left (x < 0.5, y < 0.5)
50
+ # - Quadrant 4: bottom-right (x >= 0.5, y < 0.5)
51
+ #
52
+ # @return [Integer] quadrant number (1-4)
53
+ def quadrant
54
+ if x >= 0.5 && y >= 0.5
55
+ 1
56
+ elsif x < 0.5 && y >= 0.5
57
+ 2
58
+ elsif x < 0.5 && y < 0.5
59
+ 3
60
+ else
61
+ 4
62
+ end
63
+ end
64
+ end
65
+
66
+ # Quadrant chart diagram model.
67
+ #
68
+ # Represents a 2x2 quadrant chart for visualizing data points
69
+ # in a two-dimensional space. Useful for categorizing items
70
+ # based on two independent variables.
71
+ #
72
+ # @example Creating a simple quadrant chart
73
+ # chart = QuadrantChart.new
74
+ # chart.title = 'Product Analysis'
75
+ # chart.x_axis_left = 'Low Cost'
76
+ # chart.x_axis_right = 'High Cost'
77
+ # chart.y_axis_bottom = 'Low Value'
78
+ # chart.y_axis_top = 'High Value'
79
+ # chart.quadrant_1_label = 'Invest'
80
+ # chart.points << QuadrantPoint.new(label: 'Product A', x: 0.3, y: 0.7)
81
+ class QuadrantChart < Base
82
+ # X-axis label for the left side
83
+ attribute :x_axis_left, :string
84
+
85
+ # X-axis label for the right side
86
+ attribute :x_axis_right, :string
87
+
88
+ # Y-axis label for the bottom
89
+ attribute :y_axis_bottom, :string
90
+
91
+ # Y-axis label for the top
92
+ attribute :y_axis_top, :string
93
+
94
+ # Label for quadrant 1 (top-right)
95
+ attribute :quadrant_1_label, :string
96
+
97
+ # Label for quadrant 2 (top-left)
98
+ attribute :quadrant_2_label, :string
99
+
100
+ # Label for quadrant 3 (bottom-left)
101
+ attribute :quadrant_3_label, :string
102
+
103
+ # Label for quadrant 4 (bottom-right)
104
+ attribute :quadrant_4_label, :string
105
+
106
+ # Collection of data points in the chart
107
+ attribute :points, QuadrantPoint, collection: true, default: -> { [] }
108
+
109
+ # Returns the diagram type identifier.
110
+ #
111
+ # @return [Symbol] :quadrant
112
+ def diagram_type
113
+ :quadrant
114
+ end
115
+
116
+ # Validates the quadrant chart structure.
117
+ #
118
+ # A quadrant chart is valid if:
119
+ # - All points (if any) are valid
120
+ #
121
+ # Note: Axis labels are optional for minimal diagrams
122
+ #
123
+ # @return [Boolean] true if chart is valid
124
+ def valid?
125
+ return false unless points.all?(&:valid?)
126
+
127
+ true
128
+ end
129
+
130
+ # Get points grouped by quadrant.
131
+ #
132
+ # @return [Hash<Integer, Array<QuadrantPoint>>] points by quadrant
133
+ def points_by_quadrant
134
+ points.group_by(&:quadrant)
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sirena
4
+ module Diagram
5
+ # Represents a radar/spider chart diagram
6
+ class RadarChart < Base
7
+ attr_accessor :title, :acc_title, :acc_descr, :axes, :curves, :options
8
+
9
+ def initialize
10
+ super
11
+ @axes = []
12
+ @curves = []
13
+ @options = {}
14
+ end
15
+
16
+ def type
17
+ :radar
18
+ end
19
+ end
20
+
21
+ # Represents an axis in a radar chart
22
+ class RadarAxis
23
+ attr_accessor :id, :label
24
+
25
+ def initialize(id, label = nil)
26
+ @id = id
27
+ @label = label || id
28
+ end
29
+ end
30
+
31
+ # Represents a data curve/dataset in a radar chart
32
+ class RadarCurve
33
+ attr_accessor :id, :label, :values
34
+
35
+ def initialize(id, label = nil)
36
+ @id = id
37
+ @label = label || id
38
+ @values = {}
39
+ end
40
+
41
+ # Add a value for an axis
42
+ def add_value(axis_id, value)
43
+ @values[axis_id] = value.to_f
44
+ end
45
+
46
+ # Get value for an axis
47
+ def value_for(axis_id)
48
+ @values[axis_id] || 0.0
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Sirena
6
+ module Diagram
7
+ # Represents a requirement in the diagram
8
+ class Requirement < Lutaml::Model::Serializable
9
+ attribute :name, :string
10
+ attribute :type, :string, default: -> { "requirement" }
11
+ attribute :id, :string
12
+ attribute :text, :string
13
+ attribute :risk, :string # Low, Medium, High
14
+ attribute :verifymethod, :string # Analysis, Inspection, Test, Demonstration
15
+ attribute :classes, :string, collection: true, default: -> { [] }
16
+
17
+ def add_class(class_name)
18
+ classes << class_name
19
+ end
20
+ end
21
+
22
+ # Represents an element in the diagram
23
+ class RequirementElement < Lutaml::Model::Serializable
24
+ attribute :name, :string
25
+ attribute :type, :string
26
+ attribute :docref, :string
27
+ attribute :classes, :string, collection: true, default: -> { [] }
28
+
29
+ def add_class(class_name)
30
+ classes << class_name
31
+ end
32
+ end
33
+
34
+ # Represents a relationship between requirements/elements
35
+ class RequirementRelationship < Lutaml::Model::Serializable
36
+ attribute :source, :string
37
+ attribute :target, :string
38
+ attribute :type, :string # contains, copies, derives, satisfies, verifies, refines, traces
39
+
40
+ VALID_TYPES = %w[
41
+ contains
42
+ copies
43
+ derives
44
+ satisfies
45
+ verifies
46
+ refines
47
+ traces
48
+ ].freeze
49
+
50
+ def valid?
51
+ VALID_TYPES.include?(type)
52
+ end
53
+ end
54
+
55
+ # Represents styling for a requirement or element
56
+ class RequirementStyle < Lutaml::Model::Serializable
57
+ attribute :target_ids, :string, collection: true, default: -> { [] }
58
+ attribute :fill, :string
59
+ attribute :stroke, :string
60
+ attribute :stroke_width, :string
61
+ attribute :properties, :string, collection: true, default: -> { [] }
62
+
63
+ def add_target(id)
64
+ target_ids << id
65
+ end
66
+
67
+ def add_property(property)
68
+ properties << property
69
+ end
70
+ end
71
+
72
+ # Represents a CSS class definition
73
+ class RequirementClass < Lutaml::Model::Serializable
74
+ attribute :name, :string
75
+ attribute :fill, :string
76
+ attribute :stroke, :string
77
+ attribute :stroke_width, :string
78
+ attribute :properties, :string, collection: true, default: -> { [] }
79
+
80
+ def add_property(property)
81
+ properties << property
82
+ end
83
+ end
84
+
85
+ # Represents a class assignment to requirements/elements
86
+ class RequirementClassAssignment < Lutaml::Model::Serializable
87
+ attribute :target_ids, :string, collection: true, default: -> { [] }
88
+ attribute :class_names, :string, collection: true, default: -> { [] }
89
+
90
+ def add_target(id)
91
+ target_ids << id
92
+ end
93
+
94
+ def add_class(class_name)
95
+ class_names << class_name
96
+ end
97
+ end
98
+
99
+ # Represents a Mermaid requirement diagram
100
+ class RequirementDiagram < Lutaml::Model::Serializable
101
+ attribute :requirements, Requirement, collection: true, default: -> { [] }
102
+ attribute :elements, RequirementElement, collection: true, default: -> { [] }
103
+ attribute :relationships, RequirementRelationship, collection: true, default: -> { [] }
104
+ attribute :styles, RequirementStyle, collection: true, default: -> { [] }
105
+ attribute :classes, RequirementClass, collection: true, default: -> { [] }
106
+ attribute :class_assignments, RequirementClassAssignment, collection: true, default: -> { [] }
107
+
108
+ def add_requirement(requirement)
109
+ requirements << requirement
110
+ end
111
+
112
+ def add_element(element)
113
+ elements << element
114
+ end
115
+
116
+ def add_relationship(relationship)
117
+ relationships << relationship
118
+ end
119
+
120
+ def add_style(style)
121
+ styles << style
122
+ end
123
+
124
+ def add_class(klass)
125
+ classes << klass
126
+ end
127
+
128
+ def add_class_assignment(assignment)
129
+ class_assignments << assignment
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "base"
5
+
6
+ module Sirena
7
+ module Diagram
8
+ # Represents a node in a Sankey diagram.
9
+ #
10
+ # A node can be a source, target, or intermediate point in the flow.
11
+ # Nodes are automatically discovered from flow definitions.
12
+ class SankeyNode < Lutaml::Model::Serializable
13
+ # Unique identifier for the node
14
+ attribute :id, :string
15
+
16
+ # Display label for the node (defaults to id if not set)
17
+ attribute :label, :string
18
+
19
+ def initialize(id = nil, label = nil)
20
+ super()
21
+ @id = id
22
+ @label = label || id
23
+ end
24
+
25
+ # Validates the node has required attributes.
26
+ #
27
+ # @return [Boolean] true if node is valid
28
+ def valid?
29
+ !id.nil? && !id.empty?
30
+ end
31
+
32
+ # Get display label, falling back to id.
33
+ #
34
+ # @return [String] the label to display
35
+ def display_label
36
+ label && !label.empty? ? label : id
37
+ end
38
+ end
39
+
40
+ # Represents a flow/link between two nodes in a Sankey diagram.
41
+ #
42
+ # A flow connects a source node to a target node with a value
43
+ # that determines the width of the flow arrow.
44
+ class SankeyFlow < Lutaml::Model::Serializable
45
+ # Source node identifier
46
+ attribute :source, :string
47
+
48
+ # Target node identifier
49
+ attribute :target, :string
50
+
51
+ # Flow value (determines arrow width)
52
+ attribute :value, :float
53
+
54
+ # Optional label for the flow
55
+ attribute :label, :string
56
+
57
+ def initialize(source = nil, target = nil, value = 0.0)
58
+ super()
59
+ @source = source
60
+ @target = target
61
+ @value = value.to_f
62
+ end
63
+
64
+ # Validates the flow has required attributes.
65
+ #
66
+ # @return [Boolean] true if flow is valid
67
+ def valid?
68
+ !source.nil? && !source.empty? &&
69
+ !target.nil? && !target.empty? &&
70
+ value > 0
71
+ end
72
+
73
+ # Check if this is a self-loop.
74
+ #
75
+ # @return [Boolean] true if source equals target
76
+ def self_loop?
77
+ source == target
78
+ end
79
+ end
80
+
81
+ # Sankey diagram model.
82
+ #
83
+ # Represents a Sankey diagram showing flows between nodes where
84
+ # the width of arrows is proportional to flow values. Used for
85
+ # visualizing energy flows, material flows, cost distributions, etc.
86
+ #
87
+ # @example Creating a Sankey diagram
88
+ # sankey = SankeyDiagram.new
89
+ # sankey.title = 'Energy Flow'
90
+ #
91
+ # flow1 = SankeyFlow.new('Source', 'Process', 100)
92
+ # flow2 = SankeyFlow.new('Process', 'Output', 70)
93
+ # flow3 = SankeyFlow.new('Process', 'Waste', 30)
94
+ # sankey.flows << flow1 << flow2 << flow3
95
+ class SankeyDiagram < Base
96
+ # Collection of explicitly defined nodes (optional)
97
+ attribute :nodes, SankeyNode, collection: true,
98
+ default: -> { [] }
99
+
100
+ # Collection of flows between nodes
101
+ attribute :flows, SankeyFlow, collection: true,
102
+ default: -> { [] }
103
+
104
+ # Accessibility title (for screen readers)
105
+ attribute :acc_title, :string
106
+
107
+ # Accessibility description (for screen readers)
108
+ attribute :acc_description, :string
109
+
110
+ def initialize
111
+ @nodes = []
112
+ @flows = []
113
+ super
114
+ end
115
+
116
+ # Returns the diagram type identifier.
117
+ #
118
+ # @return [Symbol] :sankey
119
+ def diagram_type
120
+ :sankey
121
+ end
122
+
123
+ # Validates the diagram structure.
124
+ #
125
+ # A Sankey diagram is valid if it has at least one flow.
126
+ #
127
+ # @return [Boolean] true if diagram is valid
128
+ def valid?
129
+ !flows.empty? && flows.all?(&:valid?)
130
+ end
131
+
132
+ # Get all unique node IDs from flows and explicit nodes.
133
+ #
134
+ # @return [Array<String>] array of unique node IDs
135
+ def all_node_ids
136
+ node_ids_from_flows = flows.flat_map { |f| [f.source, f.target] }
137
+ explicit_node_ids = nodes.map(&:id)
138
+ (node_ids_from_flows + explicit_node_ids).uniq
139
+ end
140
+
141
+ # Get or create node by ID.
142
+ #
143
+ # @param id [String] node identifier
144
+ # @return [SankeyNode] the node
145
+ def node_by_id(id)
146
+ nodes.find { |n| n.id == id } || SankeyNode.new(id)
147
+ end
148
+
149
+ # Get all flows from a specific node.
150
+ #
151
+ # @param node_id [String] the source node ID
152
+ # @return [Array<SankeyFlow>] flows from this node
153
+ def flows_from(node_id)
154
+ flows.select { |f| f.source == node_id }
155
+ end
156
+
157
+ # Get all flows to a specific node.
158
+ #
159
+ # @param node_id [String] the target node ID
160
+ # @return [Array<SankeyFlow>] flows to this node
161
+ def flows_to(node_id)
162
+ flows.select { |f| f.target == node_id }
163
+ end
164
+
165
+ # Calculate total outflow from a node.
166
+ #
167
+ # @param node_id [String] the node ID
168
+ # @return [Float] sum of all outgoing flow values
169
+ def total_outflow(node_id)
170
+ flows_from(node_id).sum(&:value)
171
+ end
172
+
173
+ # Calculate total inflow to a node.
174
+ #
175
+ # @param node_id [String] the node ID
176
+ # @return [Float] sum of all incoming flow values
177
+ def total_inflow(node_id)
178
+ flows_to(node_id).sum(&:value)
179
+ end
180
+
181
+ # Get source nodes (nodes with no inflow).
182
+ #
183
+ # @return [Array<String>] source node IDs
184
+ def source_nodes
185
+ all_node_ids.select { |id| total_inflow(id).zero? }
186
+ end
187
+
188
+ # Get sink nodes (nodes with no outflow).
189
+ #
190
+ # @return [Array<String>] sink node IDs
191
+ def sink_nodes
192
+ all_node_ids.select { |id| total_outflow(id).zero? }
193
+ end
194
+
195
+ # Get total flow value in the diagram.
196
+ #
197
+ # @return [Float] sum of all flow values
198
+ def total_flow
199
+ flows.sum(&:value)
200
+ end
201
+
202
+ # Get maximum flow value.
203
+ #
204
+ # @return [Float] maximum single flow value
205
+ def max_flow
206
+ flows.map(&:value).max || 0.0
207
+ end
208
+
209
+ # Get minimum flow value.
210
+ #
211
+ # @return [Float] minimum single flow value
212
+ def min_flow
213
+ flows.map(&:value).min || 0.0
214
+ end
215
+ end
216
+ end
217
+ end