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,342 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../diagram/sequence'
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for converting Parslet parse tree to Sequence diagram model.
9
+ #
10
+ # Converts the parse tree output from Grammars::Sequence into a
11
+ # fully-formed Diagram::Sequence object with participants, messages,
12
+ # notes, and activations.
13
+ class Sequence
14
+ # Arrow type mappings
15
+ ARROW_TYPE_MAP = {
16
+ '->>+' => 'solid_activate',
17
+ '-->>+' => 'dotted_activate',
18
+ '->>-' => 'solid_deactivate',
19
+ '-->>-' => 'dotted_deactivate',
20
+ '->>' => 'solid',
21
+ '->)' => 'async',
22
+ '-->)' => 'async_dotted',
23
+ '->' => 'solid',
24
+ '-->' => 'dotted'
25
+ }.freeze
26
+
27
+ def initialize
28
+ @message_index = 0
29
+ @activations = {}
30
+ end
31
+
32
+ # Transform parse tree into Sequence diagram.
33
+ #
34
+ # @param tree [Array, Hash] Parslet parse tree
35
+ # @return [Diagram::Sequence] the sequence diagram model
36
+ def apply(tree)
37
+ diagram = Diagram::Sequence.new
38
+ @message_index = 0
39
+ @activations = {}
40
+
41
+ # Tree is an array: [header, ...statements]
42
+ if tree.is_a?(Array)
43
+ tree.each do |item|
44
+ next if item.is_a?(Hash) && item[:header] # Skip header
45
+ process_statement(diagram, item) if item.is_a?(Hash)
46
+ end
47
+ elsif tree.is_a?(Hash) && tree[:statements]
48
+ process_statements(diagram, tree[:statements])
49
+ end
50
+
51
+ diagram
52
+ end
53
+
54
+ private
55
+
56
+ def process_statements(diagram, statements)
57
+ Array(statements).each do |stmt|
58
+ process_statement(diagram, stmt) if stmt.is_a?(Hash)
59
+ end
60
+ end
61
+
62
+ def process_statement(diagram, stmt)
63
+ return unless stmt.is_a?(Hash)
64
+
65
+ if stmt[:participant]
66
+ add_participant(diagram, stmt, 'participant')
67
+ elsif stmt[:actor]
68
+ add_participant(diagram, stmt, 'actor')
69
+ elsif stmt[:from] && stmt[:to] && stmt[:arrow]
70
+ add_message(diagram, stmt)
71
+ elsif stmt[:position] && stmt[:note_text]
72
+ add_note(diagram, stmt)
73
+ elsif stmt[:activate]
74
+ track_activation(diagram, stmt[:activate].to_s, true)
75
+ elsif stmt[:deactivate]
76
+ track_activation(diagram, stmt[:deactivate].to_s, false)
77
+ elsif stmt[:box_label]
78
+ process_box(diagram, stmt)
79
+ elsif stmt[:loop_label]
80
+ process_loop(diagram, stmt)
81
+ elsif stmt[:alt_label]
82
+ process_alt(diagram, stmt)
83
+ elsif stmt[:opt_label]
84
+ process_opt(diagram, stmt)
85
+ elsif stmt[:par_label]
86
+ process_par(diagram, stmt)
87
+ elsif stmt[:critical_label]
88
+ process_critical(diagram, stmt)
89
+ elsif stmt[:break_label]
90
+ process_break(diagram, stmt)
91
+ end
92
+ end
93
+
94
+ def add_participant(diagram, stmt, actor_type)
95
+ id = stmt[:id].to_s
96
+ label = if stmt[:label]
97
+ extract_text(stmt[:label])
98
+ else
99
+ id
100
+ end
101
+
102
+ participant = Diagram::SequenceParticipant.new.tap do |p|
103
+ p.id = id
104
+ p.label = label
105
+ p.actor_type = actor_type
106
+ end
107
+
108
+ # Add or update participant
109
+ existing = diagram.find_participant(id)
110
+ if existing
111
+ existing.label = label unless label.empty?
112
+ existing.actor_type = actor_type
113
+ else
114
+ diagram.participants << participant
115
+ end
116
+ end
117
+
118
+ def add_message(diagram, stmt)
119
+ from_id = stmt[:from].to_s
120
+ to_id = stmt[:to].to_s
121
+ message_text = stmt[:text] ? extract_text(stmt[:text]) : ''
122
+
123
+ # Determine arrow type
124
+ arrow = stmt[:arrow]
125
+ arrow_str = if arrow.is_a?(Hash)
126
+ if arrow[:arrow_activation]
127
+ arrow[:arrow_activation].to_s
128
+ elsif arrow[:arrow_plain]
129
+ arrow[:arrow_plain].to_s
130
+ else
131
+ arrow.values.first.to_s
132
+ end
133
+ else
134
+ arrow.to_s
135
+ end
136
+
137
+ arrow_type = ARROW_TYPE_MAP[arrow_str] || 'solid'
138
+
139
+ # Ensure participants exist
140
+ ensure_participant(diagram, from_id)
141
+ ensure_participant(diagram, to_id)
142
+
143
+ # Handle activation modifiers
144
+ handle_arrow_activation(diagram, from_id, to_id, arrow_str)
145
+
146
+ message = Diagram::SequenceMessage.new.tap do |m|
147
+ m.from_id = from_id
148
+ m.to_id = to_id
149
+ m.message_text = message_text
150
+ m.arrow_type = arrow_type.sub(/_activate$/, '').sub(/_deactivate$/, '')
151
+ end
152
+
153
+ diagram.messages << message
154
+ @message_index += 1
155
+ end
156
+
157
+ def handle_arrow_activation(diagram, from_id, to_id, arrow_str)
158
+ if arrow_str.end_with?('+')
159
+ # Activate target (receiver)
160
+ track_activation(diagram, to_id, true)
161
+ elsif arrow_str.end_with?('-')
162
+ # Deactivate source (sender)
163
+ track_activation(diagram, from_id, false)
164
+ end
165
+ end
166
+
167
+ def add_note(diagram, stmt)
168
+ position_data = stmt[:position]
169
+ position = if position_data.is_a?(Hash)
170
+ if position_data[:left_of]
171
+ 'left_of'
172
+ elsif position_data[:right_of]
173
+ 'right_of'
174
+ elsif position_data[:over]
175
+ 'over'
176
+ else
177
+ 'over'
178
+ end
179
+ else
180
+ 'over'
181
+ end
182
+
183
+ participants = if stmt[:participants].is_a?(Array)
184
+ stmt[:participants].map do |p|
185
+ p.is_a?(Hash) ? p[:participant].to_s : p.to_s
186
+ end
187
+ elsif stmt[:participants].is_a?(Hash)
188
+ [stmt[:participants][:participant].to_s]
189
+ else
190
+ []
191
+ end
192
+
193
+ text = extract_text(stmt[:note_text])
194
+
195
+ # Ensure participants exist
196
+ participants.each { |pid| ensure_participant(diagram, pid) }
197
+
198
+ note = Diagram::SequenceNote.new.tap do |n|
199
+ n.text = text
200
+ n.position = position
201
+ n.participant_ids = participants
202
+ n.message_index = @message_index
203
+ end
204
+
205
+ diagram.notes << note
206
+ end
207
+
208
+ def track_activation(diagram, participant_id, activate)
209
+ ensure_participant(diagram, participant_id)
210
+
211
+ @activations[participant_id] ||= []
212
+
213
+ if activate
214
+ # Start new activation
215
+ @activations[participant_id] << { start: @message_index }
216
+ else
217
+ # End latest activation
218
+ active = @activations[participant_id].last
219
+ if active && !active[:end]
220
+ active[:end] = @message_index
221
+
222
+ # Create activation record
223
+ activation = Diagram::SequenceActivation.new.tap do |a|
224
+ a.participant_id = participant_id
225
+ a.start_index = active[:start]
226
+ a.end_index = active[:end]
227
+ end
228
+
229
+ diagram.activations << activation
230
+ end
231
+ end
232
+ end
233
+
234
+ def process_box(diagram, stmt)
235
+ # Box statements contain nested participants/messages
236
+ # Process the nested statements
237
+ if stmt[:box_statements]
238
+ process_statements(diagram, stmt[:box_statements])
239
+ end
240
+ end
241
+
242
+ def process_loop(diagram, stmt)
243
+ # Process loop statements
244
+ if stmt[:loop_statements]
245
+ process_statements(diagram, stmt[:loop_statements])
246
+ end
247
+ end
248
+
249
+ def process_alt(diagram, stmt)
250
+ # Process alt statements
251
+ if stmt[:alt_statements]
252
+ process_statements(diagram, stmt[:alt_statements])
253
+ end
254
+
255
+ # Process else blocks
256
+ if stmt[:else_blocks]
257
+ Array(stmt[:else_blocks]).each do |else_block|
258
+ if else_block[:else_statements]
259
+ process_statements(diagram, else_block[:else_statements])
260
+ end
261
+ end
262
+ end
263
+ end
264
+
265
+ def process_opt(diagram, stmt)
266
+ # Process opt statements
267
+ if stmt[:opt_statements]
268
+ process_statements(diagram, stmt[:opt_statements])
269
+ end
270
+ end
271
+
272
+ def process_par(diagram, stmt)
273
+ # Process par statements
274
+ if stmt[:par_statements]
275
+ process_statements(diagram, stmt[:par_statements])
276
+ end
277
+
278
+ # Process and blocks
279
+ if stmt[:and_blocks]
280
+ Array(stmt[:and_blocks]).each do |and_block|
281
+ if and_block[:and_statements]
282
+ process_statements(diagram, and_block[:and_statements])
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ def process_critical(diagram, stmt)
289
+ # Process critical statements
290
+ if stmt[:critical_statements]
291
+ process_statements(diagram, stmt[:critical_statements])
292
+ end
293
+
294
+ # Process option blocks
295
+ if stmt[:option_blocks]
296
+ Array(stmt[:option_blocks]).each do |option_block|
297
+ if option_block[:option_statements]
298
+ process_statements(diagram, option_block[:option_statements])
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ def process_break(diagram, stmt)
305
+ # Process break statements
306
+ if stmt[:break_statements]
307
+ process_statements(diagram, stmt[:break_statements])
308
+ end
309
+ end
310
+
311
+ def ensure_participant(diagram, participant_id)
312
+ return if diagram.find_participant(participant_id)
313
+
314
+ participant = Diagram::SequenceParticipant.new.tap do |p|
315
+ p.id = participant_id
316
+ p.label = participant_id
317
+ p.actor_type = 'participant'
318
+ end
319
+
320
+ diagram.participants << participant
321
+ end
322
+
323
+ def extract_text(value)
324
+ case value
325
+ when Hash
326
+ if value[:string]
327
+ value[:string].to_s
328
+ elsif value[:message_text]
329
+ value[:message_text].to_s
330
+ else
331
+ value.values.first.to_s
332
+ end
333
+ when String
334
+ value
335
+ else
336
+ value.to_s
337
+ end.strip
338
+ end
339
+ end
340
+ end
341
+ end
342
+ end
@@ -0,0 +1,292 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../diagram/state_diagram'
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for converting Parslet parse tree to State diagram model.
9
+ #
10
+ # Converts the parse tree output from Grammars::StateDiagram into a
11
+ # fully-formed Diagram::StateDiagram object with states and transitions.
12
+ class StateDiagram
13
+ # Special state markers
14
+ START_END_MARKER = '[*]'
15
+
16
+ # Transform parse tree into State diagram.
17
+ #
18
+ # @param tree [Array, Hash] Parslet parse tree
19
+ # @return [Diagram::StateDiagram] the State diagram model
20
+ def apply(tree)
21
+ @diagram = Diagram::StateDiagram.new
22
+ @state_counter = 0
23
+
24
+ # Tree is an array: [header, direction, ...statements]
25
+ if tree.is_a?(Array)
26
+ tree.each do |item|
27
+ process_item(item) if item.is_a?(Hash)
28
+ end
29
+ elsif tree.is_a?(Hash)
30
+ process_item(tree)
31
+ end
32
+
33
+ @diagram
34
+ end
35
+
36
+ private
37
+
38
+ def process_item(item)
39
+ return unless item.is_a?(Hash)
40
+
41
+ # Process header to get direction
42
+ if item[:direction] && item[:direction][:dir_value]
43
+ @diagram.direction = extract_text(item[:direction][:dir_value])
44
+ end
45
+
46
+ # Process statements
47
+ process_statement(item) unless item[:header] || item[:direction]
48
+ end
49
+
50
+ def process_statement(stmt)
51
+ return unless stmt.is_a?(Hash)
52
+
53
+ if stmt[:keyword] == 'state' && stmt[:state_id]
54
+ # State declaration
55
+ process_state_declaration(stmt)
56
+ elsif stmt[:from] && stmt[:to]
57
+ # Transition
58
+ process_transition(stmt)
59
+ elsif stmt[:note_keyword]
60
+ # Note statement - skip for now
61
+ nil
62
+ elsif stmt[:state_id] && !stmt[:keyword]
63
+ # Standalone state
64
+ ensure_state_exists(extract_text(stmt[:state_id]))
65
+ elsif stmt[:concurrent_sep]
66
+ # Concurrent separator - handled in composite context
67
+ nil
68
+ end
69
+ end
70
+
71
+ def process_state_declaration(stmt)
72
+ state_id = extract_text(stmt[:state_id])
73
+
74
+ if stmt[:marker]
75
+ # Special state marker (choice, fork, join)
76
+ marker_type = extract_text(stmt[:marker][:marker_type])
77
+ add_special_state(state_id, marker_type)
78
+ elsif stmt[:composite]
79
+ # Composite state with nested statements
80
+ process_composite_state(state_id, stmt[:composite])
81
+ elsif stmt[:description]
82
+ # State with description
83
+ description = extract_text(stmt[:description]).strip
84
+ # Remove leading colon if present
85
+ description = description.sub(/^:\s*/, '')
86
+ add_or_update_state(state_id, state_id, description)
87
+ else
88
+ # Regular state
89
+ ensure_state_exists(state_id)
90
+ end
91
+ end
92
+
93
+ def process_transition(stmt)
94
+ # Get source state (if [*], it's a start state)
95
+ from_id = extract_state_id(stmt[:from], is_source: true)
96
+
97
+ # Get target state (if [*], it's an end state)
98
+ to_id = extract_state_id(stmt[:to], is_source: false)
99
+
100
+ # Parse label for trigger and guard
101
+ trigger, guard = parse_transition_label(stmt[:label])
102
+
103
+ # Create transition
104
+ create_transition(from_id, to_id, trigger, guard)
105
+
106
+ # Handle chained transitions
107
+ if stmt[:chain] && !stmt[:chain].empty?
108
+ chain_transitions = Array(stmt[:chain])
109
+ current_from = to_id
110
+
111
+ chain_transitions.each do |chain_item|
112
+ next unless chain_item[:chain_to]
113
+
114
+ chain_to = extract_state_id(chain_item[:chain_to], is_source: false)
115
+ create_transition(current_from, chain_to, nil, nil)
116
+ current_from = chain_to
117
+ end
118
+ end
119
+ end
120
+
121
+ def process_composite_state(parent_id, composite_data)
122
+ # Create parent state
123
+ parent_state = add_or_update_state(parent_id, parent_id)
124
+
125
+ # Process nested statements
126
+ if composite_data.is_a?(Hash) || composite_data.is_a?(Array)
127
+ statements = Array(composite_data)
128
+ statements.each do |stmt|
129
+ next unless stmt.is_a?(Hash)
130
+
131
+ process_statement(stmt)
132
+ end
133
+ end
134
+
135
+ parent_state
136
+ end
137
+
138
+ def extract_state_id(state_data, is_source: nil)
139
+ # Convert to string for comparison
140
+ state_str = extract_text(state_data)
141
+
142
+ # Check if this is a start/end marker
143
+ if state_str == START_END_MARKER || state_str.strip == START_END_MARKER
144
+ # If it's a source of transition, it's a start state
145
+ # If it's a target of transition, it's an end state
146
+ if is_source
147
+ ensure_start_state
148
+ else
149
+ ensure_end_state
150
+ end
151
+ else
152
+ state_str
153
+ end
154
+ end
155
+
156
+ def ensure_start_or_end_state
157
+ # Check if we already have a start state, if not create one
158
+ # Otherwise create an end state
159
+ start_state = @diagram.start_state
160
+ if start_state
161
+ ensure_end_state
162
+ else
163
+ ensure_start_state
164
+ end
165
+ end
166
+
167
+ def ensure_start_state
168
+ start_state = @diagram.start_state
169
+ return start_state.id if start_state
170
+
171
+ start_id = generate_state_id('start')
172
+ state = Diagram::StateNode.new.tap do |s|
173
+ s.id = start_id
174
+ s.label = START_END_MARKER
175
+ s.state_type = 'start'
176
+ end
177
+ @diagram.states << state
178
+ start_id
179
+ end
180
+
181
+ def ensure_end_state
182
+ end_state = @diagram.end_states.first
183
+ return end_state.id if end_state
184
+
185
+ end_id = generate_state_id('end')
186
+ state = Diagram::StateNode.new.tap do |s|
187
+ s.id = end_id
188
+ s.label = START_END_MARKER
189
+ s.state_type = 'end'
190
+ end
191
+ @diagram.states << state
192
+ end_id
193
+ end
194
+
195
+ def ensure_state_exists(state_id)
196
+ return if @diagram.find_state(state_id)
197
+
198
+ state = Diagram::StateNode.new.tap do |s|
199
+ s.id = state_id
200
+ s.label = state_id
201
+ s.state_type = 'normal'
202
+ end
203
+ @diagram.states << state
204
+ end
205
+
206
+ def add_special_state(state_id, state_type)
207
+ state = Diagram::StateNode.new.tap do |s|
208
+ s.id = state_id
209
+ s.label = state_id
210
+ s.state_type = state_type
211
+ end
212
+ @diagram.states << state
213
+ end
214
+
215
+ def add_or_update_state(state_id, label, description = nil)
216
+ existing = @diagram.find_state(state_id)
217
+ if existing
218
+ existing.label = label unless label.to_s.empty?
219
+ existing.description = description if description
220
+ existing
221
+ else
222
+ state = Diagram::StateNode.new.tap do |s|
223
+ s.id = state_id
224
+ s.label = label
225
+ s.state_type = 'normal'
226
+ s.description = description
227
+ end
228
+ @diagram.states << state
229
+ state
230
+ end
231
+ end
232
+
233
+ def create_transition(from_id, to_id, trigger = nil, guard = nil)
234
+ # Ensure both states exist
235
+ ensure_state_exists(from_id) unless from_id.start_with?('start_', 'end_')
236
+ ensure_state_exists(to_id) unless to_id.start_with?('start_', 'end_')
237
+
238
+ transition = Diagram::StateTransition.new.tap do |t|
239
+ t.from_id = from_id
240
+ t.to_id = to_id
241
+ t.trigger = trigger
242
+ t.guard_condition = guard
243
+ end
244
+ @diagram.transitions << transition
245
+ end
246
+
247
+ def parse_transition_label(label_data)
248
+ return [nil, nil] unless label_data
249
+
250
+ label_text = extract_text(label_data[:label_text] || label_data).strip
251
+ return [nil, nil] if label_text.empty?
252
+
253
+ # Parse trigger and guard from label
254
+ # Format: "trigger [guard]" or just "trigger"
255
+ if label_text =~ /^(.+?)\s*\[(.+?)\]\s*$/
256
+ trigger = Regexp.last_match(1).strip
257
+ guard = Regexp.last_match(2).strip
258
+ [trigger, guard]
259
+ else
260
+ [label_text, nil]
261
+ end
262
+ end
263
+
264
+ def generate_state_id(prefix)
265
+ @state_counter += 1
266
+ "#{prefix}_#{@state_counter}"
267
+ end
268
+
269
+ def extract_text(value)
270
+ case value
271
+ when Hash
272
+ if value[:string]
273
+ value[:string].to_s
274
+ elsif value[:marker_type]
275
+ value[:marker_type].to_s
276
+ elsif value[:dir_value]
277
+ value[:dir_value].to_s
278
+ elsif value[:label_text]
279
+ value[:label_text].to_s
280
+ else
281
+ value.values.first.to_s
282
+ end
283
+ when String
284
+ value
285
+ else
286
+ value.to_s
287
+ end
288
+ end
289
+ end
290
+ end
291
+ end
292
+ end