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,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../diagram/timeline"
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for converting Parslet parse tree to Timeline diagram model.
9
+ #
10
+ # Converts the parse tree output from Grammars::Timeline into a
11
+ # fully-formed Diagram::Timeline object with sections and events.
12
+ class Timeline
13
+ # Transform parse tree into Timeline diagram.
14
+ #
15
+ # @param tree [Array, Hash] Parslet parse tree
16
+ # @return [Diagram::Timeline] the timeline diagram model
17
+ def apply(tree)
18
+ diagram = Diagram::Timeline.new
19
+ @current_section = nil
20
+ @last_event = nil
21
+
22
+ # Tree structure: array with header and statements
23
+ if tree.is_a?(Array)
24
+ tree.each do |item|
25
+ next unless item.is_a?(Hash)
26
+
27
+ process_item(diagram, item)
28
+ end
29
+ elsif tree.is_a?(Hash)
30
+ process_item(diagram, tree)
31
+
32
+ if tree[:statements]
33
+ process_statements(diagram, tree[:statements])
34
+ end
35
+ end
36
+
37
+ diagram
38
+ end
39
+
40
+ private
41
+
42
+ def process_item(diagram, item)
43
+ return unless item.is_a?(Hash)
44
+
45
+ process_header(diagram, item) if item.key?(:header)
46
+ process_title(diagram, item) if item.key?(:title)
47
+ process_acc_title(diagram, item) if item.key?(:acc_title)
48
+ process_acc_descr(diagram, item) if item.key?(:acc_descr)
49
+ process_section(diagram, item) if item.key?(:section)
50
+ process_event(diagram, item) if item.key?(:event_entry)
51
+ process_continuation(diagram, item) if item.key?(:continuation_entry)
52
+ process_task(diagram, item) if item.key?(:task)
53
+ end
54
+
55
+ def process_statements(diagram, statements)
56
+ Array(statements).each do |stmt|
57
+ process_item(diagram, stmt) if stmt.is_a?(Hash)
58
+ end
59
+ end
60
+
61
+ def process_header(diagram, item)
62
+ # Header is just the 'timeline' keyword, nothing to extract
63
+ end
64
+
65
+ def process_title(diagram, item)
66
+ diagram.title = extract_text(item[:title])
67
+ end
68
+
69
+ def process_acc_title(diagram, item)
70
+ diagram.acc_title = extract_text(item[:acc_title])
71
+ end
72
+
73
+ def process_acc_descr(diagram, item)
74
+ diagram.acc_description = extract_text(item[:acc_descr])
75
+ end
76
+
77
+ def process_section(diagram, item)
78
+ section_name = extract_text(item[:section])
79
+ @current_section = Diagram::TimelineSection.new(section_name)
80
+ diagram.sections << @current_section
81
+ @last_event = nil
82
+ end
83
+
84
+ def process_event(diagram, item)
85
+ event_data = item[:event_entry]
86
+ return unless event_data
87
+
88
+ time = extract_text(event_data[:time])
89
+ descriptions = extract_descriptions(event_data[:descriptions])
90
+
91
+ event = Diagram::TimelineEvent.new.tap do |e|
92
+ e.time = time
93
+ e.descriptions.concat(descriptions)
94
+ end
95
+
96
+ # Store reference for continuation entries
97
+ @last_event = event
98
+
99
+ # Add to current section or diagram
100
+ if @current_section
101
+ @current_section.events << event
102
+ else
103
+ diagram.events << event
104
+ end
105
+ end
106
+
107
+ def process_continuation(diagram, item)
108
+ continuation_data = item[:continuation_entry]
109
+ return unless continuation_data
110
+
111
+ descriptions = extract_descriptions(continuation_data[:descriptions])
112
+
113
+ # Add descriptions to last event if it exists
114
+ if @last_event
115
+ @last_event.descriptions.concat(descriptions)
116
+ else
117
+ # If no last event, create a new event with empty time
118
+ # This handles edge case of continuation at start
119
+ event = Diagram::TimelineEvent.new.tap do |e|
120
+ e.time = ""
121
+ e.descriptions.concat(descriptions)
122
+ end
123
+
124
+ if @current_section
125
+ @current_section.events << event
126
+ else
127
+ diagram.events << event
128
+ end
129
+
130
+ @last_event = event
131
+ end
132
+ end
133
+
134
+ def process_task(diagram, item)
135
+ # Ensure we have a section for tasks
136
+ unless @current_section
137
+ @current_section = Diagram::TimelineSection.new("Default")
138
+ diagram.sections << @current_section
139
+ end
140
+
141
+ task_name = extract_text(item[:task])
142
+ @current_section.tasks << task_name unless task_name.empty?
143
+ end
144
+
145
+ def extract_descriptions(descriptions_data)
146
+ return [] unless descriptions_data
147
+
148
+ # descriptions_data is an array of {:desc => text} hashes
149
+ if descriptions_data.is_a?(Array)
150
+ descriptions_data.map do |item|
151
+ if item.is_a?(Hash) && item[:desc]
152
+ extract_text(item[:desc])
153
+ else
154
+ extract_text(item)
155
+ end
156
+ end.reject(&:empty?)
157
+ elsif descriptions_data.is_a?(Hash) && descriptions_data[:desc]
158
+ [extract_text(descriptions_data[:desc])].reject(&:empty?)
159
+ else
160
+ [extract_text(descriptions_data)].reject(&:empty?)
161
+ end
162
+ end
163
+
164
+ def extract_text(value)
165
+ case value
166
+ when Hash
167
+ value.values.first.to_s
168
+ when String
169
+ value
170
+ else
171
+ value.to_s
172
+ end.strip
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parslet'
4
+ require_relative '../../diagram/treemap'
5
+
6
+ module Sirena
7
+ module Parser
8
+ module Transforms
9
+ # Transform for converting treemap parse tree to diagram model
10
+ class Treemap < Parslet::Transform
11
+ rule(number: simple(:x)) { x.to_f }
12
+ rule(string: simple(:x)) { x.to_s }
13
+ rule(string: sequence(:x)) { '' } # Empty string
14
+ rule(identifier: simple(:x)) { x.to_s }
15
+
16
+ rule(keyword: simple(:_kw), statements: subtree(:stmts)) do
17
+ {
18
+ type: :treemap,
19
+ statements: Array(stmts).compact
20
+ }
21
+ end
22
+
23
+ # Title
24
+ rule(title: simple(:t)) do
25
+ { type: :title, value: t.to_s }
26
+ end
27
+
28
+ # Accessibility
29
+ rule(acc_title: simple(:t)) do
30
+ { type: :acc_title, value: t.to_s }
31
+ end
32
+
33
+ rule(acc_descr: simple(:d)) do
34
+ { type: :acc_descr, value: d.to_s }
35
+ end
36
+
37
+ # Class definition
38
+ rule(class_def: {
39
+ class_name: simple(:name),
40
+ class_styles: simple(:styles)
41
+ }) do
42
+ {
43
+ type: :class_def,
44
+ name: name.to_s,
45
+ styles: styles.to_s
46
+ }
47
+ end
48
+
49
+ # Transform happens bottom-up, so handle nested parts first
50
+ # Then handle the node structure
51
+
52
+ # Node with all fields
53
+ rule(node: subtree(:n)) do
54
+ indent_val = n[:indent]
55
+ # Handle indent - could be Array (empty), Parslet::Slice, or String
56
+ indent_len = if indent_val.is_a?(Array)
57
+ indent_val.length # Empty array = 0
58
+ elsif indent_val.respond_to?(:to_s)
59
+ indent_val.to_s.length # String or Parslet::Slice
60
+ else
61
+ 0
62
+ end
63
+
64
+ label_val = n[:label]
65
+ label_str = label_val.is_a?(String) ? label_val : label_val.to_s
66
+
67
+ result = {
68
+ type: :node,
69
+ indent: indent_len,
70
+ label: label_str
71
+ }
72
+
73
+ result[:value] = n[:value] if n.key?(:value)
74
+ result[:css_class] = n[:css_class].to_s if n.key?(:css_class)
75
+
76
+ result
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for XY Chart diagrams
9
+ class XYChart < Parslet::Transform
10
+ # Transform title (from grammar: {:title=>{:string=>"..."}} )
11
+ rule(title: { string: simple(:title) }) do
12
+ { type: :title, title: title.to_s }
13
+ end
14
+
15
+ # Transform values - keep as-is for now, will determine numeric vs categorical later
16
+ rule(value: simple(:v)) do
17
+ # Try to convert to number if it looks numeric, otherwise keep as string
18
+ str = v.to_s
19
+ if str.match?(/^\d+(\.\d+)?$/)
20
+ str.to_f
21
+ else
22
+ str
23
+ end
24
+ end
25
+
26
+ # Transform X-axis with label
27
+ rule(x_label: { string: simple(:label) }, x_values: subtree(:values)) do
28
+ {
29
+ type: :x_axis,
30
+ label: label.to_s,
31
+ values: Array(values)
32
+ }
33
+ end
34
+
35
+ rule(x_values: subtree(:values)) do
36
+ {
37
+ type: :x_axis,
38
+ label: nil,
39
+ values: Array(values)
40
+ }
41
+ end
42
+
43
+ # Transform Y-axis with label
44
+ rule(y_label: { string: simple(:label) }, y_min: simple(:min), y_max: simple(:max)) do
45
+ {
46
+ type: :y_axis,
47
+ label: label.to_s,
48
+ min: min.to_s.to_f,
49
+ max: max.to_s.to_f
50
+ }
51
+ end
52
+
53
+ rule(y_min: simple(:min), y_max: simple(:max)) do
54
+ {
55
+ type: :y_axis,
56
+ label: nil,
57
+ min: min.to_s.to_f,
58
+ max: max.to_s.to_f
59
+ }
60
+ end
61
+
62
+ # Transform line dataset
63
+ rule(line_values: subtree(:values)) do
64
+ {
65
+ type: :dataset,
66
+ chart_type: :line,
67
+ label: "Line",
68
+ values: Array(values)
69
+ }
70
+ end
71
+
72
+ # Transform bar dataset
73
+ rule(bar_values: subtree(:values)) do
74
+ {
75
+ type: :dataset,
76
+ chart_type: :bar,
77
+ label: "Bar",
78
+ values: Array(values)
79
+ }
80
+ end
81
+
82
+ # Transform named dataset
83
+ rule(dataset_label: { string: simple(:label) }, dataset_values: subtree(:values)) do
84
+ {
85
+ type: :dataset,
86
+ chart_type: :line,
87
+ label: label.to_s,
88
+ values: Array(values)
89
+ }
90
+ end
91
+
92
+ # Transform the entire diagram
93
+ rule(statements: subtree(:statements)) do
94
+ result = {
95
+ title: nil,
96
+ x_axis: nil,
97
+ y_axis: nil,
98
+ datasets: []
99
+ }
100
+
101
+ Array(statements).each do |stmt|
102
+ next unless stmt.is_a?(Hash)
103
+
104
+ case stmt[:type]
105
+ when :title
106
+ result[:title] = stmt[:title]
107
+ when :x_axis
108
+ result[:x_axis] = {
109
+ label: stmt[:label],
110
+ values: stmt[:values]
111
+ }
112
+ when :y_axis
113
+ result[:y_axis] = {
114
+ label: stmt[:label],
115
+ min: stmt[:min],
116
+ max: stmt[:max]
117
+ }
118
+ when :dataset
119
+ result[:datasets] << {
120
+ chart_type: stmt[:chart_type],
121
+ label: stmt[:label],
122
+ values: stmt[:values]
123
+ }
124
+ end
125
+ end
126
+
127
+ result
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'grammars/treemap'
4
+ require_relative 'transforms/treemap'
5
+ require_relative '../diagram/treemap'
6
+
7
+ module Sirena
8
+ module Parser
9
+ # Parser for treemap diagrams using Parslet
10
+ class TreemapParser
11
+ def initialize
12
+ @grammar = Grammars::Treemap.new
13
+ @transform = Transforms::Treemap.new
14
+ end
15
+
16
+ # Parse treemap source into a TreemapDiagram
17
+ #
18
+ # @param source [String] The treemap diagram source
19
+ # @return [Diagram::TreemapDiagram] The parsed diagram
20
+ # @raise [Parslet::ParseFailed] If parsing fails
21
+ def parse(source)
22
+ tree = @grammar.parse(source)
23
+ intermediate = @transform.apply(tree)
24
+ build_diagram(intermediate)
25
+ rescue Parslet::ParseFailed => e
26
+ raise ParseError, "Treemap parse error: #{e.message}"
27
+ end
28
+
29
+ private
30
+
31
+ # Build TreemapDiagram from intermediate representation
32
+ def build_diagram(data)
33
+ diagram = Diagram::TreemapDiagram.new
34
+
35
+ statements = data[:statements] || []
36
+ nodes = []
37
+
38
+ statements.each do |stmt|
39
+ case stmt[:type]
40
+ when :title
41
+ diagram.title = stmt[:value]
42
+ when :acc_title
43
+ # Store accessibility title (could be added to base)
44
+ when :acc_descr
45
+ # Store accessibility description
46
+ when :class_def
47
+ diagram.add_class_def(stmt[:name], stmt[:styles])
48
+ when :node
49
+ nodes << stmt
50
+ end
51
+ end
52
+
53
+ # Build hierarchical structure from flat node list
54
+ build_hierarchy(diagram, nodes)
55
+
56
+ diagram
57
+ end
58
+
59
+ # Build hierarchical tree from flat list of nodes with indentation
60
+ def build_hierarchy(diagram, nodes)
61
+ return if nodes.empty?
62
+
63
+ # Stack to track the current parent at each indentation level
64
+ # Format: [[indent_level, node], ...]
65
+ stack = []
66
+
67
+ nodes.each do |node_data|
68
+ indent = node_data[:indent] || 0
69
+ label = node_data[:label]
70
+ value = node_data[:value]
71
+ css_class = node_data[:css_class]
72
+
73
+ # Create the node
74
+ node = Diagram::TreemapNode.new(label, value)
75
+ node.css_class = css_class if css_class
76
+
77
+ # Pop stack until we find the parent
78
+ stack.pop while stack.any? && stack.last[0] >= indent
79
+
80
+ if stack.empty?
81
+ # This is a root node
82
+ diagram.add_root_node(node)
83
+ else
84
+ # This is a child of the last node in stack
85
+ parent = stack.last[1]
86
+ parent.add_child(node)
87
+ end
88
+
89
+ # Add current node to stack (it could be a parent for future nodes)
90
+ stack.push([indent, node])
91
+ end
92
+ end
93
+ end
94
+
95
+ # Alias for backward compatibility
96
+ Treemap = TreemapParser
97
+ end
98
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parslet'
4
+ require_relative 'base'
5
+ require_relative '../diagram/user_journey'
6
+
7
+ module Sirena
8
+ module Parser
9
+ # Parslet grammar for User Journey diagrams
10
+ class UserJourneyGrammar < Parslet::Parser
11
+ rule(:sp) { match('[ \t]').repeat(1) }
12
+ rule(:sp?) { sp.maybe }
13
+ rule(:nl) { str("\n") }
14
+
15
+ rule(:journey) { str('journey') >> sp? >> (nl | any.absent?) }
16
+ rule(:title_decl) { sp? >> str('title') >> sp >> text_line.as(:title) >> (nl | any.absent?) }
17
+ rule(:section_decl) do
18
+ sp? >> str('section') >> sp >> text_line.as(:section) >> (nl | any.absent?)
19
+ end
20
+
21
+ rule(:text_line) { (nl.absent? >> str(':').absent? >> any).repeat(1) }
22
+ rule(:task_text) { (str(':').absent? >> nl.absent? >> any).repeat(1) }
23
+ rule(:actor_text) { (match('[,\n]').absent? >> any).repeat(1) }
24
+
25
+ rule(:task_line) do
26
+ sp? >>
27
+ task_text.as(:task) >> str(':') >> sp? >>
28
+ match('[0-9]').repeat(1).as(:score) >> str(':') >> sp? >>
29
+ actor_list.as(:actors) >> sp? >> (nl | any.absent?)
30
+ end
31
+
32
+ rule(:actor_list) do
33
+ actor_text.as(:actor) >>
34
+ (sp? >> str(',') >> sp? >> actor_text.as(:actor)).repeat
35
+ end
36
+
37
+ rule(:line) { title_decl | section_decl | task_line | comment_line | blank_line }
38
+ rule(:comment_line) { sp? >> str('%%') >> (nl.absent? >> any).repeat >> nl }
39
+ rule(:blank_line) { sp? >> nl }
40
+
41
+ rule(:journey_doc) do
42
+ journey >>
43
+ line.repeat.as(:lines)
44
+ end
45
+
46
+ root(:journey_doc)
47
+ end
48
+
49
+ # User Journey diagram parser using Parslet
50
+ class UserJourneyParser < Base
51
+ def parse(source)
52
+ grammar = UserJourneyGrammar.new
53
+
54
+ begin
55
+ tree = grammar.parse(source)
56
+ rescue Parslet::ParseFailed => e
57
+ raise ParseError, "Parse error: #{e.parse_failure_cause.ascii_tree}"
58
+ end
59
+
60
+ build_diagram_from_tree(tree)
61
+ end
62
+
63
+ private
64
+
65
+ def build_diagram_from_tree(tree)
66
+ diagram = Diagram::UserJourney.new
67
+ current_section = nil
68
+
69
+ lines = Array(tree[:lines])
70
+
71
+ lines.each do |line|
72
+ next unless line.is_a?(Hash)
73
+
74
+ if line[:title]
75
+ diagram.title = line[:title].to_s.strip
76
+ elsif line[:section]
77
+ # Save previous section
78
+ diagram.sections << current_section if current_section
79
+
80
+ # Create new section
81
+ current_section = Diagram::JourneySection.new
82
+ current_section.name = line[:section].to_s.strip
83
+ elsif line[:task] && current_section
84
+ # Parse task
85
+ task = Diagram::JourneyTask.new
86
+ task.name = line[:task].to_s.strip
87
+ task.score = line[:score].to_s.to_i
88
+
89
+ validate_score!(task.score)
90
+
91
+ # Extract actors
92
+ actors_data = line[:actors]
93
+ task.actors = if actors_data.is_a?(Array)
94
+ actors_data.map do |a|
95
+ a[:actor].to_s.strip
96
+ end.reject(&:empty?)
97
+ elsif actors_data.is_a?(Hash) && actors_data[:actor]
98
+ [actors_data[:actor].to_s.strip]
99
+ else
100
+ []
101
+ end
102
+
103
+ current_section.tasks << task
104
+ end
105
+ end
106
+
107
+ # Add final section
108
+ diagram.sections << current_section if current_section
109
+
110
+ diagram
111
+ end
112
+
113
+ def validate_score!(score)
114
+ return if score >= 1 && score <= 5
115
+
116
+ raise ParseError, "Score must be between 1 and 5, got #{score}"
117
+ end
118
+ end
119
+ end
120
+ end