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,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../diagram/gantt"
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for converting Parslet parse tree to Gantt diagram model.
9
+ #
10
+ # Converts the parse tree output from Grammars::Gantt into a
11
+ # fully-formed Diagram::GanttChart object with sections and tasks.
12
+ class Gantt
13
+ # Transform parse tree into Gantt diagram.
14
+ #
15
+ # @param tree [Array, Hash] Parslet parse tree
16
+ # @return [Diagram::GanttChart] the gantt chart diagram model
17
+ def apply(tree)
18
+ diagram = Diagram::GanttChart.new
19
+ @current_section = nil
20
+ @click_map = {}
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
+ # Apply click handlers to tasks
38
+ apply_click_handlers(diagram)
39
+
40
+ diagram
41
+ end
42
+
43
+ private
44
+
45
+ def process_item(diagram, item)
46
+ return unless item.is_a?(Hash)
47
+
48
+ process_header(diagram, item) if item.key?(:header)
49
+ process_title(diagram, item) if item.key?(:title)
50
+ process_date_format(diagram, item) if item.key?(:date_format)
51
+ process_axis_format(diagram, item) if item.key?(:axis_format)
52
+ process_tick_interval(diagram, item) if item.key?(:tick_interval)
53
+ process_excludes(diagram, item) if item.key?(:excludes)
54
+ process_today_marker(diagram, item) if item.key?(:today_marker)
55
+ process_acc_title(diagram, item) if item.key?(:acc_title)
56
+ process_acc_descr(diagram, item) if item.key?(:acc_descr)
57
+ process_section(diagram, item) if item.key?(:section)
58
+ process_click(item) if item.key?(:click_id)
59
+ process_task(diagram, item) if item.key?(:task_entry)
60
+ end
61
+
62
+ def process_statements(diagram, statements)
63
+ Array(statements).each do |stmt|
64
+ process_item(diagram, stmt) if stmt.is_a?(Hash)
65
+ end
66
+ end
67
+
68
+ def process_header(diagram, item)
69
+ # Header is just the 'gantt' keyword, nothing to extract
70
+ end
71
+
72
+ def process_title(diagram, item)
73
+ diagram.title = extract_text(item[:title])
74
+ end
75
+
76
+ def process_date_format(diagram, item)
77
+ diagram.date_format = extract_text(item[:date_format])
78
+ end
79
+
80
+ def process_axis_format(diagram, item)
81
+ diagram.axis_format = extract_text(item[:axis_format])
82
+ end
83
+
84
+ def process_tick_interval(diagram, item)
85
+ diagram.tick_interval = extract_text(item[:tick_interval])
86
+ end
87
+
88
+ def process_excludes(diagram, item)
89
+ excludes_text = extract_text(item[:excludes])
90
+ diagram.excludes << excludes_text unless excludes_text.empty?
91
+ end
92
+
93
+ def process_today_marker(diagram, item)
94
+ diagram.today_marker = extract_text(item[:today_marker])
95
+ end
96
+
97
+ def process_acc_title(diagram, item)
98
+ # Store as metadata (not currently in model)
99
+ end
100
+
101
+ def process_acc_descr(diagram, item)
102
+ # Store as metadata (not currently in model)
103
+ end
104
+
105
+ def process_section(diagram, item)
106
+ section_name = extract_text(item[:section])
107
+ @current_section = Diagram::GanttSection.new(section_name)
108
+ diagram.sections << @current_section
109
+ end
110
+
111
+ def process_click(item)
112
+ click_id = extract_text(item[:click_id])
113
+ if item[:href]
114
+ @click_map[click_id] = { type: :href, value: extract_text(item[:href]) }
115
+ elsif item[:callback]
116
+ @click_map[click_id] = { type: :callback, value: extract_text(item[:callback]) }
117
+ end
118
+ end
119
+
120
+ def process_task(diagram, item)
121
+ # Ensure we have a section
122
+ unless @current_section
123
+ @current_section = Diagram::GanttSection.new("Default")
124
+ diagram.sections << @current_section
125
+ end
126
+
127
+ task_entry = item[:task_entry]
128
+ task = Diagram::GanttTask.new
129
+ task.description = extract_text(task_entry[:description])
130
+
131
+ # Process task details
132
+ if task_entry[:task_details]
133
+ process_task_details(task, task_entry[:task_details])
134
+ end
135
+
136
+ @current_section.tasks << task
137
+ end
138
+
139
+ def process_task_details(task, details)
140
+ return unless details.is_a?(Hash)
141
+
142
+ # Extract tags
143
+ if details[:tags]
144
+ extract_tags(task, details[:tags])
145
+ end
146
+
147
+ # Extract task ID
148
+ if details[:id]
149
+ task.id = extract_text(details[:id])
150
+ end
151
+
152
+ # Extract timing information
153
+ if details[:start_date]
154
+ task.start_date = extract_text(details[:start_date])
155
+ end
156
+
157
+ if details[:end_date]
158
+ task.end_date = extract_text(details[:end_date])
159
+ end
160
+
161
+ if details[:duration]
162
+ task.duration = extract_text(details[:duration])
163
+ end
164
+
165
+ if details[:after_task]
166
+ task.after_task = extract_text(details[:after_task])
167
+ end
168
+
169
+ if details[:until_task]
170
+ task.until_task = extract_text(details[:until_task])
171
+ end
172
+ end
173
+
174
+ def extract_tags(task, tags_data)
175
+ tags_array = if tags_data.is_a?(Array)
176
+ tags_data
177
+ else
178
+ [tags_data]
179
+ end
180
+
181
+ tags_array.each do |tag_item|
182
+ tag = extract_text(tag_item)
183
+ task.tags << tag unless tag.empty?
184
+ end
185
+ end
186
+
187
+ def apply_click_handlers(diagram)
188
+ diagram.sections.each do |section|
189
+ section.tasks.each do |task|
190
+ next unless task.id && @click_map[task.id]
191
+
192
+ click_info = @click_map[task.id]
193
+ if click_info[:type] == :href
194
+ task.click_href = click_info[:value]
195
+ elsif click_info[:type] == :callback
196
+ task.click_callback = click_info[:value]
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ def extract_text(value)
203
+ case value
204
+ when Hash
205
+ value.values.first.to_s
206
+ when String
207
+ value
208
+ else
209
+ value.to_s
210
+ end.strip
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for Git Graph diagrams
9
+ class GitGraph < Parslet::Transform
10
+ # Current state tracking
11
+ class State
12
+ attr_accessor :current_branch, :branches, :commits, :commit_counter
13
+
14
+ def initialize
15
+ @current_branch = "main"
16
+ @branches = { "main" => { order: 0, created_at: nil } }
17
+ @commits = []
18
+ @commit_counter = 0
19
+ end
20
+
21
+ def add_commit(options = {})
22
+ @commit_counter += 1
23
+ commit_id = options[:id] || "commit-#{@commit_counter}"
24
+
25
+ commit = {
26
+ id: commit_id,
27
+ type: options[:type] || "NORMAL",
28
+ tag: options[:tag],
29
+ branch_name: @current_branch,
30
+ parent_ids: find_parents,
31
+ is_merge: options[:is_merge] || false,
32
+ merge_branch: options[:merge_branch],
33
+ is_cherry_pick: options[:is_cherry_pick] || false,
34
+ cherry_pick_parent: options[:cherry_pick_parent]
35
+ }
36
+
37
+ @commits << commit
38
+ commit
39
+ end
40
+
41
+ def add_branch(name, order = nil)
42
+ return if @branches.key?(name)
43
+
44
+ @branches[name] = {
45
+ order: order,
46
+ parent_branch: @current_branch,
47
+ created_at: @commits.last&.fetch(:id)
48
+ }
49
+ end
50
+
51
+ def checkout_branch(name)
52
+ @current_branch = name
53
+ end
54
+
55
+ def merge_branch(branch_name, options = {})
56
+ add_commit(
57
+ options.merge(
58
+ is_merge: true,
59
+ merge_branch: branch_name
60
+ )
61
+ )
62
+ end
63
+
64
+ def cherry_pick(options = {})
65
+ add_commit(
66
+ options.merge(is_cherry_pick: true)
67
+ )
68
+ end
69
+
70
+ def extract_options(options)
71
+ return {} unless options
72
+
73
+ opts = {}
74
+ options_array = Array(options)
75
+
76
+ options_array.each do |opt|
77
+ next unless opt.is_a?(Hash)
78
+
79
+ opts[:id] = opt[:id].to_s if opt[:id]
80
+ opts[:type] = opt[:type].to_s if opt[:type]
81
+ opts[:tag] = opt[:tag].to_s if opt[:tag]
82
+ opts[:cherry_pick_parent] = opt[:parent].to_s if opt[:parent]
83
+ end
84
+
85
+ opts
86
+ end
87
+
88
+ private
89
+
90
+ def find_parents
91
+ # Find the last commit on the current branch
92
+ parent = @commits.reverse.find do |c|
93
+ c[:branch_name] == @current_branch
94
+ end
95
+
96
+ parent ? [parent[:id]] : []
97
+ end
98
+ end
99
+
100
+ # Statement handlers
101
+ rule(statements: subtree(:stmts)) do
102
+ state = State.new
103
+ stmts_array = Array(stmts)
104
+
105
+ stmts_array.each do |stmt|
106
+ next unless stmt.is_a?(Hash)
107
+
108
+ if stmt.key?(:commit)
109
+ # commit[:options] contains the parsed options if any
110
+ commit_data = stmt[:commit]
111
+ if commit_data.is_a?(Hash) && commit_data.key?(:options)
112
+ options = state.extract_options(commit_data[:options])
113
+ else
114
+ options = {}
115
+ end
116
+ state.add_commit(options)
117
+ elsif stmt.key?(:branch)
118
+ name = stmt[:branch][:name].to_s
119
+ options = stmt[:branch][:options]
120
+ order = nil
121
+
122
+ if options.is_a?(Array)
123
+ order_opt = options.find { |o| o.is_a?(Hash) && o.key?(:order) }
124
+ order = order_opt[:order].to_i if order_opt
125
+ elsif options.is_a?(Hash) && options.key?(:order)
126
+ order = options[:order].to_i
127
+ end
128
+
129
+ state.add_branch(name, order)
130
+ elsif stmt.key?(:checkout)
131
+ state.checkout_branch(stmt[:checkout][:branch].to_s)
132
+ elsif stmt.key?(:switch)
133
+ state.checkout_branch(stmt[:switch][:branch].to_s)
134
+ elsif stmt.key?(:merge)
135
+ branch = stmt[:merge][:branch].to_s
136
+ options = state.extract_options(stmt[:merge][:options])
137
+ state.merge_branch(branch, options)
138
+ elsif stmt.key?(:cherry_pick)
139
+ options = state.extract_options(stmt[:cherry_pick][:options])
140
+ state.cherry_pick(options)
141
+ end
142
+ end
143
+
144
+ # Convert state to diagram structure
145
+ {
146
+ commits: state.commits,
147
+ branches: state.branches.map do |name, info|
148
+ {
149
+ name: name,
150
+ order: info[:order],
151
+ parent_branch: info[:parent_branch],
152
+ created_at_commit: info[:created_at]
153
+ }
154
+ end
155
+ }
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../diagram/info'
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for converting Parslet parse tree to Info diagram model.
9
+ #
10
+ # Converts the parse tree output from Grammars::Info into a
11
+ # fully-formed Diagram::Info object.
12
+ class Info
13
+ # Transform parse tree into Info diagram.
14
+ #
15
+ # @param tree [Array, Hash] Parslet parse tree
16
+ # @return [Diagram::Info] the info diagram model
17
+ def apply(tree)
18
+ diagram = Diagram::Info.new
19
+
20
+ # Process tree structure
21
+ if tree.is_a?(Array)
22
+ tree.each do |item|
23
+ next unless item.is_a?(Hash)
24
+
25
+ process_show_info_inline(diagram, item) if item.key?(:show_info_inline)
26
+ process_show_info_body(diagram, item) if item.key?(:show_info_body)
27
+ end
28
+ elsif tree.is_a?(Hash)
29
+ process_show_info_inline(diagram, tree) if tree.key?(:show_info_inline)
30
+ process_show_info_body(diagram, tree) if tree.key?(:show_info_body)
31
+ end
32
+
33
+ diagram
34
+ end
35
+
36
+ private
37
+
38
+ def process_show_info_inline(diagram, item)
39
+ show_info = item[:show_info_inline]
40
+ return unless show_info
41
+ return if show_info.to_s.strip.empty?
42
+
43
+ # showInfo flag is present inline
44
+ diagram.show_info = true
45
+ end
46
+
47
+ def process_show_info_body(diagram, item)
48
+ show_info = item[:show_info_body]
49
+ return unless show_info
50
+ return if show_info.to_s.strip.empty?
51
+
52
+ # showInfo flag is present in body
53
+ diagram.show_info = true
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for Kanban diagrams
9
+ class Kanban < Parslet::Transform
10
+ # Helper class to build kanban board from indented lines
11
+ class BoardBuilder
12
+ attr_reader :columns
13
+
14
+ def initialize
15
+ @columns = []
16
+ @current_column = nil
17
+ @min_indent = nil
18
+ @items = []
19
+ end
20
+
21
+ def add_line(line_data)
22
+ # Skip empty lines
23
+ return if line_data.nil?
24
+ return unless line_data[:id]
25
+
26
+ indent_size = get_indent_size(line_data[:indent])
27
+
28
+ # Track minimum indentation
29
+ @min_indent = indent_size if @min_indent.nil? || indent_size < @min_indent
30
+
31
+ item = {
32
+ id: line_data[:id].to_s,
33
+ text: line_data[:text].to_s,
34
+ indent: indent_size,
35
+ metadata: line_data[:metadata]
36
+ }
37
+
38
+ @items << item
39
+ end
40
+
41
+ def finalize
42
+ return if @items.empty?
43
+
44
+ # Determine column vs card by indentation
45
+ # Items at minimum indent are columns, items with more indent are cards
46
+ @items.each do |item|
47
+ if item[:indent] == @min_indent
48
+ # This is a column
49
+ add_column(item)
50
+ else
51
+ # This is a card, add to current column
52
+ add_card(item)
53
+ end
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def add_column(item)
60
+ column = {
61
+ id: item[:id],
62
+ title: item[:text],
63
+ cards: []
64
+ }
65
+
66
+ @columns << column
67
+ @current_column = column
68
+ end
69
+
70
+ def add_card(item)
71
+ # Cards must belong to a column
72
+ return unless @current_column
73
+
74
+ card = {
75
+ id: item[:id],
76
+ text: item[:text]
77
+ }
78
+
79
+ # Extract metadata if present
80
+ if item[:metadata]
81
+ metadata = parse_metadata(item[:metadata])
82
+ card.merge!(metadata)
83
+ end
84
+
85
+ @current_column[:cards] << card
86
+ end
87
+
88
+ def parse_metadata(metadata_data)
89
+ return {} if metadata_data.nil?
90
+
91
+ result = {}
92
+
93
+ # metadata_data could be a single entry or an array
94
+ entries = if metadata_data.is_a?(Array)
95
+ metadata_data
96
+ else
97
+ [metadata_data]
98
+ end
99
+
100
+ entries.each do |entry|
101
+ next unless entry.is_a?(Hash)
102
+ next unless entry[:key] && entry[:value]
103
+
104
+ key = entry[:key].to_s
105
+ value = extract_value(entry[:value])
106
+
107
+ # Map to known metadata fields
108
+ case key
109
+ when "assigned"
110
+ result[:assigned] = value
111
+ when "ticket"
112
+ result[:ticket] = value
113
+ when "icon"
114
+ result[:icon] = value
115
+ when "label"
116
+ result[:label] = value
117
+ when "priority"
118
+ result[:priority] = value
119
+ else
120
+ # Store unknown keys as-is
121
+ result[key.to_sym] = value
122
+ end
123
+ end
124
+
125
+ result
126
+ end
127
+
128
+ def extract_value(value_data)
129
+ return "" if value_data.nil?
130
+
131
+ if value_data.is_a?(Hash) && value_data[:string]
132
+ # It's a string wrapped in :string key
133
+ value_data[:string].to_s
134
+ else
135
+ value_data.to_s
136
+ end
137
+ end
138
+
139
+ def get_indent_size(indent_data)
140
+ return 0 if indent_data.nil?
141
+ return 0 if indent_data.is_a?(Array) && indent_data.empty?
142
+
143
+ indent_str = if indent_data.is_a?(Array)
144
+ indent_data.join('')
145
+ else
146
+ indent_data.to_s
147
+ end
148
+
149
+ indent_str.length
150
+ end
151
+ end
152
+
153
+ # Transform the lines array into columns and cards
154
+ rule(lines: subtree(:lines)) do
155
+ builder = BoardBuilder.new
156
+ lines_array = Array(lines)
157
+
158
+ lines_array.each do |line_data|
159
+ next unless line_data.is_a?(Hash)
160
+
161
+ # Skip empty lines
162
+ next unless line_data[:id]
163
+
164
+ builder.add_line(line_data)
165
+ end
166
+
167
+ builder.finalize
168
+
169
+ {
170
+ columns: builder.columns
171
+ }
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end