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,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for Mindmap diagrams
9
+ class Mindmap < Parslet::Transform
10
+ # Helper class to build mindmap tree from indented nodes
11
+ class TreeBuilder
12
+ attr_reader :root, :all_nodes
13
+
14
+ def initialize
15
+ @all_nodes = []
16
+ @root = nil
17
+ @level_stack = []
18
+ @pending_icon = nil
19
+ @pending_classes = []
20
+ @min_indent = nil
21
+ end
22
+
23
+ def add_node(node_data)
24
+ # Handle icon and class declarations - apply to PREVIOUS node
25
+ if node_data[:icon]
26
+ icon = node_data[:icon].to_s
27
+ if @all_nodes.last
28
+ @all_nodes.last[:icon] = icon
29
+ end
30
+ return
31
+ end
32
+
33
+ if node_data[:classes]
34
+ classes_str = node_data[:classes].to_s
35
+ classes = classes_str.split(/\s+/)
36
+ if @all_nodes.last
37
+ @all_nodes.last[:classes] = classes
38
+ end
39
+ return
40
+ end
41
+
42
+ # Track minimum indentation for relative level calculation
43
+ indent_size = get_indent_size(node_data[:indent])
44
+ @min_indent = indent_size if @min_indent.nil? || indent_size < @min_indent
45
+
46
+ # Calculate level from indentation (will be adjusted later)
47
+ level = calculate_level(node_data[:indent])
48
+
49
+ # Create the node
50
+ content = extract_content(node_data)
51
+ shape = extract_shape(node_data)
52
+
53
+ node = {
54
+ id: "node-#{@all_nodes.size}",
55
+ content: content,
56
+ level: level,
57
+ shape: shape,
58
+ icon: nil,
59
+ classes: [],
60
+ children: [],
61
+ _indent_size: indent_size # Store for later adjustment
62
+ }
63
+
64
+ @all_nodes << node
65
+
66
+ # Build hierarchy
67
+ if level == 0
68
+ @root = node
69
+ @level_stack = [node]
70
+ else
71
+ # Find parent at previous level
72
+ parent = find_parent(level)
73
+ if parent
74
+ parent[:children] << node
75
+ node[:parent] = parent
76
+ end
77
+
78
+ # Update stack
79
+ @level_stack = @level_stack[0..level - 1] + [node]
80
+ end
81
+ end
82
+
83
+ def finalize
84
+ # Adjust all levels to be relative to minimum indentation
85
+ return if @min_indent.nil?
86
+
87
+ @all_nodes.each do |node|
88
+ indent_size = node.delete(:_indent_size)
89
+ node[:level] = calculate_relative_level(indent_size, @min_indent)
90
+ end
91
+
92
+ # Rebuild hierarchy with corrected levels
93
+ rebuild_hierarchy
94
+ end
95
+
96
+ private
97
+
98
+ def rebuild_hierarchy
99
+ @root = nil
100
+ @level_stack = []
101
+
102
+ @all_nodes.each do |node|
103
+ level = node[:level]
104
+
105
+ # Clear old parent/children relationships
106
+ node[:children] = []
107
+ node.delete(:parent)
108
+
109
+ if level == 0
110
+ @root = node
111
+ @level_stack = [node]
112
+ else
113
+ # Find parent at previous level
114
+ parent = @level_stack[level - 1]
115
+ if parent
116
+ parent[:children] << node
117
+ node[:parent] = parent
118
+ end
119
+
120
+ # Update stack
121
+ @level_stack = @level_stack[0..level - 1] + [node]
122
+ end
123
+ end
124
+ end
125
+
126
+ def get_indent_size(indent_data)
127
+ return 0 if indent_data.nil?
128
+ return 0 if indent_data.is_a?(Array) && indent_data.empty?
129
+
130
+ indent_str = if indent_data.is_a?(Array)
131
+ indent_data.join('')
132
+ else
133
+ indent_data.to_s
134
+ end
135
+
136
+ indent_str.length
137
+ end
138
+
139
+ def calculate_relative_level(indent_size, min_indent)
140
+ relative_indent = indent_size - min_indent
141
+ return 0 if relative_indent <= 0
142
+
143
+ # Try 2-space indentation first
144
+ level = relative_indent / 2
145
+ # If not evenly divisible, try 4-space
146
+ level = relative_indent / 4 if relative_indent % 2 != 0
147
+
148
+ level
149
+ end
150
+
151
+ private
152
+
153
+ def calculate_level(indent_data)
154
+ # Handle empty array or nil
155
+ return 0 if indent_data.nil?
156
+ return 0 if indent_data.is_a?(Array) && indent_data.empty?
157
+
158
+ # Convert to string and count length
159
+ indent_str = if indent_data.is_a?(Array)
160
+ indent_data.join('')
161
+ else
162
+ indent_data.to_s
163
+ end
164
+
165
+ return 0 if indent_str.empty?
166
+
167
+ # Count spaces (2 or 4 spaces per level)
168
+ spaces = indent_str.length
169
+ # Try 2-space indentation first
170
+ level = spaces / 2
171
+ # If not evenly divisible, try 4-space
172
+ level = spaces / 4 if spaces % 2 != 0
173
+
174
+ level
175
+ end
176
+
177
+ def extract_content(node_data)
178
+ return node_data[:content].to_s if node_data[:content]
179
+ ""
180
+ end
181
+
182
+ def extract_shape(node_data)
183
+ # Determine shape based on which parser rule matched
184
+ return "circle" if node_data[:shape_circle]
185
+ return "bang" if node_data[:shape_bang]
186
+ return "cloud" if node_data[:shape_cloud]
187
+ return "hexagon" if node_data[:shape_hexagon]
188
+ return "square" if node_data[:shape_square]
189
+ "default"
190
+ end
191
+
192
+ def find_parent(level)
193
+ return nil if level == 0
194
+ # Parent is the last node at level - 1
195
+ @level_stack[level - 1]
196
+ end
197
+ end
198
+
199
+ # Transform the nodes array into a tree structure
200
+ rule(nodes: subtree(:nodes)) do
201
+ builder = TreeBuilder.new
202
+ nodes_array = Array(nodes)
203
+
204
+ nodes_array.each do |node_data|
205
+ next unless node_data.is_a?(Hash)
206
+
207
+ # Skip if no actual node data (just whitespace)
208
+ next unless node_data[:content] || node_data[:icon] || node_data[:classes] ||
209
+ node_data[:shape_circle] || node_data[:shape_bang] ||
210
+ node_data[:shape_cloud] || node_data[:shape_hexagon] ||
211
+ node_data[:shape_square]
212
+
213
+ builder.add_node(node_data)
214
+ end
215
+
216
+ # Finalize to adjust levels and rebuild hierarchy
217
+ builder.finalize
218
+
219
+ {
220
+ root: builder.root,
221
+ nodes: builder.all_nodes
222
+ }
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for Packet diagrams
9
+ class Packet < Parslet::Transform
10
+ # Transform field definition
11
+ rule(
12
+ bit_start: simple(:bit_start),
13
+ bit_end: simple(:bit_end),
14
+ label: simple(:label)
15
+ ) do
16
+ {
17
+ type: :field,
18
+ bit_start: bit_start.to_s.to_i,
19
+ bit_end: bit_end.to_s.to_i,
20
+ label: label.to_s
21
+ }
22
+ end
23
+
24
+ # Transform title
25
+ rule(title: simple(:title)) do
26
+ { type: :title, title: title.to_s }
27
+ end
28
+
29
+ # Transform the entire diagram
30
+ rule(statements: subtree(:statements)) do
31
+ result = {
32
+ title: nil,
33
+ fields: []
34
+ }
35
+
36
+ # Handle nil or empty statements
37
+ stmts = statements.nil? ? [] : Array(statements)
38
+
39
+ stmts.each do |stmt|
40
+ next unless stmt.is_a?(Hash)
41
+
42
+ case stmt[:type]
43
+ when :title
44
+ result[:title] = stmt[:title]
45
+ when :field
46
+ result[:fields] << stmt
47
+ end
48
+ end
49
+
50
+ result
51
+ end
52
+
53
+ # Handle empty diagram (no statements)
54
+ rule(statements: nil) do
55
+ {
56
+ title: nil,
57
+ fields: []
58
+ }
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../diagram/pie'
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for converting Parslet parse tree to Pie diagram model.
9
+ #
10
+ # Converts the parse tree output from Grammars::Pie into a
11
+ # fully-formed Diagram::Pie object with slices and metadata.
12
+ class Pie
13
+ # Transform parse tree into Pie diagram.
14
+ #
15
+ # @param tree [Array, Hash] Parslet parse tree
16
+ # @return [Diagram::Pie] the pie chart diagram model
17
+ def apply(tree)
18
+ diagram = Diagram::Pie.new
19
+
20
+ # Tree structure: array with header and statements
21
+ if tree.is_a?(Array)
22
+ tree.each do |item|
23
+ next unless item.is_a?(Hash)
24
+
25
+ process_header(diagram, item) if item.key?(:header)
26
+ process_title(diagram, item) if item.key?(:title)
27
+ process_show_data(diagram, item) if item.key?(:show_data)
28
+ process_statement(diagram, item) if statement?(item)
29
+ end
30
+ elsif tree.is_a?(Hash)
31
+ process_header(diagram, tree) if tree.key?(:header)
32
+ process_title(diagram, tree) if tree.key?(:title)
33
+ process_show_data(diagram, tree) if tree.key?(:show_data)
34
+
35
+ if tree[:statements]
36
+ process_statements(diagram, tree[:statements])
37
+ end
38
+ end
39
+
40
+ diagram
41
+ end
42
+
43
+ private
44
+
45
+ def statement?(item)
46
+ item.key?(:data_entry) ||
47
+ item.key?(:acc_title) ||
48
+ item.key?(:acc_descr) ||
49
+ item.key?(:standalone_title)
50
+ end
51
+
52
+ def process_header(diagram, item)
53
+ # Header is just the 'pie' keyword, nothing to extract
54
+ end
55
+
56
+ def process_title(diagram, item)
57
+ title_data = item[:title]
58
+ return unless title_data
59
+
60
+ # Extract title text
61
+ title_text = if title_data.is_a?(Hash)
62
+ extract_text(title_data[:title])
63
+ else
64
+ extract_text(title_data)
65
+ end
66
+
67
+ diagram.title = title_text unless title_text.empty?
68
+ end
69
+
70
+ def process_show_data(diagram, item)
71
+ show_data = item[:show_data]
72
+ return unless show_data
73
+
74
+ # showData flag is present if this key exists
75
+ diagram.show_data = true
76
+ end
77
+
78
+ def process_statements(diagram, statements)
79
+ Array(statements).each do |stmt|
80
+ process_statement(diagram, stmt) if stmt.is_a?(Hash)
81
+ end
82
+ end
83
+
84
+ def process_statement(diagram, stmt)
85
+ return unless stmt.is_a?(Hash)
86
+
87
+ if stmt[:data_entry]
88
+ add_data_entry(diagram, stmt)
89
+ elsif stmt[:acc_title]
90
+ diagram.acc_title = extract_text(stmt[:acc_title])
91
+ elsif stmt[:acc_descr]
92
+ diagram.acc_description = extract_text(stmt[:acc_descr])
93
+ elsif stmt[:standalone_title]
94
+ diagram.title = extract_text(stmt[:standalone_title])
95
+ end
96
+ end
97
+
98
+ def add_data_entry(diagram, stmt)
99
+ label = extract_text(stmt[:label])
100
+ value = extract_numeric_value(stmt[:value])
101
+
102
+ return if label.empty?
103
+
104
+ slice = Diagram::PieSlice.new.tap do |s|
105
+ s.label = label
106
+ s.value = value
107
+ end
108
+
109
+ diagram.slices << slice
110
+ end
111
+
112
+ def extract_text(value)
113
+ case value
114
+ when Hash
115
+ if value[:string]
116
+ value[:string].to_s
117
+ elsif value[:title]
118
+ value[:title].to_s
119
+ else
120
+ value.values.first.to_s
121
+ end
122
+ when String
123
+ value
124
+ else
125
+ value.to_s
126
+ end.strip
127
+ end
128
+
129
+ def extract_numeric_value(value)
130
+ # Value can be a simple string or a complex structure
131
+ value_str = if value.is_a?(Hash)
132
+ value.values.first.to_s
133
+ else
134
+ value.to_s
135
+ end
136
+
137
+ # Parse as float to handle both integers and decimals
138
+ value_str.to_f
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../diagram/quadrant'
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for converting Parslet parse tree to QuadrantChart model.
9
+ #
10
+ # Converts the parse tree output from Grammars::Quadrant into a
11
+ # fully-formed Diagram::QuadrantChart object with points and labels.
12
+ class Quadrant
13
+ # Transform parse tree into QuadrantChart diagram.
14
+ #
15
+ # @param tree [Array, Hash] Parslet parse tree
16
+ # @return [Diagram::QuadrantChart] the quadrant chart diagram model
17
+ def apply(tree)
18
+ diagram = Diagram::QuadrantChart.new
19
+
20
+ # Tree structure: array with header and statements
21
+ if tree.is_a?(Array)
22
+ tree.each do |item|
23
+ next unless item.is_a?(Hash)
24
+
25
+ process_header(diagram, item) if item.key?(:header)
26
+ process_title(diagram, item) if item.key?(:title)
27
+ process_x_axis(diagram, item) if item.key?(:x_axis_left)
28
+ process_y_axis(diagram, item) if item.key?(:y_axis_bottom)
29
+ process_quadrant_label(diagram, item) if item.key?(:quadrant_label)
30
+ process_data_point(diagram, item) if item.key?(:data_point)
31
+ end
32
+ elsif tree.is_a?(Hash)
33
+ process_header(diagram, tree) if tree.key?(:header)
34
+ process_title(diagram, tree) if tree.key?(:title)
35
+ process_x_axis(diagram, tree) if tree.key?(:x_axis_left)
36
+ process_y_axis(diagram, tree) if tree.key?(:y_axis_bottom)
37
+
38
+ if tree[:statements]
39
+ process_statements(diagram, tree[:statements])
40
+ end
41
+ end
42
+
43
+ diagram
44
+ end
45
+
46
+ private
47
+
48
+ def process_header(diagram, item)
49
+ # Header is just the 'quadrantChart' keyword
50
+ end
51
+
52
+ def process_title(diagram, item)
53
+ title_data = item[:title]
54
+ return unless title_data
55
+
56
+ title_text = extract_text(title_data)
57
+ diagram.title = title_text unless title_text.empty?
58
+ end
59
+
60
+ def process_x_axis(diagram, item)
61
+ diagram.x_axis_left = extract_text(item[:x_axis_left])
62
+ diagram.x_axis_right = extract_text(item[:x_axis_right])
63
+ end
64
+
65
+ def process_y_axis(diagram, item)
66
+ diagram.y_axis_bottom = extract_text(item[:y_axis_bottom])
67
+ diagram.y_axis_top = extract_text(item[:y_axis_top])
68
+ end
69
+
70
+ def process_quadrant_label(diagram, item)
71
+ quadrant_num = item[:quadrant_number].to_s
72
+ label_text = extract_text(item[:quadrant_label])
73
+
74
+ case quadrant_num
75
+ when '1'
76
+ diagram.quadrant_1_label = label_text
77
+ when '2'
78
+ diagram.quadrant_2_label = label_text
79
+ when '3'
80
+ diagram.quadrant_3_label = label_text
81
+ when '4'
82
+ diagram.quadrant_4_label = label_text
83
+ end
84
+ end
85
+
86
+ def process_statements(diagram, statements)
87
+ Array(statements).each do |stmt|
88
+ next unless stmt.is_a?(Hash)
89
+
90
+ process_title(diagram, stmt) if stmt.key?(:title)
91
+ process_x_axis(diagram, stmt) if stmt.key?(:x_axis_left)
92
+ process_y_axis(diagram, stmt) if stmt.key?(:y_axis_bottom)
93
+ process_quadrant_label(diagram, stmt) if stmt.key?(:quadrant_label)
94
+ process_data_point(diagram, stmt) if stmt.key?(:data_point)
95
+ end
96
+ end
97
+
98
+ def process_data_point(diagram, item)
99
+ label = extract_text(item[:label])
100
+ coords = item[:coordinates]
101
+ styling = item[:styling]
102
+
103
+ return if label.empty? || coords.nil?
104
+
105
+ x = extract_float(coords[:x])
106
+ y = extract_float(coords[:y])
107
+
108
+ point = Diagram::QuadrantPoint.new.tap do |p|
109
+ p.label = label
110
+ p.x = x
111
+ p.y = y
112
+
113
+ # Process optional styling parameters
114
+ if styling
115
+ style_hash = extract_styling(styling)
116
+ p.radius = style_hash[:radius] if style_hash[:radius]
117
+ p.color = style_hash[:color] if style_hash[:color]
118
+ p.stroke_color = style_hash[:stroke_color] if style_hash[:stroke_color]
119
+ p.stroke_width = style_hash[:stroke_width] if style_hash[:stroke_width]
120
+ end
121
+ end
122
+
123
+ diagram.points << point
124
+ end
125
+
126
+ def extract_styling(styling)
127
+ result = {}
128
+
129
+ # Styling can be a Hash or Array depending on parse tree structure
130
+ items = if styling.is_a?(Array)
131
+ styling
132
+ elsif styling.is_a?(Hash)
133
+ [styling]
134
+ else
135
+ []
136
+ end
137
+
138
+ items.each do |item|
139
+ next unless item.is_a?(Hash)
140
+
141
+ result[:radius] = extract_float(item[:radius]) if item[:radius]
142
+ result[:color] = extract_text(item[:color]) if item[:color]
143
+ result[:stroke_color] = extract_text(item[:stroke_color]) if item[:stroke_color]
144
+ result[:stroke_width] = extract_float(item[:stroke_width]) if item[:stroke_width]
145
+ end
146
+
147
+ result
148
+ end
149
+
150
+ def extract_text(value)
151
+ case value
152
+ when Hash
153
+ if value[:string]
154
+ value[:string].to_s
155
+ else
156
+ value.values.first.to_s
157
+ end
158
+ when String
159
+ value
160
+ else
161
+ value.to_s
162
+ end.strip
163
+ end
164
+
165
+ def extract_float(value)
166
+ value_str = if value.is_a?(Hash)
167
+ value.values.first.to_s
168
+ else
169
+ value.to_s
170
+ end
171
+
172
+ value_str.to_f
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end