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,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../diagram/er_diagram'
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for converting Parslet parse tree to ER diagram model.
9
+ #
10
+ # Converts the parse tree output from Grammars::ErDiagram into a
11
+ # fully-formed Diagram::ErDiagram object with entities, attributes,
12
+ # and relationships.
13
+ class ErDiagram
14
+ # Cardinality symbol mappings
15
+ CARDINALITY_SYMBOLS = {
16
+ '||' => 'one',
17
+ 'o{' => 'zero_or_more',
18
+ '|{' => 'one_or_more',
19
+ '}o' => 'zero_or_one',
20
+ '{o' => 'zero_or_more',
21
+ '{|' => 'one_or_more',
22
+ '}{' => 'one_or_more',
23
+ '{}' => 'one_or_more'
24
+ }.freeze
25
+
26
+ # Transform parse tree into ER diagram.
27
+ #
28
+ # @param tree [Array, Hash] Parslet parse tree
29
+ # @return [Diagram::ErDiagram] the ER diagram model
30
+ def apply(tree)
31
+ diagram = Diagram::ErDiagram.new
32
+
33
+ # Tree is an array: [header, ...statements]
34
+ if tree.is_a?(Array)
35
+ tree.each do |item|
36
+ next if item.is_a?(Hash) && item[:header] # Skip header
37
+ process_statement(diagram, item) if item.is_a?(Hash)
38
+ end
39
+ elsif tree.is_a?(Hash) && tree[:statements]
40
+ process_statements(diagram, tree[:statements])
41
+ end
42
+
43
+ diagram
44
+ end
45
+
46
+ private
47
+
48
+ def process_statements(diagram, statements)
49
+ Array(statements).each do |stmt|
50
+ process_statement(diagram, stmt) if stmt.is_a?(Hash)
51
+ end
52
+ end
53
+
54
+ def process_statement(diagram, stmt)
55
+ return unless stmt.is_a?(Hash)
56
+
57
+ if stmt[:entity_id] && stmt[:attributes]
58
+ # Entity definition with attributes
59
+ process_entity_definition(diagram, stmt)
60
+ elsif stmt[:from_id] && stmt[:to_id] && stmt[:pattern]
61
+ # Relationship
62
+ process_relationship(diagram, stmt)
63
+ elsif stmt[:entity_id]
64
+ # Stand-alone entity declaration
65
+ ensure_entity(diagram, stmt[:entity_id].to_s)
66
+ end
67
+ end
68
+
69
+ def process_entity_definition(diagram, stmt)
70
+ entity_id = stmt[:entity_id].to_s
71
+ entity = find_or_create_entity(diagram, entity_id)
72
+
73
+ # Process attributes
74
+ if stmt[:attributes]
75
+ attributes = Array(stmt[:attributes])
76
+ attributes.each do |attr_data|
77
+ next unless attr_data.is_a?(Hash)
78
+
79
+ attribute = Diagram::ErAttribute.new.tap do |attr|
80
+ attr.name = attr_data[:name].to_s
81
+ attr.attribute_type = extract_text(attr_data[:type]) if attr_data[:type]
82
+ attr.key_type = extract_key_type(attr_data[:key]) if attr_data[:key]
83
+ end
84
+
85
+ entity.attributes << attribute
86
+ end
87
+ end
88
+ end
89
+
90
+ def process_relationship(diagram, stmt)
91
+ from_id = stmt[:from_id].to_s
92
+ to_id = stmt[:to_id].to_s
93
+ pattern = stmt[:pattern]
94
+
95
+ # Ensure entities exist
96
+ ensure_entity(diagram, from_id)
97
+ ensure_entity(diagram, to_id)
98
+
99
+ # Parse relationship pattern
100
+ card_from = extract_text(pattern[:card_from])
101
+ card_to = extract_text(pattern[:card_to])
102
+ operator = extract_text(pattern[:operator])
103
+
104
+ # Map cardinalities
105
+ card_from_val = CARDINALITY_SYMBOLS[card_from]
106
+ card_to_val = CARDINALITY_SYMBOLS[card_to]
107
+
108
+ # Determine relationship type from operator
109
+ rel_type = operator == '==' ? 'identifying' : 'non-identifying'
110
+
111
+ # Extract label if present
112
+ label = nil
113
+ if stmt[:label] && stmt[:label][:label_text]
114
+ label = extract_text(stmt[:label][:label_text]).strip
115
+ end
116
+
117
+ relationship = Diagram::ErRelationship.new.tap do |rel|
118
+ rel.from_id = from_id
119
+ rel.to_id = to_id
120
+ rel.relationship_type = rel_type
121
+ rel.cardinality_from = card_from_val
122
+ rel.cardinality_to = card_to_val
123
+ rel.label = label unless label.nil? || label.empty?
124
+ end
125
+
126
+ diagram.relationships << relationship
127
+ end
128
+
129
+ def find_or_create_entity(diagram, entity_id)
130
+ existing = diagram.find_entity(entity_id)
131
+ return existing if existing
132
+
133
+ entity = Diagram::ErEntity.new.tap do |e|
134
+ e.id = entity_id
135
+ e.name = entity_id
136
+ end
137
+ diagram.entities << entity
138
+ entity
139
+ end
140
+
141
+ def ensure_entity(diagram, entity_id)
142
+ find_or_create_entity(diagram, entity_id)
143
+ end
144
+
145
+ def extract_text(value)
146
+ case value
147
+ when Hash
148
+ if value[:string]
149
+ value[:string].to_s
150
+ elsif value[:key_type]
151
+ value[:key_type].to_s
152
+ else
153
+ value.values.first.to_s
154
+ end
155
+ when String
156
+ value
157
+ else
158
+ value.to_s
159
+ end
160
+ end
161
+
162
+ def extract_key_type(value)
163
+ text = extract_text(value)
164
+ text if %w[PK FK UK].include?(text)
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../diagram/error'
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for converting Parslet parse tree to Error diagram model.
9
+ #
10
+ # Converts the parse tree output from Grammars::Error into a
11
+ # fully-formed Diagram::Error object.
12
+ class Error
13
+ # Transform parse tree into Error diagram.
14
+ #
15
+ # @param tree [Array, Hash] Parslet parse tree
16
+ # @return [Diagram::Error] the error diagram model
17
+ def apply(tree)
18
+ diagram = Diagram::Error.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_message(diagram, item) if item.key?(:message)
26
+ end
27
+ elsif tree.is_a?(Hash)
28
+ process_message(diagram, tree) if tree.key?(:message)
29
+ end
30
+
31
+ diagram
32
+ end
33
+
34
+ private
35
+
36
+ def process_message(diagram, item)
37
+ message = item[:message]
38
+ return unless message
39
+
40
+ # Extract message text
41
+ message_text = extract_text(message)
42
+ diagram.message = message_text unless message_text.empty?
43
+ end
44
+
45
+ def extract_text(value)
46
+ case value
47
+ when Hash
48
+ value.values.first.to_s
49
+ when String
50
+ value
51
+ else
52
+ value.to_s
53
+ end.strip
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,293 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parslet'
4
+ require_relative '../../diagram/flowchart'
5
+
6
+ module Sirena
7
+ module Parser
8
+ module Transforms
9
+ # Transform for converting flowchart parse trees to diagram models.
10
+ #
11
+ # Handles transformation of nodes, edges, subgraphs, and styling
12
+ # directives from Parslet parse trees into Flowchart diagram objects.
13
+ class Flowchart < Parslet::Transform
14
+ # Shape delimiter to type mapping
15
+ SHAPE_MAP = {
16
+ '[]' => 'rect',
17
+ '()' => 'rounded',
18
+ '([])' => 'stadium',
19
+ '[[]]' => 'subroutine',
20
+ '[()]' => 'cylindrical',
21
+ '(())' => 'circle',
22
+ '((()))' => 'double_circle',
23
+ '>]' => 'asymmetric',
24
+ '{}' => 'rhombus',
25
+ '{{}}' => 'hexagon',
26
+ '[//]' => 'parallelogram',
27
+ '[\\\\]' => 'parallelogram_alt',
28
+ '[/\\]' => 'trapezoid',
29
+ '[\\/]' => 'trapezoid_alt'
30
+ }.freeze
31
+
32
+ # Arrow type mapping
33
+ ARROW_MAP = {
34
+ '-->' => 'arrow',
35
+ '->' => 'arrow',
36
+ '---' => 'line',
37
+ '-.>' => 'dotted_arrow',
38
+ '-.-' => 'dotted_arrow',
39
+ '==>' => 'thick_arrow',
40
+ '==' => 'thick_arrow'
41
+ }.freeze
42
+
43
+ # Direction value
44
+ rule(dir_value: simple(:v)) { v.to_s }
45
+
46
+ # Node ID
47
+ rule(node_id: simple(:id)) { id.to_s }
48
+ rule(node_id: { string: simple(:s) }) { s.to_s }
49
+
50
+ # Shape with label
51
+ rule(
52
+ shape: {
53
+ open: simple(:o),
54
+ label: simple(:l),
55
+ close: simple(:c)
56
+ }
57
+ ) do
58
+ delims = "#{o}#{c}"
59
+ {
60
+ shape_type: SHAPE_MAP[delims] || 'rect',
61
+ label: l.to_s.strip
62
+ }
63
+ end
64
+
65
+ # Handle empty labels
66
+ rule(
67
+ shape: {
68
+ open: simple(:o),
69
+ label: sequence(:_),
70
+ close: simple(:c)
71
+ }
72
+ ) do
73
+ delims = "#{o}#{c}"
74
+ {
75
+ shape_type: SHAPE_MAP[delims] || 'rect',
76
+ label: ''
77
+ }
78
+ end
79
+
80
+ # Node with shape
81
+ rule(
82
+ node: {
83
+ node_id: simple(:id),
84
+ shape: subtree(:s)
85
+ }
86
+ ) do
87
+ {
88
+ node_id: id.to_s,
89
+ shape_type: s[:shape_type] || 'rect',
90
+ label: s[:label] || id.to_s
91
+ }
92
+ end
93
+
94
+ # Node with shape and inline class
95
+ rule(
96
+ node: {
97
+ node_id: simple(:id),
98
+ inline_class: simple(:cls),
99
+ shape: subtree(:s)
100
+ }
101
+ ) do
102
+ {
103
+ node_id: id.to_s,
104
+ shape_type: s[:shape_type] || 'rect',
105
+ label: s[:label] || id.to_s,
106
+ classes: cls.to_s
107
+ }
108
+ end
109
+
110
+ # Node without shape
111
+ rule(node: { node_id: simple(:id) }) do
112
+ {
113
+ node_id: id.to_s,
114
+ shape_type: 'rect',
115
+ label: id.to_s
116
+ }
117
+ end
118
+
119
+ # Node with inline class but no shape
120
+ rule(
121
+ node: {
122
+ node_id: simple(:id),
123
+ inline_class: simple(:cls)
124
+ }
125
+ ) do
126
+ {
127
+ node_id: id.to_s,
128
+ shape_type: 'rect',
129
+ label: id.to_s,
130
+ classes: cls.to_s
131
+ }
132
+ end
133
+
134
+ # Arrow types
135
+ rule(arrow: { plain: simple(:a) }) { a.to_s }
136
+ rule(arrow: { dotted: simple(:a) }) { a.to_s }
137
+ rule(arrow: { thick: simple(:a) }) { a.to_s }
138
+
139
+ # Edge label
140
+ rule(label: simple(:l)) { l.to_s.strip }
141
+ rule(label: sequence(:l)) { l.join.strip }
142
+
143
+ # Helper method to create nodes
144
+ def self.create_node(node_data)
145
+ Diagram::FlowchartNode.new.tap do |n|
146
+ n.id = node_data[:node_id]
147
+ n.label = node_data[:label] || node_data[:node_id]
148
+ n.shape = node_data[:shape_type] || 'rect'
149
+ n.classes = node_data[:classes] if node_data[:classes]
150
+ end
151
+ end
152
+
153
+ # Helper method to create edges
154
+ def self.create_edge(source_id, target_data, arrow_type, label = nil)
155
+ Diagram::FlowchartEdge.new.tap do |e|
156
+ e.source_id = source_id
157
+ e.target_id = target_data[:node_id]
158
+ e.arrow_type = ARROW_MAP[arrow_type] || 'arrow'
159
+ # Convert Parslet::Slice to string before checking empty
160
+ label_str = label.to_s if label
161
+ e.label = label_str if label_str && !label_str.empty?
162
+ end
163
+ end
164
+
165
+ # Process parsed diagram
166
+ def self.apply(tree, diagram = nil)
167
+ diagram ||= Diagram::Flowchart.new
168
+
169
+ # Parse tree is an array: [header_element, *statement_elements]
170
+ tree = [tree] unless tree.is_a?(Array)
171
+
172
+ # Extract header (first element)
173
+ header = tree.first
174
+ if header && header.is_a?(Hash) && header[:direction]
175
+ dir_value = header[:direction][:dir_value] || header[:direction]
176
+ diagram.direction = dir_value.to_s if dir_value
177
+ end
178
+
179
+ # Process statements (remaining elements)
180
+ statements = tree[1..-1] || []
181
+
182
+ process_statements(diagram, statements)
183
+
184
+ diagram
185
+ end
186
+
187
+ def self.process_statements(diagram, statements)
188
+ statements.each do |stmt|
189
+ next unless stmt.is_a?(Hash)
190
+
191
+ if stmt[:node]
192
+ # Node with edges
193
+ process_node_edge_statement(diagram, stmt)
194
+ elsif stmt[:node_id]
195
+ # Standalone node
196
+ node_data = { node_id: stmt[:node_id], shape_type: 'rect', label: stmt[:node_id] }
197
+ add_or_update_node(diagram, node_data)
198
+ elsif stmt[:subgraph_keyword]
199
+ # Subgraph (acknowledge but don't fully implement for now)
200
+ # Process subgraph statements recursively
201
+ if stmt[:subgraph_statements]
202
+ sub_stmts = stmt[:subgraph_statements]
203
+ sub_stmts = [sub_stmts] unless sub_stmts.is_a?(Array)
204
+ process_statements(diagram, sub_stmts)
205
+ end
206
+ elsif stmt[:style_keyword] || stmt[:classdef_keyword] ||
207
+ stmt[:class_keyword] || stmt[:click_keyword]
208
+ # Styling directives (acknowledge but don't fully implement)
209
+ # These are parsed but not processed into the model
210
+ end
211
+ end
212
+ end
213
+
214
+ def self.process_node_edge_statement(diagram, stmt)
215
+ node_data = extract_node_data(stmt[:node])
216
+ add_or_update_node(diagram, node_data)
217
+
218
+ # Process edges if present
219
+ edges = stmt[:edges]
220
+ return unless edges
221
+
222
+ edges = [edges] unless edges.is_a?(Array)
223
+ source_id = node_data[:node_id]
224
+
225
+ edges.each do |edge_data|
226
+ next unless edge_data.is_a?(Hash)
227
+
228
+ arrow_type = edge_data[:arrow].to_s
229
+ label = edge_data[:label]
230
+ target_data = edge_data[:target]
231
+
232
+ next unless target_data
233
+
234
+ # Extract and add target node
235
+ target_node_data = extract_node_data(target_data)
236
+ add_or_update_node(diagram, target_node_data)
237
+
238
+ # Create edge
239
+ edge = create_edge(source_id, target_node_data, arrow_type, label)
240
+ diagram.edges << edge
241
+
242
+ # For chaining, next edge source is current target
243
+ source_id = target_node_data[:node_id]
244
+ end
245
+ end
246
+
247
+ def self.add_or_update_node(diagram, node_data)
248
+ return unless node_data
249
+
250
+ existing = diagram.find_node(node_data[:node_id])
251
+ if existing
252
+ # Update existing node - only update if we have non-default values
253
+ existing.label = node_data[:label] if node_data[:label] && !node_data[:label].empty?
254
+ # Only update shape if the new shape is not the default 'rect' or if existing is 'rect'
255
+ if node_data[:shape_type] && node_data[:shape_type] != 'rect'
256
+ existing.shape = node_data[:shape_type]
257
+ end
258
+ existing.classes = node_data[:classes] if node_data[:classes]
259
+ else
260
+ # Add new node
261
+ node = create_node(node_data)
262
+ diagram.nodes << node
263
+ end
264
+ end
265
+
266
+ # Extract node data from parse tree
267
+ def self.extract_node_data(node_hash)
268
+ return nil unless node_hash
269
+
270
+ node_id = node_hash[:node_id].to_s
271
+
272
+ # Extract shape info if present
273
+ if node_hash[:shape]
274
+ shape_data = node_hash[:shape]
275
+ delims = "#{shape_data[:open]}#{shape_data[:close]}"
276
+ shape_type = SHAPE_MAP[delims] || 'rect'
277
+ label = shape_data[:label]&.to_s&.strip || node_id
278
+ else
279
+ shape_type = 'rect'
280
+ label = node_id
281
+ end
282
+
283
+ {
284
+ node_id: node_id,
285
+ shape_type: shape_type,
286
+ label: label,
287
+ classes: node_hash[:inline_class]&.to_s
288
+ }
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end