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,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+ require_relative 'base'
5
+
6
+ module Sirena
7
+ module Diagram
8
+ # Represents a sequence diagram participant.
9
+ #
10
+ # A participant represents an actor or entity in the sequence diagram
11
+ # that can send and receive messages.
12
+ class SequenceParticipant < Lutaml::Model::Serializable
13
+ # Unique identifier for the participant
14
+ attribute :id, :string
15
+
16
+ # Display text/label for the participant
17
+ attribute :label, :string
18
+
19
+ # Participant type: :participant, :actor
20
+ attribute :actor_type, :string
21
+
22
+ # Initialize with default actor type
23
+ def initialize(*args)
24
+ super
25
+ self.actor_type ||= 'participant'
26
+ end
27
+
28
+ # Validates the participant has required attributes.
29
+ #
30
+ # @return [Boolean] true if participant is valid
31
+ def valid?
32
+ !id.nil? && !id.empty? && !label.nil?
33
+ end
34
+ end
35
+
36
+ # Represents a sequence diagram message.
37
+ #
38
+ # A message represents communication between participants in a
39
+ # sequence diagram, with various arrow types and optional text.
40
+ class SequenceMessage < Lutaml::Model::Serializable
41
+ # Source participant identifier
42
+ attribute :from_id, :string
43
+
44
+ # Target participant identifier
45
+ attribute :to_id, :string
46
+
47
+ # Message text content
48
+ attribute :message_text, :string
49
+
50
+ # Arrow type: :solid, :dotted, :async, :async_dotted,
51
+ # :solid_cross, :dotted_cross
52
+ attribute :arrow_type, :string
53
+
54
+ # Initialize with default arrow type
55
+ def initialize(*args)
56
+ super
57
+ self.arrow_type ||= 'solid'
58
+ end
59
+
60
+ # Validates the message has required attributes.
61
+ #
62
+ # @return [Boolean] true if message is valid
63
+ def valid?
64
+ !from_id.nil? && !from_id.empty? &&
65
+ !to_id.nil? && !to_id.empty?
66
+ end
67
+ end
68
+
69
+ # Represents an activation box (lifecycle) in a sequence diagram.
70
+ #
71
+ # An activation shows when a participant is active or processing.
72
+ class SequenceActivation < Lutaml::Model::Serializable
73
+ # Participant identifier
74
+ attribute :participant_id, :string
75
+
76
+ # Start message index (position in messages array)
77
+ attribute :start_index, :integer
78
+
79
+ # End message index (position in messages array)
80
+ attribute :end_index, :integer
81
+
82
+ # Validates the activation has required attributes.
83
+ #
84
+ # @return [Boolean] true if activation is valid
85
+ def valid?
86
+ !participant_id.nil? && !participant_id.empty? &&
87
+ !start_index.nil? && !end_index.nil? &&
88
+ start_index <= end_index
89
+ end
90
+ end
91
+
92
+ # Represents a note in a sequence diagram.
93
+ #
94
+ # A note provides additional context or information in the diagram.
95
+ class SequenceNote < Lutaml::Model::Serializable
96
+ # Note text content
97
+ attribute :text, :string
98
+
99
+ # Position: :left_of, :right_of, :over
100
+ attribute :position, :string
101
+
102
+ # Participant identifier(s) - array for :over with multiple
103
+ attribute :participant_ids, :string, collection: true,
104
+ default: -> { [] }
105
+
106
+ # Message index where the note appears
107
+ attribute :message_index, :integer
108
+
109
+ # Validates the note has required attributes.
110
+ #
111
+ # @return [Boolean] true if note is valid
112
+ def valid?
113
+ !text.nil? && !text.empty? &&
114
+ !position.nil? && !participant_ids.empty?
115
+ end
116
+ end
117
+
118
+ # Sequence diagram model.
119
+ #
120
+ # Represents a complete sequence diagram with participants, messages,
121
+ # activations, and notes. Sequence diagrams show interactions between
122
+ # participants over time in a vertical timeline format.
123
+ #
124
+ # @example Creating a simple sequence diagram
125
+ # sequence = Sequence.new
126
+ # sequence.participants << SequenceParticipant.new(
127
+ # id: 'Alice',
128
+ # label: 'Alice',
129
+ # actor_type: 'actor'
130
+ # )
131
+ # sequence.participants << SequenceParticipant.new(
132
+ # id: 'Bob',
133
+ # label: 'Bob',
134
+ # actor_type: 'actor'
135
+ # )
136
+ # sequence.messages << SequenceMessage.new(
137
+ # from_id: 'Alice',
138
+ # to_id: 'Bob',
139
+ # message_text: 'Hello Bob',
140
+ # arrow_type: 'solid'
141
+ # )
142
+ class Sequence < Base
143
+ # Collection of participants in the sequence diagram
144
+ attribute :participants, SequenceParticipant, collection: true,
145
+ default: -> { [] }
146
+
147
+ # Collection of messages between participants
148
+ attribute :messages, SequenceMessage, collection: true,
149
+ default: -> { [] }
150
+
151
+ # Collection of activation boxes
152
+ attribute :activations, SequenceActivation, collection: true,
153
+ default: -> { [] }
154
+
155
+ # Collection of notes
156
+ attribute :notes, SequenceNote, collection: true,
157
+ default: -> { [] }
158
+
159
+ # Returns the diagram type identifier.
160
+ #
161
+ # @return [Symbol] :sequence
162
+ def diagram_type
163
+ :sequence
164
+ end
165
+
166
+ # Validates the sequence diagram structure.
167
+ #
168
+ # A sequence diagram is valid if:
169
+ # - It has at least one participant
170
+ # - All participants are valid
171
+ # - All messages are valid
172
+ # - All message references point to existing participants
173
+ # - All activations are valid
174
+ # - All notes are valid
175
+ #
176
+ # @return [Boolean] true if sequence diagram is valid
177
+ def valid?
178
+ return false if participants.nil? || participants.empty?
179
+ return false unless participants.all?(&:valid?)
180
+ return false unless messages.nil? || messages.all?(&:valid?)
181
+ return false unless activations.nil? || activations.all?(&:valid?)
182
+ return false unless notes.nil? || notes.all?(&:valid?)
183
+
184
+ # Validate message references
185
+ participant_ids = participants.map(&:id)
186
+ messages&.each do |message|
187
+ return false unless participant_ids.include?(message.from_id)
188
+ return false unless participant_ids.include?(message.to_id)
189
+ end
190
+
191
+ # Validate activation references
192
+ activations&.each do |activation|
193
+ return false unless participant_ids.include?(
194
+ activation.participant_id
195
+ )
196
+ end
197
+
198
+ # Validate note references
199
+ notes&.each do |note|
200
+ note.participant_ids.each do |pid|
201
+ return false unless participant_ids.include?(pid)
202
+ end
203
+ end
204
+
205
+ true
206
+ end
207
+
208
+ # Finds a participant by its identifier.
209
+ #
210
+ # @param id [String] the participant identifier to find
211
+ # @return [SequenceParticipant, nil] the participant or nil if not
212
+ # found
213
+ def find_participant(id)
214
+ participants.find { |p| p.id == id }
215
+ end
216
+
217
+ # Finds all messages from a specific participant.
218
+ #
219
+ # @param participant_id [String] the source participant identifier
220
+ # @return [Array<SequenceMessage>] messages from the participant
221
+ def messages_from(participant_id)
222
+ messages.select { |m| m.from_id == participant_id }
223
+ end
224
+
225
+ # Finds all messages to a specific participant.
226
+ #
227
+ # @param participant_id [String] the target participant identifier
228
+ # @return [Array<SequenceMessage>] messages to the participant
229
+ def messages_to(participant_id)
230
+ messages.select { |m| m.to_id == participant_id }
231
+ end
232
+
233
+ # Finds activations for a specific participant.
234
+ #
235
+ # @param participant_id [String] the participant identifier
236
+ # @return [Array<SequenceActivation>] activations for the participant
237
+ def activations_for(participant_id)
238
+ activations.select { |a| a.participant_id == participant_id }
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+ require_relative 'base'
5
+
6
+ module Sirena
7
+ module Diagram
8
+ # Represents a state node in a state diagram.
9
+ #
10
+ # A state node can represent different types of states in a state
11
+ # machine, including normal states, start/end states, choice points,
12
+ # and fork/join nodes.
13
+ class StateNode < Lutaml::Model::Serializable
14
+ # Unique identifier for the state
15
+ attribute :id, :string
16
+
17
+ # Display label for the state
18
+ attribute :label, :string
19
+
20
+ # State type: :normal, :start, :end, :choice, :fork, :join
21
+ attribute :state_type, :string
22
+
23
+ # Optional description for composite states
24
+ attribute :description, :string
25
+
26
+ # Child states for composite/nested states
27
+ attribute :children, StateNode, collection: true, default: -> { [] }
28
+
29
+ # Initialize with default state type
30
+ def initialize(*args)
31
+ super
32
+ self.state_type ||= 'normal'
33
+ end
34
+
35
+ # Validates the state node has required attributes.
36
+ #
37
+ # @return [Boolean] true if state node is valid
38
+ def valid?
39
+ !id.nil? && !id.empty? &&
40
+ !state_type.nil? && !state_type.empty?
41
+ end
42
+
43
+ # Checks if this is a start state.
44
+ #
45
+ # @return [Boolean] true if start state
46
+ def start_state?
47
+ state_type == 'start'
48
+ end
49
+
50
+ # Checks if this is an end state.
51
+ #
52
+ # @return [Boolean] true if end state
53
+ def end_state?
54
+ state_type == 'end'
55
+ end
56
+
57
+ # Checks if this is a choice state.
58
+ #
59
+ # @return [Boolean] true if choice state
60
+ def choice_state?
61
+ state_type == 'choice'
62
+ end
63
+
64
+ # Checks if this is a fork state.
65
+ #
66
+ # @return [Boolean] true if fork state
67
+ def fork_state?
68
+ state_type == 'fork'
69
+ end
70
+
71
+ # Checks if this is a join state.
72
+ #
73
+ # @return [Boolean] true if join state
74
+ def join_state?
75
+ state_type == 'join'
76
+ end
77
+
78
+ # Checks if this is a composite state.
79
+ #
80
+ # @return [Boolean] true if has child states
81
+ def composite_state?
82
+ !children.nil? && !children.empty?
83
+ end
84
+ end
85
+
86
+ # Represents a state transition in a state diagram.
87
+ #
88
+ # A transition connects two states with optional trigger event
89
+ # and guard condition.
90
+ class StateTransition < Lutaml::Model::Serializable
91
+ # Source state identifier
92
+ attribute :from_id, :string
93
+
94
+ # Target state identifier
95
+ attribute :to_id, :string
96
+
97
+ # Optional trigger event for the transition
98
+ attribute :trigger, :string
99
+
100
+ # Optional guard condition for the transition
101
+ attribute :guard_condition, :string
102
+
103
+ # Validates the transition has required attributes.
104
+ #
105
+ # @return [Boolean] true if transition is valid
106
+ def valid?
107
+ !from_id.nil? && !from_id.empty? &&
108
+ !to_id.nil? && !to_id.empty?
109
+ end
110
+
111
+ # Returns the full transition label.
112
+ #
113
+ # @return [String] formatted label with trigger and guard
114
+ def label
115
+ parts = []
116
+ parts << trigger if trigger && !trigger.empty?
117
+ parts << "[#{guard_condition}]" if guard_condition && !guard_condition.empty?
118
+ parts.join(' ')
119
+ end
120
+ end
121
+
122
+ # State diagram model.
123
+ #
124
+ # Represents a complete state machine diagram with states and
125
+ # transitions. State diagrams show the dynamic behavior of a
126
+ # system through states, events, and transitions.
127
+ #
128
+ # @example Creating a simple state diagram
129
+ # diagram = StateDiagram.new(direction: 'TB')
130
+ # diagram.states << StateNode.new(
131
+ # id: 'start',
132
+ # label: '[*]',
133
+ # state_type: 'start'
134
+ # )
135
+ # diagram.states << StateNode.new(
136
+ # id: 'idle',
137
+ # label: 'Idle',
138
+ # state_type: 'normal'
139
+ # )
140
+ # diagram.transitions << StateTransition.new(
141
+ # from_id: 'start',
142
+ # to_id: 'idle'
143
+ # )
144
+ class StateDiagram < Base
145
+ # Collection of states in the diagram
146
+ attribute :states, StateNode, collection: true, default: -> { [] }
147
+
148
+ # Collection of transitions between states
149
+ attribute :transitions, StateTransition, collection: true,
150
+ default: -> { [] }
151
+
152
+ # Returns the diagram type identifier.
153
+ #
154
+ # @return [Symbol] :state_diagram
155
+ def diagram_type
156
+ :state_diagram
157
+ end
158
+
159
+ # Validates the state diagram structure.
160
+ #
161
+ # A state diagram is valid if:
162
+ # - It has at least one state
163
+ # - All states are valid
164
+ # - All transitions are valid
165
+ # - All transition references point to existing states
166
+ #
167
+ # @return [Boolean] true if state diagram is valid
168
+ def valid?
169
+ return false if states.nil? || states.empty?
170
+ return false unless states.all?(&:valid?)
171
+ return false unless transitions.nil? ||
172
+ transitions.all?(&:valid?)
173
+
174
+ # Validate transition references
175
+ state_ids = states.map(&:id)
176
+ transitions&.each do |transition|
177
+ return false unless state_ids.include?(transition.from_id)
178
+ return false unless state_ids.include?(transition.to_id)
179
+ end
180
+
181
+ true
182
+ end
183
+
184
+ # Finds a state by its identifier.
185
+ #
186
+ # @param id [String] the state identifier to find
187
+ # @return [StateNode, nil] the state or nil if not found
188
+ def find_state(id)
189
+ states.find { |s| s.id == id }
190
+ end
191
+
192
+ # Finds all transitions originating from a specific state.
193
+ #
194
+ # @param state_id [String] the source state identifier
195
+ # @return [Array<StateTransition>] transitions from the state
196
+ def transitions_from(state_id)
197
+ transitions.select { |t| t.from_id == state_id }
198
+ end
199
+
200
+ # Finds all transitions targeting a specific state.
201
+ #
202
+ # @param state_id [String] the target state identifier
203
+ # @return [Array<StateTransition>] transitions to the state
204
+ def transitions_to(state_id)
205
+ transitions.select { |t| t.to_id == state_id }
206
+ end
207
+
208
+ # Finds the start state.
209
+ #
210
+ # @return [StateNode, nil] the start state or nil if not found
211
+ def start_state
212
+ states.find(&:start_state?)
213
+ end
214
+
215
+ # Finds all end states.
216
+ #
217
+ # @return [Array<StateNode>] end states
218
+ def end_states
219
+ states.select(&:end_state?)
220
+ end
221
+
222
+ # Finds all composite states.
223
+ #
224
+ # @return [Array<StateNode>] composite states
225
+ def composite_states
226
+ states.select(&:composite_state?)
227
+ end
228
+
229
+ # Finds all choice states.
230
+ #
231
+ # @return [Array<StateNode>] choice states
232
+ def choice_states
233
+ states.select(&:choice_state?)
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "base"
5
+
6
+ module Sirena
7
+ module Diagram
8
+ # Represents an event in a timeline.
9
+ #
10
+ # An event represents a point in time with associated description(s).
11
+ # Events can have multiple descriptions (when multiple things happened
12
+ # at the same time).
13
+ class TimelineEvent < Lutaml::Model::Serializable
14
+ # The time/date/year for this event
15
+ attribute :time, :string
16
+
17
+ # Event descriptions (can be multiple for same time)
18
+ attribute :descriptions, :string, collection: true, default: -> { [] }
19
+
20
+ # Validates the event has required attributes.
21
+ #
22
+ # @return [Boolean] true if event is valid
23
+ def valid?
24
+ !time.nil? && !time.empty? && !descriptions.empty?
25
+ end
26
+
27
+ # Get primary description (first one).
28
+ #
29
+ # @return [String, nil] the first description or nil
30
+ def primary_description
31
+ descriptions.first
32
+ end
33
+
34
+ # Check if event has multiple descriptions.
35
+ #
36
+ # @return [Boolean] true if more than one description
37
+ def multiple_descriptions?
38
+ descriptions.size > 1
39
+ end
40
+ end
41
+
42
+ # Represents a section/period in a timeline.
43
+ #
44
+ # A section groups related events together, typically representing
45
+ # a period, category, or theme.
46
+ class TimelineSection < Lutaml::Model::Serializable
47
+ # Name/title of this section
48
+ attribute :name, :string
49
+
50
+ # Events within this section
51
+ attribute :events, TimelineEvent, collection: true,
52
+ default: -> { [] }
53
+
54
+ # Task names (for sections without timestamps)
55
+ attribute :tasks, :string, collection: true, default: -> { [] }
56
+
57
+ def initialize(name = nil)
58
+ super()
59
+ @name = name
60
+ @events = []
61
+ @tasks = []
62
+ end
63
+
64
+ # Validates the section.
65
+ #
66
+ # @return [Boolean] true if section has a name
67
+ def valid?
68
+ !name.nil? && !name.empty?
69
+ end
70
+
71
+ # Check if section has events.
72
+ #
73
+ # @return [Boolean] true if section has events
74
+ def has_events?
75
+ !events.empty?
76
+ end
77
+
78
+ # Check if section has tasks.
79
+ #
80
+ # @return [Boolean] true if section has tasks
81
+ def has_tasks?
82
+ !tasks.empty?
83
+ end
84
+ end
85
+
86
+ # Timeline diagram model.
87
+ #
88
+ # Represents a timeline showing chronological events, optionally
89
+ # grouped into sections. Timelines are useful for showing historical
90
+ # progression, project milestones, or any time-based sequence.
91
+ #
92
+ # @example Creating a simple timeline
93
+ # timeline = Timeline.new
94
+ # timeline.title = 'History of Computing'
95
+ #
96
+ # section = TimelineSection.new('Early Era')
97
+ # event = TimelineEvent.new
98
+ # event.time = '1936'
99
+ # event.descriptions << 'Turing machine'
100
+ # section.events << event
101
+ #
102
+ # timeline.sections << section
103
+ class Timeline < Base
104
+ # Collection of sections in the timeline
105
+ attribute :sections, TimelineSection, collection: true,
106
+ default: -> { [] }
107
+
108
+ # Collection of events not in any section
109
+ attribute :events, TimelineEvent, collection: true,
110
+ default: -> { [] }
111
+
112
+ # Accessibility title (for screen readers)
113
+ attribute :acc_title, :string
114
+
115
+ # Accessibility description (for screen readers)
116
+ attribute :acc_description, :string
117
+
118
+ def initialize
119
+ @sections = []
120
+ @events = []
121
+ super
122
+ end
123
+
124
+ # Returns the diagram type identifier.
125
+ #
126
+ # @return [Symbol] :timeline
127
+ def diagram_type
128
+ :timeline
129
+ end
130
+
131
+ # Validates the timeline structure.
132
+ #
133
+ # A timeline is always valid (can be empty for parsing tests).
134
+ # In practice, useful timelines would have events or sections.
135
+ #
136
+ # @return [Boolean] true if timeline is valid
137
+ def valid?
138
+ true
139
+ end
140
+
141
+ # Check if timeline has any events.
142
+ #
143
+ # @return [Boolean] true if has events (in sections or standalone)
144
+ def has_events?
145
+ !events.empty? || sections.any?(&:has_events?)
146
+ end
147
+
148
+ # Check if timeline has sections.
149
+ #
150
+ # @return [Boolean] true if has sections
151
+ def has_sections?
152
+ !sections.empty?
153
+ end
154
+
155
+ # Get all events across all sections and standalone.
156
+ #
157
+ # @return [Array<TimelineEvent>] all events
158
+ def all_events
159
+ section_events = sections.flat_map(&:events)
160
+ events + section_events
161
+ end
162
+
163
+ # Get all unique time values from events.
164
+ #
165
+ # @return [Array<String>] sorted array of unique times
166
+ def all_times
167
+ all_events.map(&:time).uniq.sort
168
+ end
169
+ end
170
+ end
171
+ end