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,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "grammars/xy_chart"
5
+ require_relative "transforms/xy_chart"
6
+ require_relative "../diagram/xy_chart"
7
+
8
+ module Sirena
9
+ module Parser
10
+ # XY Chart parser for Mermaid xychart-beta diagram syntax.
11
+ #
12
+ # Uses Parslet grammar-based parsing to handle XY chart syntax
13
+ # with axes and multiple datasets.
14
+ #
15
+ # Parses XY charts with support for:
16
+ # - Title
17
+ # - X-axis with categorical or numeric values
18
+ # - Y-axis with range
19
+ # - Multiple datasets (line, bar, or named)
20
+ #
21
+ # @example Parse a simple XY chart
22
+ # parser = XYChartParser.new
23
+ # source = <<~MERMAID
24
+ # xychart-beta
25
+ # title "Sales Revenue"
26
+ # x-axis [jan, feb, mar]
27
+ # y-axis 0 --> 100
28
+ # line [5, 10, 15]
29
+ # MERMAID
30
+ # diagram = parser.parse(source)
31
+ class XYChartParser < Base
32
+ # Parses XY chart diagram source into an XYChart model.
33
+ #
34
+ # @param source [String] the Mermaid XY chart diagram source
35
+ # @return [Diagram::XYChart] the parsed XY chart
36
+ # @raise [ParseError] if syntax is invalid
37
+ def parse(source)
38
+ grammar = Grammars::XYChart.new
39
+
40
+ begin
41
+ parse_tree = grammar.parse(source)
42
+ rescue Parslet::ParseFailed => e
43
+ raise ParseError, "Syntax error at #{e.parse_failure_cause.pos}: " \
44
+ "#{e.parse_failure_cause}"
45
+ end
46
+
47
+ # Transform parse tree to intermediate representation
48
+ transform = Transforms::XYChart.new
49
+ result = transform.apply(parse_tree)
50
+
51
+ # Create the diagram model
52
+ create_diagram(result)
53
+ end
54
+
55
+ private
56
+
57
+ def create_diagram(result)
58
+ diagram = Diagram::XYChart.new
59
+ diagram.title = result[:title]
60
+
61
+ # Create X-axis
62
+ if result[:x_axis]
63
+ diagram.x_axis = create_x_axis(result[:x_axis])
64
+ end
65
+
66
+ # Create Y-axis
67
+ if result[:y_axis]
68
+ diagram.y_axis = create_y_axis(result[:y_axis])
69
+ end
70
+
71
+ # Create datasets
72
+ result[:datasets].each_with_index do |dataset_data, idx|
73
+ dataset = Diagram::XYDataset.new(
74
+ "dataset_#{idx}",
75
+ dataset_data[:label],
76
+ dataset_data[:chart_type]
77
+ )
78
+ dataset.values = dataset_data[:values]
79
+ diagram.add_dataset(dataset)
80
+ end
81
+
82
+ diagram
83
+ end
84
+
85
+ def create_x_axis(axis_data)
86
+ axis = Diagram::XYAxis.new
87
+ axis.label = axis_data[:label]
88
+
89
+ # Extract values and determine if categorical or numeric
90
+ values = axis_data[:values]
91
+
92
+ # Check if all values are numeric
93
+ if values.all? { |v| v.is_a?(Numeric) }
94
+ axis.type = :numeric
95
+ axis.values = values
96
+ else
97
+ axis.type = :categorical
98
+ axis.values = values.map(&:to_s)
99
+ end
100
+
101
+ axis
102
+ end
103
+
104
+ def create_y_axis(axis_data)
105
+ axis = Diagram::XYAxis.new
106
+ axis.label = axis_data[:label]
107
+ axis.type = :numeric
108
+ axis.min = axis_data[:min]
109
+ axis.max = axis_data[:max]
110
+ axis
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'parser/base'
4
+
5
+ module Sirena
6
+ module Parser
7
+ end
8
+ end
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Sirena
6
+ module Renderer
7
+ # Architecture diagram renderer for converting positioned layouts to SVG
8
+ class ArchitectureRenderer < Base
9
+ # Icon mappings for common service types
10
+ ICON_GLYPHS = {
11
+ "database" => "⬢",
12
+ "server" => "▦",
13
+ "disk" => "◎",
14
+ "cloud" => "☁",
15
+ "internet" => "◈",
16
+ "browser" => "⊞",
17
+ }.freeze
18
+
19
+ # Renders a positioned layout to SVG
20
+ #
21
+ # @param layout [Hash] positioned layout with service positions
22
+ # @return [Svg::Document] the rendered SVG document
23
+ def render(layout)
24
+ svg = create_document_from_layout(layout)
25
+
26
+ # Render groups first (as backgrounds)
27
+ render_groups(layout, svg) if layout[:groups]
28
+
29
+ # Render edges (connections)
30
+ render_edges(layout, svg) if layout[:edges]
31
+
32
+ # Render services on top
33
+ render_services(layout, svg) if layout[:services]
34
+
35
+ svg
36
+ end
37
+
38
+ protected
39
+
40
+ def create_document_from_layout(layout)
41
+ width = layout[:width] || 800
42
+ height = layout[:height] || 600
43
+
44
+ Svg::Document.new(width: width, height: height)
45
+ end
46
+
47
+ def render_groups(layout, svg)
48
+ # Render groups in order (parent groups before children)
49
+ layout[:groups].each do |group_id, group_info|
50
+ render_group(group_info, svg)
51
+ end
52
+ end
53
+
54
+ def render_group(group_info, svg)
55
+ group = group_info[:group]
56
+ x = group_info[:x]
57
+ y = group_info[:y]
58
+ width = group_info[:width]
59
+ height = group_info[:height]
60
+
61
+ # Create group element
62
+ g = Svg::Group.new.tap do |elem|
63
+ elem.id = "group-#{group.id}"
64
+ end
65
+
66
+ # Draw group boundary
67
+ boundary = Svg::Rect.new.tap do |rect|
68
+ rect.x = x
69
+ rect.y = y
70
+ rect.width = width
71
+ rect.height = height
72
+ rect.fill = theme_color(:group_background) || "#f0f0f0"
73
+ rect.fill_opacity = "0.3"
74
+ rect.stroke = theme_color(:border_color) || "#999"
75
+ rect.stroke_width = "2"
76
+ rect.stroke_dasharray = "5,5"
77
+ rect.rx = "8"
78
+ rect.ry = "8"
79
+ end
80
+
81
+ g.children << boundary
82
+
83
+ # Draw group label
84
+ if group.label
85
+ label = Svg::Text.new.tap do |text|
86
+ text.x = x + 10
87
+ text.y = y + 20
88
+ text.content = group.label
89
+ text.font_size = "14"
90
+ text.font_weight = "bold"
91
+ text.fill = theme_color(:text_color) || "#333"
92
+ end
93
+
94
+ g.children << label
95
+ end
96
+
97
+ # Draw group icon if present
98
+ if group.icon
99
+ icon_text = Svg::Text.new.tap do |text|
100
+ text.x = x + width - 30
101
+ text.y = y + 25
102
+ text.content = icon_glyph(group.icon)
103
+ text.font_size = "20"
104
+ text.fill = theme_color(:text_color) || "#666"
105
+ end
106
+
107
+ g.children << icon_text
108
+ end
109
+
110
+ svg << g
111
+ end
112
+
113
+ def render_services(layout, svg)
114
+ layout[:services].each do |service_id, service_info|
115
+ render_service(service_info, svg)
116
+ end
117
+ end
118
+
119
+ def render_service(service_info, svg)
120
+ service = service_info[:service]
121
+ x = service_info[:x]
122
+ y = service_info[:y]
123
+ width = service_info[:width]
124
+ height = service_info[:height]
125
+
126
+ # Create service group
127
+ g = Svg::Group.new.tap do |elem|
128
+ elem.id = "service-#{service.id}"
129
+ end
130
+
131
+ # Draw service box
132
+ box = Svg::Rect.new.tap do |rect|
133
+ rect.x = x
134
+ rect.y = y
135
+ rect.width = width
136
+ rect.height = height
137
+ apply_theme_to_node(rect)
138
+ rect.rx = "5"
139
+ rect.ry = "5"
140
+ end
141
+
142
+ g.children << box
143
+
144
+ # Draw service icon
145
+ if service.icon
146
+ icon = Svg::Text.new.tap do |text|
147
+ text.x = x + width / 2
148
+ text.y = y + height / 3
149
+ text.content = icon_glyph(service.icon)
150
+ text.font_size = "24"
151
+ text.text_anchor = "middle"
152
+ text.dominant_baseline = "middle"
153
+ text.fill = theme_color(:text_color) || "#666"
154
+ end
155
+
156
+ g.children << icon
157
+ end
158
+
159
+ # Draw service label
160
+ if service.label
161
+ label = Svg::Text.new.tap do |text|
162
+ text.x = x + width / 2
163
+ text.y = y + height * 2 / 3
164
+ text.content = service.label
165
+ apply_theme_to_text(text)
166
+ text.text_anchor = "middle"
167
+ text.dominant_baseline = "middle"
168
+ text.font_size = "12"
169
+ end
170
+
171
+ g.children << label
172
+ end
173
+
174
+ svg << g
175
+ end
176
+
177
+ def render_edges(layout, svg)
178
+ layout[:edges].each do |edge_info|
179
+ render_edge(edge_info, svg)
180
+ end
181
+ end
182
+
183
+ def render_edge(edge_info, svg)
184
+ edge = edge_info[:edge]
185
+ from_x = edge_info[:from_x]
186
+ from_y = edge_info[:from_y]
187
+ to_x = edge_info[:to_x]
188
+ to_y = edge_info[:to_y]
189
+
190
+ # Create edge group
191
+ g = Svg::Group.new.tap do |elem|
192
+ elem.id = "edge-#{edge.from_id}-#{edge.to_id}"
193
+ end
194
+
195
+ # Calculate path
196
+ path_data = calculate_edge_path(from_x, from_y, to_x, to_y)
197
+
198
+ # Draw connection line
199
+ path = Svg::Path.new.tap do |p|
200
+ p.d = path_data
201
+ p.fill = "none"
202
+ apply_theme_to_edge(p)
203
+ p.marker_end = "url(#arrowhead)"
204
+ end
205
+
206
+ g.children << path
207
+
208
+ # Draw edge label if present
209
+ if edge.label && !edge.label.empty?
210
+ mid_x = (from_x + to_x) / 2
211
+ mid_y = (from_y + to_y) / 2
212
+
213
+ label = Svg::Text.new.tap do |text|
214
+ text.x = mid_x
215
+ text.y = mid_y - 5
216
+ text.content = edge.label
217
+ text.font_size = "10"
218
+ text.text_anchor = "middle"
219
+ text.fill = theme_color(:text_color) || "#666"
220
+ end
221
+
222
+ g.children << label
223
+ end
224
+
225
+ svg << g
226
+ end
227
+
228
+ def calculate_edge_path(from_x, from_y, to_x, to_y)
229
+ # Use simple straight line for now
230
+ # Could be enhanced with bezier curves or orthogonal routing
231
+ "M #{from_x} #{from_y} L #{to_x} #{to_y}"
232
+ end
233
+
234
+ def icon_glyph(icon_name)
235
+ # Extract base icon name (remove prefixes like "logos:" or "fa:")
236
+ base_name = icon_name.split(":").last
237
+
238
+ # Map to glyph or use first letter as fallback
239
+ ICON_GLYPHS[base_name] || base_name[0].upcase
240
+ end
241
+
242
+ def calculate_width(layout)
243
+ layout[:width] || 800
244
+ end
245
+
246
+ def calculate_height(layout)
247
+ layout[:height] || 600
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sirena
4
+ module Renderer
5
+ # Abstract base class for diagram renderers.
6
+ #
7
+ # Renderers convert laid-out graph structures (with computed positions)
8
+ # into SVG documents using the Svg builder classes. Each diagram type has
9
+ # its own renderer that maps graph elements to appropriate SVG shapes.
10
+ #
11
+ # The renderer implements the template method pattern, defining the
12
+ # overall rendering pipeline while allowing subclasses to customize
13
+ # specific rendering steps.
14
+ #
15
+ # @example Define a custom renderer
16
+ # class FlowchartRenderer < Renderer::Base
17
+ # def render(graph)
18
+ # svg = create_document(graph)
19
+ # render_nodes(graph, svg)
20
+ # render_edges(graph, svg)
21
+ # svg
22
+ # end
23
+ #
24
+ # protected
25
+ #
26
+ # def render_nodes(graph, svg)
27
+ # # Render graph nodes as SVG shapes
28
+ # end
29
+ #
30
+ # def render_edges(graph, svg)
31
+ # # Render graph edges as SVG paths
32
+ # end
33
+ # end
34
+ #
35
+ # @abstract Subclass and implement rendering methods
36
+ class Base
37
+ attr_accessor :theme
38
+
39
+ # Creates a new renderer instance.
40
+ #
41
+ # @param theme [Theme, nil] theme to use for rendering
42
+ def initialize(theme: nil)
43
+ @theme = theme || Theme::Registry.get(:default)
44
+ end
45
+
46
+ # Renders a laid-out graph to an SVG document.
47
+ #
48
+ # This method should be overridden by subclasses to implement
49
+ # diagram-specific rendering logic.
50
+ #
51
+ # @param graph [Object] the laid-out elkrb graph with positions
52
+ # @return [Svg::Document] the rendered SVG document
53
+ # @raise [NotImplementedError] if not implemented by subclass
54
+ def render(graph)
55
+ raise NotImplementedError,
56
+ "#{self.class} must implement #render(graph)"
57
+ end
58
+
59
+ protected
60
+
61
+ # Creates an SVG document with appropriate dimensions.
62
+ #
63
+ # @param graph [Object] the graph to get dimensions from
64
+ # @param padding [Numeric] padding around the diagram
65
+ # @return [Svg::Document] new SVG document
66
+ def create_document(graph, padding: 20)
67
+ width = calculate_width(graph) + (padding * 2)
68
+ height = calculate_height(graph) + (padding * 2)
69
+
70
+ Svg::Document.new.tap do |doc|
71
+ doc.width = width
72
+ doc.height = height
73
+ doc.view_box = "0 0 #{width} #{height}"
74
+ end
75
+ end
76
+
77
+ # Calculates the total width needed for the diagram.
78
+ #
79
+ # Subclasses should override this to compute width from graph.
80
+ #
81
+ # @param graph [Object] the graph
82
+ # @return [Numeric] the width in pixels
83
+ def calculate_width(_graph)
84
+ 800 # Default width
85
+ end
86
+
87
+ # Calculates the total height needed for the diagram.
88
+ #
89
+ # Subclasses should override this to compute height from graph.
90
+ #
91
+ # @param graph [Object] the graph
92
+ # @return [Numeric] the height in pixels
93
+ def calculate_height(_graph)
94
+ 600 # Default height
95
+ end
96
+
97
+ # Helper methods for accessing theme properties
98
+
99
+ # Gets a color property from the theme.
100
+ #
101
+ # @param property [Symbol] property name
102
+ # @return [String, nil] color value
103
+ def theme_color(property)
104
+ theme&.colors&.send(property)
105
+ rescue NoMethodError
106
+ nil
107
+ end
108
+
109
+ # Gets a typography property from the theme.
110
+ #
111
+ # @param property [Symbol] property name
112
+ # @return [String, Float, nil] typography value
113
+ def theme_typography(property)
114
+ theme&.typography&.send(property)
115
+ rescue NoMethodError
116
+ nil
117
+ end
118
+
119
+ # Gets a shape property from the theme.
120
+ #
121
+ # @param property [Symbol] property name
122
+ # @return [Float, String, nil] shape value
123
+ def theme_shape(property)
124
+ theme&.shapes&.send(property)
125
+ rescue NoMethodError
126
+ nil
127
+ end
128
+
129
+ # Gets a spacing property from the theme.
130
+ #
131
+ # @param property [Symbol] property name
132
+ # @return [Float, nil] spacing value
133
+ def theme_spacing(property)
134
+ theme&.spacing&.send(property)
135
+ rescue NoMethodError
136
+ nil
137
+ end
138
+
139
+ # Gets an effect property from the theme.
140
+ #
141
+ # @param property [Symbol] property name
142
+ # @return [Boolean, Float, String, nil] effect value
143
+ def theme_effect(property)
144
+ theme&.effects&.send(property)
145
+ rescue NoMethodError
146
+ nil
147
+ end
148
+
149
+ # Applies theme styles to a node element.
150
+ #
151
+ # @param element [Svg::Element] SVG element
152
+ # @return [void]
153
+ def apply_theme_to_node(element)
154
+ element.fill = theme_color(:node_fill) if theme_color(:node_fill)
155
+ element.stroke = theme_color(:node_stroke) if theme_color(:node_stroke)
156
+ if theme_shape(:stroke_width)
157
+ element.stroke_width = theme_shape(:stroke_width).to_s
158
+ end
159
+ end
160
+
161
+ # Applies theme styles to an edge element.
162
+ #
163
+ # @param element [Svg::Element] SVG element
164
+ # @return [void]
165
+ def apply_theme_to_edge(element)
166
+ element.stroke = theme_color(:edge_stroke) if theme_color(:edge_stroke)
167
+ if theme_shape(:stroke_width)
168
+ element.stroke_width = theme_shape(:stroke_width).to_s
169
+ end
170
+ end
171
+
172
+ # Applies theme styles to a text element.
173
+ #
174
+ # @param element [Svg::Text] SVG text element
175
+ # @return [void]
176
+ def apply_theme_to_text(element)
177
+ element.fill = theme_color(:label_text) if theme_color(:label_text)
178
+ if theme_typography(:font_family)
179
+ element.font_family = theme_typography(:font_family)
180
+ end
181
+ if theme_typography(:font_size_normal)
182
+ element.font_size = theme_typography(:font_size_normal).to_s
183
+ end
184
+ end
185
+
186
+ # Creates default style for SVG elements.
187
+ #
188
+ # @param element_type [Symbol] type of element (:node, :edge, :text)
189
+ # @return [Svg::Style] style object
190
+ def default_style(element_type)
191
+ case element_type
192
+ when :node
193
+ Svg::Style.new.tap do |style|
194
+ style.fill = theme_color(:node_fill) || '#ffffff'
195
+ style.stroke = theme_color(:node_stroke) || '#000000'
196
+ style.stroke_width =
197
+ (theme_shape(:stroke_width) || 2).to_s
198
+ end
199
+ when :edge
200
+ Svg::Style.new.tap do |style|
201
+ style.fill = 'none'
202
+ style.stroke = theme_color(:edge_stroke) || '#000000'
203
+ style.stroke_width =
204
+ (theme_shape(:stroke_width) || 2).to_s
205
+ end
206
+ when :text
207
+ Svg::Style.new.tap do |style|
208
+ style.fill = theme_color(:label_text) || '#000000'
209
+ style.font_family =
210
+ theme_typography(:font_family) || 'Arial, sans-serif'
211
+ style.font_size =
212
+ (theme_typography(:font_size_normal) || 14).to_s
213
+ style.text_anchor = 'middle'
214
+ end
215
+ else
216
+ Svg::Style.new
217
+ end
218
+ end
219
+
220
+ # Creates an SVG path data string from bend points.
221
+ #
222
+ # @param start_point [Hash] hash with :x and :y keys
223
+ # @param end_point [Hash] hash with :x and :y keys
224
+ # @param bend_points [Array<Hash>] array of hashes with :x and :y
225
+ # @return [String] SVG path data string
226
+ def create_path_data(start_point, end_point, bend_points = [])
227
+ points = [start_point] + bend_points + [end_point]
228
+ path_parts = ["M #{points[0][:x]} #{points[0][:y]}"]
229
+
230
+ points[1..].each do |point|
231
+ path_parts << "L #{point[:x]} #{point[:y]}"
232
+ end
233
+
234
+ path_parts.join(' ')
235
+ end
236
+
237
+ # Adds an arrow marker to the SVG document.
238
+ #
239
+ # @param svg [Svg::Document] the SVG document
240
+ # @param id [String] unique identifier for the marker
241
+ # @return [void]
242
+ def add_arrow_marker(svg, id: 'arrow')
243
+ # This is a placeholder for actual marker implementation
244
+ # Subclasses can implement this to add arrow markers
245
+ end
246
+ end
247
+
248
+ # Error raised during rendering.
249
+ class RenderError < StandardError; end
250
+ end
251
+ end