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,347 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../diagram/c4'
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for converting Parslet parse tree to C4 diagram model.
9
+ #
10
+ # Converts the parse tree output from Grammars::C4 into a
11
+ # fully-formed Diagram::C4 object with elements, relationships, and
12
+ # boundaries.
13
+ class C4
14
+ def initialize
15
+ @boundary_stack = []
16
+ @current_boundary = nil
17
+ end
18
+
19
+ # Transform parse tree into C4 diagram.
20
+ #
21
+ # @param tree [Array, Hash] Parslet parse tree
22
+ # @return [Diagram::C4] the C4 diagram model
23
+ def apply(tree)
24
+ diagram = Diagram::C4.new
25
+ @boundary_stack = []
26
+ @current_boundary = nil
27
+
28
+ # Extract level from header
29
+ extract_level(diagram, tree)
30
+
31
+ # Process statements - collect scattered attributes
32
+ if tree.is_a?(Array)
33
+ # Merge scattered attribute hashes with their parent element/boundary
34
+ merged_tree = merge_scattered_attributes(tree)
35
+ merged_tree.each do |item|
36
+ process_statement(diagram, item) if item.is_a?(Hash)
37
+ end
38
+ elsif tree.is_a?(Hash)
39
+ process_statement(diagram, tree)
40
+ end
41
+
42
+ diagram
43
+ end
44
+
45
+ private
46
+
47
+ # Merge scattered attribute hashes into their parent statements
48
+ def merge_scattered_attributes(tree)
49
+ result = []
50
+ i = 0
51
+ while i < tree.length
52
+ item = tree[i]
53
+
54
+ # Check if this is an element/boundary statement starter
55
+ if item.is_a?(Hash) && (item[:element_type] || item[:boundary_type])
56
+ # Don't merge if this has a body (it's a complete boundary statement)
57
+ if item[:body]
58
+ result << item
59
+ i += 1
60
+ next
61
+ end
62
+
63
+ # Look ahead and merge all related hashes
64
+ j = i + 1
65
+ while j < tree.length && tree[j].is_a?(Hash)
66
+ next_item = tree[j]
67
+ # Stop if we hit another element/boundary/relationship/title/config
68
+ break if next_item[:element_type] || next_item[:boundary_type] ||
69
+ next_item[:rel_type] || next_item[:title] ||
70
+ next_item[:config_params] || next_item[:header]
71
+ # Merge this hash into the current statement
72
+ item = item.merge(next_item)
73
+ j += 1
74
+ end
75
+ result << item
76
+ i = j
77
+ else
78
+ result << item
79
+ i += 1
80
+ end
81
+ end
82
+ result
83
+ end
84
+
85
+ def extract_level(diagram, tree)
86
+ header = if tree.is_a?(Array)
87
+ tree.find { |item| item.is_a?(Hash) && item[:header] }
88
+ elsif tree.is_a?(Hash) && tree[:header]
89
+ tree
90
+ end
91
+
92
+ if header && header[:header]
93
+ header_str = header[:header].to_s
94
+ diagram.level = case header_str
95
+ when 'C4Context'
96
+ 'Context'
97
+ when 'C4Container'
98
+ 'Container'
99
+ when 'C4Component'
100
+ 'Component'
101
+ when 'C4Dynamic'
102
+ 'Dynamic'
103
+ when 'C4Deployment'
104
+ 'Deployment'
105
+ when 'C4 diagram'
106
+ 'Context' # Default
107
+ else
108
+ 'Context'
109
+ end
110
+ end
111
+ end
112
+
113
+ def process_statement(diagram, stmt)
114
+ return unless stmt.is_a?(Hash)
115
+
116
+ if stmt[:header]
117
+ # Already processed
118
+ nil
119
+ elsif stmt[:title]
120
+ diagram.title = extract_text(stmt[:title])
121
+ elsif stmt[:config_params]
122
+ process_layout_config(diagram, stmt)
123
+ elsif stmt[:boundary_type]
124
+ process_boundary(diagram, stmt)
125
+ elsif stmt[:rel_type]
126
+ process_relationship(diagram, stmt)
127
+ elsif stmt[:element_type]
128
+ process_element(diagram, stmt)
129
+ end
130
+ end
131
+
132
+ def process_layout_config(diagram, stmt)
133
+ # Extract layout config as a simple string
134
+ config_parts = []
135
+ if stmt[:config_params].is_a?(Array)
136
+ stmt[:config_params].each do |param|
137
+ key = extract_text(param[:key]) if param[:key]
138
+ value = extract_text(param[:value]) if param[:value]
139
+ config_parts << "#{key}=#{value}" if key && value
140
+ end
141
+ elsif stmt[:config_params].is_a?(Hash)
142
+ key = extract_text(stmt[:config_params][:key])
143
+ value = extract_text(stmt[:config_params][:value])
144
+ config_parts << "#{key}=#{value}"
145
+ end
146
+
147
+ diagram.layout_config = config_parts.join(', ')
148
+ end
149
+
150
+ def process_boundary(diagram, stmt)
151
+ boundary = Diagram::C4Boundary.new
152
+
153
+ # Handle boundary type (can be a variable reference)
154
+ boundary_type = stmt[:boundary_type]
155
+ if boundary_type.is_a?(Hash) && boundary_type[:variable]
156
+ # Variable reference like ${macroName}
157
+ boundary.boundary_type = extract_text(boundary_type[:variable][:var])
158
+ else
159
+ boundary.boundary_type = boundary_type.to_s
160
+ end
161
+
162
+ boundary.id = extract_text(stmt[:id]) if stmt[:id]
163
+ boundary.label = extract_text(stmt[:label]) if stmt[:label]
164
+ boundary.type_param = extract_text(stmt[:type]) if stmt[:type]
165
+ boundary.link = extract_text(stmt[:link]) if stmt[:link]
166
+ boundary.tags = extract_text(stmt[:tags]) if stmt[:tags]
167
+
168
+ # Set parent if we're inside another boundary
169
+ boundary.parent_id = @current_boundary if @current_boundary
170
+
171
+ # Add boundary to diagram
172
+ diagram.boundaries << boundary
173
+
174
+ # Process nested content
175
+ if stmt[:body]
176
+ old_boundary = @current_boundary
177
+ @current_boundary = boundary.id
178
+
179
+ # Normalize body to array of items
180
+ body_array = stmt[:body].is_a?(Array) ? stmt[:body] : [stmt[:body]]
181
+
182
+ # Extract items from the body structure
183
+ body_items = body_array.flat_map do |item|
184
+ if item.is_a?(Hash) && item[:item]
185
+ [item[:item]]
186
+ else
187
+ []
188
+ end
189
+ end
190
+
191
+ # Merge scattered attributes in the body
192
+ merged_body = merge_scattered_attributes(body_items)
193
+
194
+ # Process each merged item
195
+ merged_body.each do |nested_item|
196
+ if nested_item[:boundary_type]
197
+ # Nested boundary
198
+ nested_boundary_id = process_nested_boundary(diagram,
199
+ nested_item)
200
+ boundary.boundary_ids << nested_boundary_id if
201
+ nested_boundary_id
202
+ elsif nested_item[:element_type]
203
+ # Element inside boundary
204
+ element_id = process_nested_element(diagram, nested_item)
205
+ boundary.element_ids << element_id if element_id
206
+ end
207
+ end
208
+
209
+ @current_boundary = old_boundary
210
+ end
211
+ end
212
+
213
+ def process_nested_boundary(diagram, stmt)
214
+ boundary = Diagram::C4Boundary.new
215
+
216
+ # Handle boundary type (can be a variable reference)
217
+ boundary_type = stmt[:boundary_type]
218
+ if boundary_type.is_a?(Hash) && boundary_type[:variable]
219
+ # Variable reference like ${macroName}
220
+ boundary.boundary_type = extract_text(boundary_type[:variable][:var])
221
+ else
222
+ boundary.boundary_type = boundary_type.to_s
223
+ end
224
+
225
+ boundary.id = extract_text(stmt[:id]) if stmt[:id]
226
+ boundary.label = extract_text(stmt[:label]) if stmt[:label]
227
+ boundary.type_param = extract_text(stmt[:type]) if stmt[:type]
228
+ boundary.link = extract_text(stmt[:link]) if stmt[:link]
229
+ boundary.tags = extract_text(stmt[:tags]) if stmt[:tags]
230
+ boundary.parent_id = @current_boundary
231
+
232
+ diagram.boundaries << boundary
233
+
234
+ # Process nested content recursively
235
+ if stmt[:body]
236
+ old_boundary = @current_boundary
237
+ @current_boundary = boundary.id
238
+
239
+ # Convert body to array and extract items
240
+ body_items = Array(stmt[:body]).flat_map do |item|
241
+ if item.is_a?(Hash) && item[:item]
242
+ [item[:item]]
243
+ else
244
+ []
245
+ end
246
+ end
247
+
248
+ # Merge scattered attributes in the body
249
+ merged_body = merge_scattered_attributes(body_items)
250
+
251
+ # Process each merged item
252
+ merged_body.each do |nested_item|
253
+ if nested_item[:boundary_type]
254
+ nested_boundary_id = process_nested_boundary(diagram,
255
+ nested_item)
256
+ boundary.boundary_ids << nested_boundary_id if
257
+ nested_boundary_id
258
+ elsif nested_item[:element_type]
259
+ element_id = process_nested_element(diagram, nested_item)
260
+ boundary.element_ids << element_id if element_id
261
+ end
262
+ end
263
+
264
+ @current_boundary = old_boundary
265
+ end
266
+
267
+ boundary.id
268
+ end
269
+
270
+ def process_element(diagram, stmt)
271
+ element = create_element(stmt)
272
+ element.boundary_id = @current_boundary if @current_boundary
273
+ diagram.elements << element
274
+ end
275
+
276
+ def process_nested_element(diagram, stmt)
277
+ element = create_element(stmt)
278
+ element.boundary_id = @current_boundary if @current_boundary
279
+ diagram.elements << element
280
+ element.id
281
+ end
282
+
283
+ def create_element(stmt)
284
+ element = Diagram::C4Element.new
285
+
286
+ # Extract element type
287
+ element_type = stmt[:element_type]
288
+ if element_type.is_a?(Hash) && element_type[:variable]
289
+ # Handle ${macroName} variable references (used in tests)
290
+ element.element_type = extract_text(element_type[:variable][:var])
291
+ else
292
+ element.element_type = element_type.to_s
293
+ end
294
+
295
+ # Extract parameters
296
+ element.id = extract_text(stmt[:id]) if stmt[:id]
297
+ element.label = extract_text(stmt[:label]) if stmt[:label]
298
+ element.description = extract_text(stmt[:description]) if
299
+ stmt[:description]
300
+ element.technology = extract_text(stmt[:technology]) if
301
+ stmt[:technology]
302
+
303
+ # Extract attributes
304
+ element.sprite = extract_text(stmt[:sprite]) if stmt[:sprite]
305
+ element.link = extract_text(stmt[:link]) if stmt[:link]
306
+ element.tags = extract_text(stmt[:tags]) if stmt[:tags]
307
+
308
+ # Set external flag based on element type
309
+ element.external = element.element_type&.end_with?('_Ext') || false
310
+
311
+ element
312
+ end
313
+
314
+ def process_relationship(diagram, stmt)
315
+ relationship = Diagram::C4Relationship.new
316
+
317
+ relationship.rel_type = stmt[:rel_type].to_s
318
+ relationship.from_id = extract_text(stmt[:from]) if stmt[:from]
319
+ relationship.to_id = extract_text(stmt[:to]) if stmt[:to]
320
+ relationship.label = extract_text(stmt[:label]) if stmt[:label]
321
+ relationship.technology = extract_text(stmt[:technology]) if
322
+ stmt[:technology]
323
+
324
+ diagram.relationships << relationship
325
+ end
326
+
327
+ def extract_text(value)
328
+ case value
329
+ when Hash
330
+ if value[:string]
331
+ value[:string].to_s
332
+ elsif value[:var]
333
+ # Variable reference like ${macroName}
334
+ value[:var].to_s
335
+ else
336
+ value.values.first.to_s
337
+ end
338
+ when String
339
+ value
340
+ else
341
+ value.to_s
342
+ end.strip
343
+ end
344
+ end
345
+ end
346
+ end
347
+ end
@@ -0,0 +1,352 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../diagram/class_diagram'
4
+
5
+ module Sirena
6
+ module Parser
7
+ module Transforms
8
+ # Transform for converting Parslet parse tree to Class diagram model.
9
+ #
10
+ # Converts the parse tree output from Grammars::ClassDiagram into a
11
+ # fully-formed Diagram::ClassDiagram object with entities and
12
+ # relationships.
13
+ class ClassDiagram
14
+ # Relationship type mappings from operators
15
+ RELATIONSHIP_TYPES = {
16
+ '<|--' => 'inheritance',
17
+ '--|>' => 'inheritance',
18
+ '*--' => 'composition',
19
+ '--*' => 'composition',
20
+ 'o--' => 'aggregation',
21
+ '--o' => 'aggregation',
22
+ '-->' => 'association',
23
+ '<--' => 'association',
24
+ '--' => 'association',
25
+ '..|>' => 'realization',
26
+ '<|..' => 'realization',
27
+ '..>' => 'dependency',
28
+ '<..' => 'dependency',
29
+ '..' => 'association'
30
+ }.freeze
31
+
32
+ # Operators where arrow points left (reverse direction)
33
+ LEFT_POINTING = ['<|--', '<--', '<|..', '<..'].freeze
34
+
35
+ # Visibility symbol mappings
36
+ VISIBILITY_SYMBOLS = {
37
+ '+' => 'public',
38
+ '-' => 'private',
39
+ '#' => 'protected',
40
+ '~' => 'package'
41
+ }.freeze
42
+
43
+ # Transform parse tree into Class diagram.
44
+ #
45
+ # @param tree [Array, Hash] Parslet parse tree
46
+ # @return [Diagram::ClassDiagram] the Class diagram model
47
+ def apply(tree)
48
+ @diagram = Diagram::ClassDiagram.new
49
+ @current_namespace = nil
50
+
51
+ # Tree is an array: [header, direction, ...statements]
52
+ if tree.is_a?(Array)
53
+ tree.each do |item|
54
+ process_item(item) if item.is_a?(Hash)
55
+ end
56
+ elsif tree.is_a?(Hash)
57
+ process_item(tree)
58
+ end
59
+
60
+ @diagram
61
+ end
62
+
63
+ private
64
+
65
+ def process_item(item)
66
+ return unless item.is_a?(Hash)
67
+
68
+ # Process header to get direction
69
+ if item[:direction] && item[:direction][:dir_value]
70
+ @diagram.direction = extract_text(item[:direction][:dir_value])
71
+ end
72
+
73
+ # Process statements
74
+ process_statement(item) unless item[:header] || item[:direction]
75
+ end
76
+
77
+ def process_statement(stmt)
78
+ return unless stmt.is_a?(Hash)
79
+
80
+ if stmt[:namespace_keyword]
81
+ # Namespace block
82
+ process_namespace(stmt)
83
+ elsif stmt[:keyword] == 'class' && stmt[:class_id]
84
+ # Class declaration
85
+ process_class_declaration(stmt)
86
+ elsif stmt[:stereotype] && stmt[:class_id] && !stmt[:keyword]
87
+ # Standalone stereotype
88
+ process_standalone_stereotype(stmt)
89
+ elsif stmt[:class_id] && stmt[:member]
90
+ # Colon member definition
91
+ process_colon_member(stmt)
92
+ elsif stmt[:from_id] && stmt[:to_id] && stmt[:operator]
93
+ # Relationship
94
+ process_relationship(stmt)
95
+ elsif stmt[:link_keyword] || stmt[:callback_keyword]
96
+ # Link or callback - ignore for now
97
+ nil
98
+ elsif stmt[:class_id] && !stmt[:keyword]
99
+ # Standalone class
100
+ ensure_entity_exists(extract_text(stmt[:class_id]))
101
+ end
102
+ end
103
+
104
+ def process_namespace(stmt)
105
+ namespace_name = extract_text(stmt[:namespace_name])
106
+ old_namespace = @current_namespace
107
+ @current_namespace = namespace_name
108
+
109
+ # Process namespace body
110
+ if stmt[:namespace_body]
111
+ statements = Array(stmt[:namespace_body])
112
+ statements.each do |s|
113
+ process_statement(s) if s.is_a?(Hash)
114
+ end
115
+ end
116
+
117
+ @current_namespace = old_namespace
118
+ end
119
+
120
+ def process_class_declaration(stmt)
121
+ class_id = extract_text(stmt[:class_id])
122
+ class_id = qualify_name(class_id)
123
+
124
+ entity = find_or_create_entity(class_id)
125
+
126
+ # Handle stereotype
127
+ if stmt[:stereotype] && stmt[:stereotype][:stereotype_value]
128
+ entity.stereotype = extract_text(stmt[:stereotype][:stereotype_value])
129
+ end
130
+
131
+ # Handle generic parameters
132
+ if stmt[:generic] && stmt[:generic][:generic_type]
133
+ generic_type = extract_text(stmt[:generic][:generic_type])
134
+ # Append generic to class name for display
135
+ entity.name = "#{entity.name}~#{generic_type}~"
136
+ end
137
+
138
+ # Handle class body
139
+ process_class_body(entity, stmt[:body]) if stmt[:body]
140
+ end
141
+
142
+ def process_standalone_stereotype(stmt)
143
+ class_id = extract_text(stmt[:class_id])
144
+ class_id = qualify_name(class_id)
145
+
146
+ entity = find_or_create_entity(class_id)
147
+
148
+ if stmt[:stereotype][:stereotype_value]
149
+ entity.stereotype = extract_text(stmt[:stereotype][:stereotype_value])
150
+ end
151
+ end
152
+
153
+ def process_colon_member(stmt)
154
+ class_id = extract_text(stmt[:class_id])
155
+ class_id = qualify_name(class_id)
156
+
157
+ entity = find_or_create_entity(class_id)
158
+
159
+ # Parse visibility
160
+ visibility = parse_visibility(stmt[:visibility])
161
+
162
+ # Parse member
163
+ member_data = stmt[:member]
164
+ if member_data[:method_name]
165
+ # It's a method
166
+ add_method_to_entity(entity, member_data, visibility)
167
+ else
168
+ # It's an attribute
169
+ add_attribute_to_entity(entity, member_data, visibility)
170
+ end
171
+ end
172
+
173
+ def process_class_body(entity, body_data)
174
+ return unless body_data.is_a?(Array)
175
+
176
+ body_data.each do |member_item|
177
+ next unless member_item.is_a?(Hash)
178
+ next unless member_item[:member]
179
+
180
+ visibility = parse_visibility(member_item[:visibility])
181
+ member_data = member_item[:member]
182
+
183
+ if member_data[:method_name]
184
+ add_method_to_entity(entity, member_data, visibility)
185
+ else
186
+ add_attribute_to_entity(entity, member_data, visibility)
187
+ end
188
+ end
189
+ end
190
+
191
+ def add_method_to_entity(entity, method_data, visibility)
192
+ method_name = extract_text(method_data[:method_name])
193
+ parameters = method_data[:parameters] ? extract_text(method_data[:parameters]) : ''
194
+ return_type = nil
195
+
196
+ if method_data[:return_type] && method_data[:return_type][:type]
197
+ return_type = extract_text(method_data[:return_type][:type])
198
+ end
199
+
200
+ method = Diagram::ClassMethod.new.tap do |m|
201
+ m.name = method_name
202
+ m.parameters = parameters
203
+ m.return_type = return_type
204
+ m.visibility = visibility
205
+ end
206
+
207
+ entity.class_methods << method
208
+ end
209
+
210
+ def add_attribute_to_entity(entity, attr_data, visibility)
211
+ attr_name = extract_text(attr_data[:attr_name])
212
+ attr_type = attr_data[:type] ? extract_text(attr_data[:type]) : nil
213
+
214
+ attribute = Diagram::ClassAttribute.new.tap do |attr|
215
+ attr.name = attr_name
216
+ attr.type = attr_type
217
+ attr.visibility = visibility
218
+ end
219
+
220
+ entity.attributes << attribute
221
+ end
222
+
223
+ def process_relationship(stmt)
224
+ from_id = extract_text(stmt[:from_id])
225
+ to_id = extract_text(stmt[:to_id])
226
+ operator = extract_text(stmt[:operator][:arrow])
227
+
228
+ # Qualify names if in namespace
229
+ from_id = qualify_name(from_id)
230
+ to_id = qualify_name(to_id)
231
+
232
+ # Ensure both entities exist
233
+ ensure_entity_exists(from_id)
234
+ ensure_entity_exists(to_id)
235
+
236
+ # Get relationship type
237
+ relationship_type = RELATIONSHIP_TYPES[operator]
238
+ relationship_type ||= 'association'
239
+
240
+ # Parse cardinality
241
+ source_card = nil
242
+ target_card = nil
243
+
244
+ if stmt[:source_card]
245
+ source_card = extract_text(stmt[:source_card][:string])
246
+ end
247
+
248
+ if stmt[:target_card]
249
+ target_card = extract_text(stmt[:target_card][:string])
250
+ end
251
+
252
+ # Parse label
253
+ label = nil
254
+ if stmt[:pipe_label] && stmt[:pipe_label][:label_text]
255
+ label = extract_text(stmt[:pipe_label][:label_text])
256
+ # Strip surrounding quotes if present
257
+ label = label.gsub(/^["']|["']$/, '') if label
258
+ elsif stmt[:colon_label] && stmt[:colon_label][:label_text]
259
+ label = extract_text(stmt[:colon_label][:label_text]).strip
260
+ end
261
+
262
+ # Determine direction
263
+ # For left-pointing arrows, reverse the relationship
264
+ if LEFT_POINTING.include?(operator)
265
+ actual_from = to_id
266
+ actual_to = from_id
267
+ # Swap cardinalities
268
+ actual_source_card = target_card
269
+ actual_target_card = source_card
270
+ else
271
+ actual_from = from_id
272
+ actual_to = to_id
273
+ actual_source_card = source_card
274
+ actual_target_card = target_card
275
+ end
276
+
277
+ relationship = Diagram::ClassRelationship.new.tap do |rel|
278
+ rel.from_id = actual_from
279
+ rel.to_id = actual_to
280
+ rel.relationship_type = relationship_type
281
+ rel.label = label
282
+ rel.source_cardinality = actual_source_card
283
+ rel.target_cardinality = actual_target_card
284
+ end
285
+
286
+ @diagram.relationships << relationship
287
+ end
288
+
289
+ def find_or_create_entity(class_id)
290
+ existing = @diagram.find_entity(class_id)
291
+ return existing if existing
292
+
293
+ entity = Diagram::ClassEntity.new.tap do |e|
294
+ e.id = class_id
295
+ e.name = class_id
296
+ end
297
+ @diagram.entities << entity
298
+ entity
299
+ end
300
+
301
+ def ensure_entity_exists(class_id)
302
+ find_or_create_entity(class_id)
303
+ end
304
+
305
+ def qualify_name(name)
306
+ return name unless @current_namespace
307
+ return name if name.include?('.')
308
+
309
+ "#{@current_namespace}.#{name}"
310
+ end
311
+
312
+ def parse_visibility(vis_data)
313
+ return 'public' unless vis_data
314
+
315
+ symbol = extract_text(vis_data[:vis_symbol])
316
+ VISIBILITY_SYMBOLS[symbol] || 'public'
317
+ end
318
+
319
+ def extract_text(value)
320
+ case value
321
+ when Hash
322
+ if value[:string]
323
+ value[:string].to_s
324
+ elsif value[:arrow]
325
+ value[:arrow].to_s
326
+ elsif value[:stereotype_value]
327
+ value[:stereotype_value].to_s
328
+ elsif value[:dir_value]
329
+ value[:dir_value].to_s
330
+ elsif value[:label_text]
331
+ value[:label_text].to_s
332
+ elsif value[:vis_symbol]
333
+ value[:vis_symbol].to_s
334
+ elsif value[:type]
335
+ value[:type].to_s
336
+ elsif value[:attr_name]
337
+ value[:attr_name].to_s
338
+ elsif value[:method_name]
339
+ value[:method_name].to_s
340
+ else
341
+ value.values.first.to_s
342
+ end
343
+ when String
344
+ value
345
+ else
346
+ value.to_s
347
+ end
348
+ end
349
+ end
350
+ end
351
+ end
352
+ end