etassia 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 (727) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +140 -0
  3. data/LICENSE +25 -0
  4. data/README.adoc +436 -0
  5. data/exe/etassia +7 -0
  6. data/lib/etassia/cli.rb +187 -0
  7. data/lib/etassia/commands/batch.rb +210 -0
  8. data/lib/etassia/commands/notation_loader.rb +22 -0
  9. data/lib/etassia/commands/render.rb +98 -0
  10. data/lib/etassia/commands/types.rb +33 -0
  11. data/lib/etassia/commands/version.rb +24 -0
  12. data/lib/etassia/corpus_type_names.rb +52 -0
  13. data/lib/etassia/diagram/architecture.rb +108 -0
  14. data/lib/etassia/diagram/base.rb +61 -0
  15. data/lib/etassia/diagram/block.rb +102 -0
  16. data/lib/etassia/diagram/c4.rb +337 -0
  17. data/lib/etassia/diagram/class_diagram.rb +466 -0
  18. data/lib/etassia/diagram/class_namespace.rb +17 -0
  19. data/lib/etassia/diagram/class_note.rb +17 -0
  20. data/lib/etassia/diagram/containment.rb +107 -0
  21. data/lib/etassia/diagram/er_diagram.rb +317 -0
  22. data/lib/etassia/diagram/error.rb +38 -0
  23. data/lib/etassia/diagram/flowchart.rb +419 -0
  24. data/lib/etassia/diagram/flowchart_label_text.rb +63 -0
  25. data/lib/etassia/diagram/gantt.rb +94 -0
  26. data/lib/etassia/diagram/generic_text.rb +30 -0
  27. data/lib/etassia/diagram/git_graph.rb +57 -0
  28. data/lib/etassia/diagram/info.rb +38 -0
  29. data/lib/etassia/diagram/kanban.rb +198 -0
  30. data/lib/etassia/diagram/mindmap.rb +71 -0
  31. data/lib/etassia/diagram/packet.rb +93 -0
  32. data/lib/etassia/diagram/pie.rb +115 -0
  33. data/lib/etassia/diagram/quadrant.rb +135 -0
  34. data/lib/etassia/diagram/radar.rb +65 -0
  35. data/lib/etassia/diagram/requirement.rb +168 -0
  36. data/lib/etassia/diagram/sankey.rb +217 -0
  37. data/lib/etassia/diagram/sequence.rb +299 -0
  38. data/lib/etassia/diagram/sequence_box.rb +18 -0
  39. data/lib/etassia/diagram/sequence_frame.rb +37 -0
  40. data/lib/etassia/diagram/sequence_frame_section.rb +23 -0
  41. data/lib/etassia/diagram/sequence_text.rb +69 -0
  42. data/lib/etassia/diagram/sequence_wrap.rb +23 -0
  43. data/lib/etassia/diagram/state_diagram.rb +282 -0
  44. data/lib/etassia/diagram/timeline.rb +183 -0
  45. data/lib/etassia/diagram/treemap.rb +95 -0
  46. data/lib/etassia/diagram/user_journey.rb +153 -0
  47. data/lib/etassia/diagram/xy_chart.rb +90 -0
  48. data/lib/etassia/diagram.rb +8 -0
  49. data/lib/etassia/diagram_registry.rb +34 -0
  50. data/lib/etassia/engine.rb +334 -0
  51. data/lib/etassia/error/diagram_type_error.rb +10 -0
  52. data/lib/etassia/error/layout_error.rb +12 -0
  53. data/lib/etassia/error/notation_registration_error.rb +10 -0
  54. data/lib/etassia/error/parse_error.rb +11 -0
  55. data/lib/etassia/error/pipeline_error.rb +10 -0
  56. data/lib/etassia/error/render_error.rb +12 -0
  57. data/lib/etassia/error/score_error.rb +12 -0
  58. data/lib/etassia/error.rb +10 -0
  59. data/lib/etassia/error_report.rb +69 -0
  60. data/lib/etassia/ir/data.rb +39 -0
  61. data/lib/etassia/ir/data_value.rb +35 -0
  62. data/lib/etassia/ir/dimension.rb +10 -0
  63. data/lib/etassia/ir/edge.rb +34 -0
  64. data/lib/etassia/ir/graph.rb +28 -0
  65. data/lib/etassia/ir/integrity.rb +80 -0
  66. data/lib/etassia/ir/item.rb +46 -0
  67. data/lib/etassia/ir/model.rb +56 -0
  68. data/lib/etassia/ir/node.rb +10 -0
  69. data/lib/etassia/ir/placement.rb +47 -0
  70. data/lib/etassia/ir/prepositioned.rb +25 -0
  71. data/lib/etassia/ir/prepositioned_item.rb +26 -0
  72. data/lib/etassia/ir/property_set.rb +43 -0
  73. data/lib/etassia/ir/scalar.rb +25 -0
  74. data/lib/etassia/ir/series.rb +10 -0
  75. data/lib/etassia/ir.rb +17 -0
  76. data/lib/etassia/js_math.rb +511 -0
  77. data/lib/etassia/js_number.rb +64 -0
  78. data/lib/etassia/layout/architecture.rb +1472 -0
  79. data/lib/etassia/layout/base.rb +276 -0
  80. data/lib/etassia/layout/block.rb +738 -0
  81. data/lib/etassia/layout/c4.rb +876 -0
  82. data/lib/etassia/layout/c4_boundary_metrics.rb +35 -0
  83. data/lib/etassia/layout/c4_bounds.rb +106 -0
  84. data/lib/etassia/layout/c4_intersection.rb +77 -0
  85. data/lib/etassia/layout/c4_placement.rb +135 -0
  86. data/lib/etassia/layout/c4_shape_metrics.rb +97 -0
  87. data/lib/etassia/layout/c4_shape_outline.rb +67 -0
  88. data/lib/etassia/layout/c4_stereotype.rb +30 -0
  89. data/lib/etassia/layout/c4_text.rb +54 -0
  90. data/lib/etassia/layout/class_cell_placement.rb +148 -0
  91. data/lib/etassia/layout/class_diagram.rb +1116 -0
  92. data/lib/etassia/layout/class_diagram_namespaces.rb +26 -0
  93. data/lib/etassia/layout/class_diagram_notes.rb +26 -0
  94. data/lib/etassia/layout/class_name_advances.rb +31 -0
  95. data/lib/etassia/layout/class_namespace_box.rb +21 -0
  96. data/lib/etassia/layout/class_namespace_nodes.rb +197 -0
  97. data/lib/etassia/layout/class_namespace_placement.rb +71 -0
  98. data/lib/etassia/layout/class_note.rb +44 -0
  99. data/lib/etassia/layout/class_note_nodes.rb +84 -0
  100. data/lib/etassia/layout/elk_notices.rb +48 -0
  101. data/lib/etassia/layout/elk_placement.rb +197 -0
  102. data/lib/etassia/layout/er_diagram.rb +1002 -0
  103. data/lib/etassia/layout/er_diagram_adjacency.rb +20 -0
  104. data/lib/etassia/layout/er_diagram_alignment.rb +72 -0
  105. data/lib/etassia/layout/er_diagram_compaction.rb +85 -0
  106. data/lib/etassia/layout/er_diagram_conflicts.rb +70 -0
  107. data/lib/etassia/layout/er_diagram_coordinates.rb +104 -0
  108. data/lib/etassia/layout/er_diagram_entity_size.rb +82 -0
  109. data/lib/etassia/layout/er_diagram_layering.rb +109 -0
  110. data/lib/etassia/layout/er_diagram_ordering.rb +143 -0
  111. data/lib/etassia/layout/er_diagram_placement.rb +163 -0
  112. data/lib/etassia/layout/er_diagram_placement_node.rb +22 -0
  113. data/lib/etassia/layout/error.rb +133 -0
  114. data/lib/etassia/layout/flowchart/brandes_koepf.rb +208 -0
  115. data/lib/etassia/layout/flowchart/cluster_reach.rb +70 -0
  116. data/lib/etassia/layout/flowchart/component_packing.rb +129 -0
  117. data/lib/etassia/layout/flowchart/directed_placement.rb +428 -0
  118. data/lib/etassia/layout/flowchart/edge_chains.rb +111 -0
  119. data/lib/etassia/layout/flowchart/edge_order.rb +34 -0
  120. data/lib/etassia/layout/flowchart/edge_router.rb +533 -0
  121. data/lib/etassia/layout/flowchart/label_backdrop_geometry.rb +62 -0
  122. data/lib/etassia/layout/flowchart/layer_spacing.rb +85 -0
  123. data/lib/etassia/layout/flowchart/level_edges.rb +74 -0
  124. data/lib/etassia/layout/flowchart/level_frame.rb +47 -0
  125. data/lib/etassia/layout/flowchart/minimum_rank_spacing.rb +74 -0
  126. data/lib/etassia/layout/flowchart/row_centering.rb +229 -0
  127. data/lib/etassia/layout/flowchart.rb +1290 -0
  128. data/lib/etassia/layout/gantt.rb +814 -0
  129. data/lib/etassia/layout/gantt_duration.rb +49 -0
  130. data/lib/etassia/layout/gantt_exclusions.rb +74 -0
  131. data/lib/etassia/layout/gantt_ticks.rb +165 -0
  132. data/lib/etassia/layout/git_graph.rb +1075 -0
  133. data/lib/etassia/layout/grid.rb +165 -0
  134. data/lib/etassia/layout/info.rb +113 -0
  135. data/lib/etassia/layout/kanban.rb +452 -0
  136. data/lib/etassia/layout/kanban_card_text.rb +95 -0
  137. data/lib/etassia/layout/mindmap.rb +425 -0
  138. data/lib/etassia/layout/mindmap_cose.rb +81 -0
  139. data/lib/etassia/layout/mindmap_cose_body.rb +93 -0
  140. data/lib/etassia/layout/mindmap_cose_geometry.rb +190 -0
  141. data/lib/etassia/layout/mindmap_cose_grid.rb +96 -0
  142. data/lib/etassia/layout/mindmap_cose_link.rb +42 -0
  143. data/lib/etassia/layout/mindmap_cose_relaxation.rb +166 -0
  144. data/lib/etassia/layout/mindmap_cose_seed.rb +93 -0
  145. data/lib/etassia/layout/mindmap_cose_wedge.rb +37 -0
  146. data/lib/etassia/layout/mindmap_node_size.rb +130 -0
  147. data/lib/etassia/layout/packet.rb +285 -0
  148. data/lib/etassia/layout/pie.rb +305 -0
  149. data/lib/etassia/layout/pie_legend.rb +64 -0
  150. data/lib/etassia/layout/pie_legend_entry.rb +21 -0
  151. data/lib/etassia/layout/quadrant.rb +402 -0
  152. data/lib/etassia/layout/radar.rb +442 -0
  153. data/lib/etassia/layout/requirement.rb +765 -0
  154. data/lib/etassia/layout/sankey.rb +501 -0
  155. data/lib/etassia/layout/scene.rb +18 -0
  156. data/lib/etassia/layout/sequence/actor_gaps.rb +151 -0
  157. data/lib/etassia/layout/sequence/box_shape.rb +23 -0
  158. data/lib/etassia/layout/sequence/creations.rb +73 -0
  159. data/lib/etassia/layout/sequence/frame_divider.rb +22 -0
  160. data/lib/etassia/layout/sequence/frame_placement.rb +424 -0
  161. data/lib/etassia/layout/sequence/frame_reader.rb +75 -0
  162. data/lib/etassia/layout/sequence/frame_shape.rb +45 -0
  163. data/lib/etassia/layout/sequence/geometry.rb +24 -0
  164. data/lib/etassia/layout/sequence/message_rows.rb +165 -0
  165. data/lib/etassia/layout/sequence/note.rb +16 -0
  166. data/lib/etassia/layout/sequence/note_box.rb +114 -0
  167. data/lib/etassia/layout/sequence/note_placement.rb +223 -0
  168. data/lib/etassia/layout/sequence/text_width.rb +69 -0
  169. data/lib/etassia/layout/sequence/text_wrap.rb +93 -0
  170. data/lib/etassia/layout/sequence.rb +936 -0
  171. data/lib/etassia/layout/state_diagram.rb +1277 -0
  172. data/lib/etassia/layout/timeline.rb +165 -0
  173. data/lib/etassia/layout/timeline_card.rb +142 -0
  174. data/lib/etassia/layout/timeline_flow.rb +164 -0
  175. data/lib/etassia/layout/timeline_line.rb +33 -0
  176. data/lib/etassia/layout/timeline_rounding.rb +19 -0
  177. data/lib/etassia/layout/timeline_text.rb +105 -0
  178. data/lib/etassia/layout/treemap.rb +303 -0
  179. data/lib/etassia/layout/treemap_placement.rb +52 -0
  180. data/lib/etassia/layout/treemap_squarify.rb +108 -0
  181. data/lib/etassia/layout/user_journey.rb +300 -0
  182. data/lib/etassia/layout/user_journey_face.rb +33 -0
  183. data/lib/etassia/layout/user_journey_face_geometry.rb +80 -0
  184. data/lib/etassia/layout/user_journey_geometry.rb +336 -0
  185. data/lib/etassia/layout/user_journey_legend.rb +84 -0
  186. data/lib/etassia/layout/xy_chart.rb +620 -0
  187. data/lib/etassia/layout.rb +19 -0
  188. data/lib/etassia/lint_debt_scoreboard.rb +219 -0
  189. data/lib/etassia/markdown_text/emphasis_simulator.rb +373 -0
  190. data/lib/etassia/markdown_text.rb +393 -0
  191. data/lib/etassia/notation/builtin.rb +39 -0
  192. data/lib/etassia/notation/entry.rb +15 -0
  193. data/lib/etassia/notation/mermaid/class_diagram_config.rb +42 -0
  194. data/lib/etassia/notation/mermaid/git_graph_config.rb +46 -0
  195. data/lib/etassia/notation/mermaid/ir_adapter.rb +27 -0
  196. data/lib/etassia/notation/mermaid/ir_adapters/architecture.rb +196 -0
  197. data/lib/etassia/notation/mermaid/ir_adapters/block.rb +138 -0
  198. data/lib/etassia/notation/mermaid/ir_adapters/c4.rb +258 -0
  199. data/lib/etassia/notation/mermaid/ir_adapters/class_diagram.rb +257 -0
  200. data/lib/etassia/notation/mermaid/ir_adapters/er_diagram.rb +220 -0
  201. data/lib/etassia/notation/mermaid/ir_adapters/error.rb +32 -0
  202. data/lib/etassia/notation/mermaid/ir_adapters/flowchart.rb +208 -0
  203. data/lib/etassia/notation/mermaid/ir_adapters/gantt.rb +236 -0
  204. data/lib/etassia/notation/mermaid/ir_adapters/git_graph.rb +186 -0
  205. data/lib/etassia/notation/mermaid/ir_adapters/info.rb +34 -0
  206. data/lib/etassia/notation/mermaid/ir_adapters/kanban.rb +110 -0
  207. data/lib/etassia/notation/mermaid/ir_adapters/mindmap.rb +76 -0
  208. data/lib/etassia/notation/mermaid/ir_adapters/packet.rb +51 -0
  209. data/lib/etassia/notation/mermaid/ir_adapters/pie.rb +71 -0
  210. data/lib/etassia/notation/mermaid/ir_adapters/quadrant.rb +116 -0
  211. data/lib/etassia/notation/mermaid/ir_adapters/radar.rb +142 -0
  212. data/lib/etassia/notation/mermaid/ir_adapters/requirement.rb +237 -0
  213. data/lib/etassia/notation/mermaid/ir_adapters/sankey.rb +74 -0
  214. data/lib/etassia/notation/mermaid/ir_adapters/sequence.rb +304 -0
  215. data/lib/etassia/notation/mermaid/ir_adapters/state_diagram.rb +225 -0
  216. data/lib/etassia/notation/mermaid/ir_adapters/timeline.rb +93 -0
  217. data/lib/etassia/notation/mermaid/ir_adapters/treemap.rb +143 -0
  218. data/lib/etassia/notation/mermaid/ir_adapters/user_journey.rb +127 -0
  219. data/lib/etassia/notation/mermaid/ir_adapters/xychart.rb +131 -0
  220. data/lib/etassia/notation/mermaid.rb +399 -0
  221. data/lib/etassia/notation/parsed.rb +31 -0
  222. data/lib/etassia/notation/plantuml/activity/arrow_head.rb +45 -0
  223. data/lib/etassia/notation/plantuml/activity/arrow_style.rb +53 -0
  224. data/lib/etassia/notation/plantuml/activity/bar_columns.rb +75 -0
  225. data/lib/etassia/notation/plantuml/activity/box.rb +60 -0
  226. data/lib/etassia/notation/plantuml/activity/builder.rb +77 -0
  227. data/lib/etassia/notation/plantuml/activity/canvas.rb +117 -0
  228. data/lib/etassia/notation/plantuml/activity/column_centres.rb +151 -0
  229. data/lib/etassia/notation/plantuml/activity/columns.rb +224 -0
  230. data/lib/etassia/notation/plantuml/activity/diagram.rb +35 -0
  231. data/lib/etassia/notation/plantuml/activity/edge.rb +35 -0
  232. data/lib/etassia/notation/plantuml/activity/edge_marks.rb +68 -0
  233. data/lib/etassia/notation/plantuml/activity/exits.rb +61 -0
  234. data/lib/etassia/notation/plantuml/activity/frame_marks.rb +116 -0
  235. data/lib/etassia/notation/plantuml/activity/lane_steps.rb +82 -0
  236. data/lib/etassia/notation/plantuml/activity/lane_titles.rb +46 -0
  237. data/lib/etassia/notation/plantuml/activity/layout.rb +50 -0
  238. data/lib/etassia/notation/plantuml/activity/legacy_parser.rb +181 -0
  239. data/lib/etassia/notation/plantuml/activity/legacy_row_gaps.rb +65 -0
  240. data/lib/etassia/notation/plantuml/activity/mark.rb +23 -0
  241. data/lib/etassia/notation/plantuml/activity/marks.rb +62 -0
  242. data/lib/etassia/notation/plantuml/activity/metrics.rb +169 -0
  243. data/lib/etassia/notation/plantuml/activity/new_parser/blocks.rb +166 -0
  244. data/lib/etassia/notation/plantuml/activity/new_parser.rb +177 -0
  245. data/lib/etassia/notation/plantuml/activity/node.rb +50 -0
  246. data/lib/etassia/notation/plantuml/activity/node_marks.rb +174 -0
  247. data/lib/etassia/notation/plantuml/activity/note.rb +27 -0
  248. data/lib/etassia/notation/plantuml/activity/parser.rb +51 -0
  249. data/lib/etassia/notation/plantuml/activity/placement.rb +327 -0
  250. data/lib/etassia/notation/plantuml/activity/ranking.rb +142 -0
  251. data/lib/etassia/notation/plantuml/activity/reach.rb +58 -0
  252. data/lib/etassia/notation/plantuml/activity/renderer.rb +220 -0
  253. data/lib/etassia/notation/plantuml/activity/route.rb +22 -0
  254. data/lib/etassia/notation/plantuml/activity/router.rb +308 -0
  255. data/lib/etassia/notation/plantuml/activity/row_gaps.rb +105 -0
  256. data/lib/etassia/notation/plantuml/activity/row_spread.rb +59 -0
  257. data/lib/etassia/notation/plantuml/activity/scene.rb +20 -0
  258. data/lib/etassia/notation/plantuml/activity/text.rb +36 -0
  259. data/lib/etassia/notation/plantuml/activity.rb +50 -0
  260. data/lib/etassia/notation/plantuml/arrow.rb +135 -0
  261. data/lib/etassia/notation/plantuml/caption.rb +50 -0
  262. data/lib/etassia/notation/plantuml/caption_layout.rb +97 -0
  263. data/lib/etassia/notation/plantuml/caption_rows.rb +67 -0
  264. data/lib/etassia/notation/plantuml/class_header.rb +178 -0
  265. data/lib/etassia/notation/plantuml/class_name.rb +51 -0
  266. data/lib/etassia/notation/plantuml/component.rb +37 -0
  267. data/lib/etassia/notation/plantuml/creole.rb +278 -0
  268. data/lib/etassia/notation/plantuml/creole_icons.rb +33 -0
  269. data/lib/etassia/notation/plantuml/diagram.rb +60 -0
  270. data/lib/etassia/notation/plantuml/diagram_builder.rb +442 -0
  271. data/lib/etassia/notation/plantuml/diagram_name.rb +51 -0
  272. data/lib/etassia/notation/plantuml/directives.rb +33 -0
  273. data/lib/etassia/notation/plantuml/drawing/box.rb +28 -0
  274. data/lib/etassia/notation/plantuml/drawing/face.rb +39 -0
  275. data/lib/etassia/notation/plantuml/drawing/pen.rb +105 -0
  276. data/lib/etassia/notation/plantuml/drawing/renderer.rb +36 -0
  277. data/lib/etassia/notation/plantuml/drawing/scene.rb +28 -0
  278. data/lib/etassia/notation/plantuml/drawing.rb +19 -0
  279. data/lib/etassia/notation/plantuml/ebnf/blocks.rb +272 -0
  280. data/lib/etassia/notation/plantuml/ebnf/layout.rb +117 -0
  281. data/lib/etassia/notation/plantuml/ebnf/model.rb +22 -0
  282. data/lib/etassia/notation/plantuml/ebnf/reader.rb +131 -0
  283. data/lib/etassia/notation/plantuml/ebnf.rb +34 -0
  284. data/lib/etassia/notation/plantuml/end_texts.rb +78 -0
  285. data/lib/etassia/notation/plantuml/entity.rb +54 -0
  286. data/lib/etassia/notation/plantuml/further.rb +38 -0
  287. data/lib/etassia/notation/plantuml/gantt/layout.rb +269 -0
  288. data/lib/etassia/notation/plantuml/gantt/reader.rb +210 -0
  289. data/lib/etassia/notation/plantuml/gantt/renderer.rb +81 -0
  290. data/lib/etassia/notation/plantuml/gantt.rb +34 -0
  291. data/lib/etassia/notation/plantuml/include/blocks.rb +66 -0
  292. data/lib/etassia/notation/plantuml/include/finder.rb +61 -0
  293. data/lib/etassia/notation/plantuml/include.rb +78 -0
  294. data/lib/etassia/notation/plantuml/ir_adapter/annotations.rb +40 -0
  295. data/lib/etassia/notation/plantuml/ir_adapter/links.rb +91 -0
  296. data/lib/etassia/notation/plantuml/ir_adapter/sink.rb +49 -0
  297. data/lib/etassia/notation/plantuml/ir_adapter/structure.rb +86 -0
  298. data/lib/etassia/notation/plantuml/ir_adapter.rb +32 -0
  299. data/lib/etassia/notation/plantuml/ir_reader/annotations.rb +51 -0
  300. data/lib/etassia/notation/plantuml/ir_reader/index.rb +49 -0
  301. data/lib/etassia/notation/plantuml/ir_reader/links.rb +133 -0
  302. data/lib/etassia/notation/plantuml/ir_reader/structure.rb +83 -0
  303. data/lib/etassia/notation/plantuml/ir_reader.rb +42 -0
  304. data/lib/etassia/notation/plantuml/junction.rb +20 -0
  305. data/lib/etassia/notation/plantuml/klass.rb +41 -0
  306. data/lib/etassia/notation/plantuml/label_clearance.rb +67 -0
  307. data/lib/etassia/notation/plantuml/layout.rb +2806 -0
  308. data/lib/etassia/notation/plantuml/member.rb +37 -0
  309. data/lib/etassia/notation/plantuml/member_icon.rb +87 -0
  310. data/lib/etassia/notation/plantuml/member_icon_elements.rb +91 -0
  311. data/lib/etassia/notation/plantuml/member_pitch.rb +76 -0
  312. data/lib/etassia/notation/plantuml/member_rule.rb +48 -0
  313. data/lib/etassia/notation/plantuml/network/grid.rb +170 -0
  314. data/lib/etassia/notation/plantuml/network/layout.rb +158 -0
  315. data/lib/etassia/notation/plantuml/network/model.rb +22 -0
  316. data/lib/etassia/notation/plantuml/network/placement.rb +64 -0
  317. data/lib/etassia/notation/plantuml/network/reader.rb +126 -0
  318. data/lib/etassia/notation/plantuml/network/shapes.rb +109 -0
  319. data/lib/etassia/notation/plantuml/network.rb +37 -0
  320. data/lib/etassia/notation/plantuml/node_graph/archimate.rb +49 -0
  321. data/lib/etassia/notation/plantuml/node_graph/box_reader.rb +86 -0
  322. data/lib/etassia/notation/plantuml/node_graph/cloud.rb +73 -0
  323. data/lib/etassia/notation/plantuml/node_graph/crows_foot.rb +85 -0
  324. data/lib/etassia/notation/plantuml/node_graph/curve.rb +66 -0
  325. data/lib/etassia/notation/plantuml/node_graph/data_reader.rb +77 -0
  326. data/lib/etassia/notation/plantuml/node_graph/data_shape.rb +39 -0
  327. data/lib/etassia/notation/plantuml/node_graph/detour.rb +98 -0
  328. data/lib/etassia/notation/plantuml/node_graph/edge.rb +26 -0
  329. data/lib/etassia/notation/plantuml/node_graph/entity_metrics.rb +49 -0
  330. data/lib/etassia/notation/plantuml/node_graph/entity_reader.rb +65 -0
  331. data/lib/etassia/notation/plantuml/node_graph/entity_shape.rb +111 -0
  332. data/lib/etassia/notation/plantuml/node_graph/figures.rb +92 -0
  333. data/lib/etassia/notation/plantuml/node_graph/frame_ports.rb +73 -0
  334. data/lib/etassia/notation/plantuml/node_graph/graph.rb +40 -0
  335. data/lib/etassia/notation/plantuml/node_graph/heads.rb +89 -0
  336. data/lib/etassia/notation/plantuml/node_graph/label_fan.rb +76 -0
  337. data/lib/etassia/notation/plantuml/node_graph/label_placer.rb +310 -0
  338. data/lib/etassia/notation/plantuml/node_graph/label_waypoint.rb +47 -0
  339. data/lib/etassia/notation/plantuml/node_graph/layout.rb +293 -0
  340. data/lib/etassia/notation/plantuml/node_graph/link.rb +78 -0
  341. data/lib/etassia/notation/plantuml/node_graph/macros.rb +39 -0
  342. data/lib/etassia/notation/plantuml/node_graph/metrics.rb +113 -0
  343. data/lib/etassia/notation/plantuml/node_graph/node.rb +44 -0
  344. data/lib/etassia/notation/plantuml/node_graph/note_tail.rb +58 -0
  345. data/lib/etassia/notation/plantuml/node_graph/packer.rb +126 -0
  346. data/lib/etassia/notation/plantuml/node_graph/parser.rb +309 -0
  347. data/lib/etassia/notation/plantuml/node_graph/paths.rb +48 -0
  348. data/lib/etassia/notation/plantuml/node_graph/ranking.rb +231 -0
  349. data/lib/etassia/notation/plantuml/node_graph/reader.rb +53 -0
  350. data/lib/etassia/notation/plantuml/node_graph/references.rb +38 -0
  351. data/lib/etassia/notation/plantuml/node_graph/renderer.rb +160 -0
  352. data/lib/etassia/notation/plantuml/node_graph/scene.rb +22 -0
  353. data/lib/etassia/notation/plantuml/node_graph/shapes.rb +302 -0
  354. data/lib/etassia/notation/plantuml/node_graph/skipped_ranks.rb +61 -0
  355. data/lib/etassia/notation/plantuml/node_graph/tab.rb +68 -0
  356. data/lib/etassia/notation/plantuml/node_graph/usecase_ellipse.rb +100 -0
  357. data/lib/etassia/notation/plantuml/node_graph.rb +38 -0
  358. data/lib/etassia/notation/plantuml/note.rb +27 -0
  359. data/lib/etassia/notation/plantuml/note_layout.rb +257 -0
  360. data/lib/etassia/notation/plantuml/note_outline.rb +68 -0
  361. data/lib/etassia/notation/plantuml/objects/map_rows.rb +46 -0
  362. data/lib/etassia/notation/plantuml/objects/reader.rb +208 -0
  363. data/lib/etassia/notation/plantuml/objects.rb +39 -0
  364. data/lib/etassia/notation/plantuml/package.rb +45 -0
  365. data/lib/etassia/notation/plantuml/package_color.rb +32 -0
  366. data/lib/etassia/notation/plantuml/package_frames.rb +178 -0
  367. data/lib/etassia/notation/plantuml/parser.rb +622 -0
  368. data/lib/etassia/notation/plantuml/portion_visibility.rb +91 -0
  369. data/lib/etassia/notation/plantuml/qualified_association_layout.rb +332 -0
  370. data/lib/etassia/notation/plantuml/qualifier_box.rb +78 -0
  371. data/lib/etassia/notation/plantuml/rank_chain.rb +527 -0
  372. data/lib/etassia/notation/plantuml/rank_hierarchy.rb +347 -0
  373. data/lib/etassia/notation/plantuml/relation.rb +103 -0
  374. data/lib/etassia/notation/plantuml/relation_label_separator.rb +199 -0
  375. data/lib/etassia/notation/plantuml/relation_path_points.rb +84 -0
  376. data/lib/etassia/notation/plantuml/renderer.rb +490 -0
  377. data/lib/etassia/notation/plantuml/scene.rb +134 -0
  378. data/lib/etassia/notation/plantuml/segment_box.rb +85 -0
  379. data/lib/etassia/notation/plantuml/sequence/activation.rb +28 -0
  380. data/lib/etassia/notation/plantuml/sequence/appearance.rb +71 -0
  381. data/lib/etassia/notation/plantuml/sequence/arrow_end.rb +23 -0
  382. data/lib/etassia/notation/plantuml/sequence/arrow_marks.rb +111 -0
  383. data/lib/etassia/notation/plantuml/sequence/arrow_style.rb +42 -0
  384. data/lib/etassia/notation/plantuml/sequence/arrow_syntax.rb +122 -0
  385. data/lib/etassia/notation/plantuml/sequence/bar_tracker.rb +59 -0
  386. data/lib/etassia/notation/plantuml/sequence/box.rb +23 -0
  387. data/lib/etassia/notation/plantuml/sequence/chrome.rb +60 -0
  388. data/lib/etassia/notation/plantuml/sequence/chrome_rows.rb +184 -0
  389. data/lib/etassia/notation/plantuml/sequence/create.rb +34 -0
  390. data/lib/etassia/notation/plantuml/sequence/delay.rb +19 -0
  391. data/lib/etassia/notation/plantuml/sequence/destroy.rb +20 -0
  392. data/lib/etassia/notation/plantuml/sequence/diagram.rb +86 -0
  393. data/lib/etassia/notation/plantuml/sequence/divider.rb +19 -0
  394. data/lib/etassia/notation/plantuml/sequence/edge.rb +53 -0
  395. data/lib/etassia/notation/plantuml/sequence/embedded.rb +77 -0
  396. data/lib/etassia/notation/plantuml/sequence/fill.rb +56 -0
  397. data/lib/etassia/notation/plantuml/sequence/fragment.rb +28 -0
  398. data/lib/etassia/notation/plantuml/sequence/fragment_shape.rb +138 -0
  399. data/lib/etassia/notation/plantuml/sequence/head_style.rb +43 -0
  400. data/lib/etassia/notation/plantuml/sequence/ir_adapter/blocks.rb +73 -0
  401. data/lib/etassia/notation/plantuml/sequence/ir_adapter/fills.rb +24 -0
  402. data/lib/etassia/notation/plantuml/sequence/ir_adapter/items.rb +48 -0
  403. data/lib/etassia/notation/plantuml/sequence/ir_adapter/messages.rb +62 -0
  404. data/lib/etassia/notation/plantuml/sequence/ir_adapter/participants.rb +42 -0
  405. data/lib/etassia/notation/plantuml/sequence/ir_adapter/settings.rb +54 -0
  406. data/lib/etassia/notation/plantuml/sequence/ir_adapter.rb +38 -0
  407. data/lib/etassia/notation/plantuml/sequence/ir_reader/blocks.rb +77 -0
  408. data/lib/etassia/notation/plantuml/sequence/ir_reader/fills.rb +27 -0
  409. data/lib/etassia/notation/plantuml/sequence/ir_reader/index.rb +36 -0
  410. data/lib/etassia/notation/plantuml/sequence/ir_reader/items.rb +35 -0
  411. data/lib/etassia/notation/plantuml/sequence/ir_reader/messages.rb +70 -0
  412. data/lib/etassia/notation/plantuml/sequence/ir_reader/participants.rb +42 -0
  413. data/lib/etassia/notation/plantuml/sequence/ir_reader/settings.rb +74 -0
  414. data/lib/etassia/notation/plantuml/sequence/ir_reader.rb +38 -0
  415. data/lib/etassia/notation/plantuml/sequence/keyword_defaults.rb +28 -0
  416. data/lib/etassia/notation/plantuml/sequence/layout.rb +1567 -0
  417. data/lib/etassia/notation/plantuml/sequence/message.rb +97 -0
  418. data/lib/etassia/notation/plantuml/sequence/message_wrap.rb +77 -0
  419. data/lib/etassia/notation/plantuml/sequence/note.rb +61 -0
  420. data/lib/etassia/notation/plantuml/sequence/note_fill.rb +35 -0
  421. data/lib/etassia/notation/plantuml/sequence/note_geometry.rb +126 -0
  422. data/lib/etassia/notation/plantuml/sequence/note_shape.rb +51 -0
  423. data/lib/etassia/notation/plantuml/sequence/numbered_label.rb +57 -0
  424. data/lib/etassia/notation/plantuml/sequence/numbering.rb +49 -0
  425. data/lib/etassia/notation/plantuml/sequence/outline.rb +204 -0
  426. data/lib/etassia/notation/plantuml/sequence/page_break.rb +17 -0
  427. data/lib/etassia/notation/plantuml/sequence/paint.rb +79 -0
  428. data/lib/etassia/notation/plantuml/sequence/parallel_message.rb +19 -0
  429. data/lib/etassia/notation/plantuml/sequence/parser.rb +844 -0
  430. data/lib/etassia/notation/plantuml/sequence/participant.rb +27 -0
  431. data/lib/etassia/notation/plantuml/sequence/picture.rb +29 -0
  432. data/lib/etassia/notation/plantuml/sequence/ref.rb +24 -0
  433. data/lib/etassia/notation/plantuml/sequence/ref_shape.rb +74 -0
  434. data/lib/etassia/notation/plantuml/sequence/refusals.rb +47 -0
  435. data/lib/etassia/notation/plantuml/sequence/renderer.rb +501 -0
  436. data/lib/etassia/notation/plantuml/sequence/scene.rb +200 -0
  437. data/lib/etassia/notation/plantuml/sequence/skin.rb +76 -0
  438. data/lib/etassia/notation/plantuml/sequence/spacing.rb +28 -0
  439. data/lib/etassia/notation/plantuml/sequence/style.rb +151 -0
  440. data/lib/etassia/notation/plantuml/sequence/tab_size.rb +42 -0
  441. data/lib/etassia/notation/plantuml/sequence/walker.rb +1190 -0
  442. data/lib/etassia/notation/plantuml/sequence.rb +47 -0
  443. data/lib/etassia/notation/plantuml/source_lines.rb +33 -0
  444. data/lib/etassia/notation/plantuml/state/axis_solver.rb +272 -0
  445. data/lib/etassia/notation/plantuml/state/cluster_padding.rb +75 -0
  446. data/lib/etassia/notation/plantuml/state/curve.rb +73 -0
  447. data/lib/etassia/notation/plantuml/state/dot_axis.rb +248 -0
  448. data/lib/etassia/notation/plantuml/state/extent.rb +42 -0
  449. data/lib/etassia/notation/plantuml/state/hierarchy.rb +355 -0
  450. data/lib/etassia/notation/plantuml/state/label_clearance.rb +148 -0
  451. data/lib/etassia/notation/plantuml/state/label_reach.rb +139 -0
  452. data/lib/etassia/notation/plantuml/state/layout.rb +173 -0
  453. data/lib/etassia/notation/plantuml/state/level_edges.rb +113 -0
  454. data/lib/etassia/notation/plantuml/state/look.rb +36 -0
  455. data/lib/etassia/notation/plantuml/state/note_shape.rb +73 -0
  456. data/lib/etassia/notation/plantuml/state/plane.rb +85 -0
  457. data/lib/etassia/notation/plantuml/state/ranking.rb +181 -0
  458. data/lib/etassia/notation/plantuml/state/reader.rb +310 -0
  459. data/lib/etassia/notation/plantuml/state/renderer.rb +253 -0
  460. data/lib/etassia/notation/plantuml/state/route_detour.rb +115 -0
  461. data/lib/etassia/notation/plantuml/state/route_geometry.rb +186 -0
  462. data/lib/etassia/notation/plantuml/state/routes.rb +196 -0
  463. data/lib/etassia/notation/plantuml/state/row.rb +88 -0
  464. data/lib/etassia/notation/plantuml/state/rows.rb +50 -0
  465. data/lib/etassia/notation/plantuml/state/stretch.rb +71 -0
  466. data/lib/etassia/notation/plantuml/state.rb +62 -0
  467. data/lib/etassia/notation/plantuml/stereotype_head.rb +58 -0
  468. data/lib/etassia/notation/plantuml/style/bound.rb +34 -0
  469. data/lib/etassia/notation/plantuml/style/colour.rb +41 -0
  470. data/lib/etassia/notation/plantuml/style/colour_names.rb +89 -0
  471. data/lib/etassia/notation/plantuml/style/defaults.rb +33 -0
  472. data/lib/etassia/notation/plantuml/style/inline.rb +56 -0
  473. data/lib/etassia/notation/plantuml/style/reader.rb +242 -0
  474. data/lib/etassia/notation/plantuml/style/sheet.rb +103 -0
  475. data/lib/etassia/notation/plantuml/style.rb +32 -0
  476. data/lib/etassia/notation/plantuml/style_reader.rb +160 -0
  477. data/lib/etassia/notation/plantuml/style_sheet.rb +27 -0
  478. data/lib/etassia/notation/plantuml/styled_relation_curve.rb +71 -0
  479. data/lib/etassia/notation/plantuml/timing/arrows.rb +81 -0
  480. data/lib/etassia/notation/plantuml/timing/axis.rb +100 -0
  481. data/lib/etassia/notation/plantuml/timing/layout.rb +172 -0
  482. data/lib/etassia/notation/plantuml/timing/model.rb +34 -0
  483. data/lib/etassia/notation/plantuml/timing/reader.rb +173 -0
  484. data/lib/etassia/notation/plantuml/timing/signals.rb +328 -0
  485. data/lib/etassia/notation/plantuml/timing.rb +38 -0
  486. data/lib/etassia/notation/plantuml/tree/layout.rb +276 -0
  487. data/lib/etassia/notation/plantuml/tree/mindmap_layout.rb +157 -0
  488. data/lib/etassia/notation/plantuml/tree/reader.rb +186 -0
  489. data/lib/etassia/notation/plantuml/tree/renderer.rb +67 -0
  490. data/lib/etassia/notation/plantuml/tree/role.rb +57 -0
  491. data/lib/etassia/notation/plantuml/tree/rules.rb +156 -0
  492. data/lib/etassia/notation/plantuml/tree/wbs_layout.rb +164 -0
  493. data/lib/etassia/notation/plantuml/tree.rb +41 -0
  494. data/lib/etassia/notation/plantuml/unsupported_construct_error.rb +39 -0
  495. data/lib/etassia/notation/plantuml/unsupported_constructs.rb +103 -0
  496. data/lib/etassia/notation/plantuml/usecase.rb +36 -0
  497. data/lib/etassia/notation/plantuml.rb +132 -0
  498. data/lib/etassia/notation/plantuml_data/boxes/frames.rb +101 -0
  499. data/lib/etassia/notation/plantuml_data/boxes/graph_layout.rb +348 -0
  500. data/lib/etassia/notation/plantuml_data/boxes/heads.rb +65 -0
  501. data/lib/etassia/notation/plantuml_data/boxes/highlights.rb +70 -0
  502. data/lib/etassia/notation/plantuml_data/boxes/inline_rows.rb +99 -0
  503. data/lib/etassia/notation/plantuml_data/boxes/json_reader.rb +24 -0
  504. data/lib/etassia/notation/plantuml_data/boxes/link_text.rb +94 -0
  505. data/lib/etassia/notation/plantuml_data/boxes/marks.rb +76 -0
  506. data/lib/etassia/notation/plantuml_data/boxes/ornaments.rb +130 -0
  507. data/lib/etassia/notation/plantuml_data/boxes/renderer.rb +95 -0
  508. data/lib/etassia/notation/plantuml_data/boxes/row_alignment.rb +58 -0
  509. data/lib/etassia/notation/plantuml_data/boxes/row_bend.rb +43 -0
  510. data/lib/etassia/notation/plantuml_data/boxes/scene.rb +27 -0
  511. data/lib/etassia/notation/plantuml_data/boxes/table.rb +56 -0
  512. data/lib/etassia/notation/plantuml_data/boxes/table_box.rb +355 -0
  513. data/lib/etassia/notation/plantuml_data/boxes/text.rb +101 -0
  514. data/lib/etassia/notation/plantuml_data/boxes/tree_builder.rb +82 -0
  515. data/lib/etassia/notation/plantuml_data/boxes/tree_layout.rb +82 -0
  516. data/lib/etassia/notation/plantuml_data/boxes/yaml_reader.rb +105 -0
  517. data/lib/etassia/notation/plantuml_data/openiconic.LICENSE +6 -0
  518. data/lib/etassia/notation/plantuml_data/openiconic.json +1 -0
  519. data/lib/etassia/notation/plantuml_data.rb +84 -0
  520. data/lib/etassia/notation/plantuml_salt/block.rb +80 -0
  521. data/lib/etassia/notation/plantuml_salt/box.rb +30 -0
  522. data/lib/etassia/notation/plantuml_salt/font_metrics.rb +28 -0
  523. data/lib/etassia/notation/plantuml_salt/grid_marks.rb +66 -0
  524. data/lib/etassia/notation/plantuml_salt/group_marks.rb +77 -0
  525. data/lib/etassia/notation/plantuml_salt/layout.rb +206 -0
  526. data/lib/etassia/notation/plantuml_salt/mark.rb +36 -0
  527. data/lib/etassia/notation/plantuml_salt/markup.rb +57 -0
  528. data/lib/etassia/notation/plantuml_salt/parser.rb +239 -0
  529. data/lib/etassia/notation/plantuml_salt/renderer.rb +138 -0
  530. data/lib/etassia/notation/plantuml_salt/row.rb +19 -0
  531. data/lib/etassia/notation/plantuml_salt/run.rb +34 -0
  532. data/lib/etassia/notation/plantuml_salt/scene.rb +23 -0
  533. data/lib/etassia/notation/plantuml_salt/scroll_marks.rb +94 -0
  534. data/lib/etassia/notation/plantuml_salt/tab_marks.rb +74 -0
  535. data/lib/etassia/notation/plantuml_salt/tree_marks.rb +127 -0
  536. data/lib/etassia/notation/plantuml_salt/widget.rb +40 -0
  537. data/lib/etassia/notation/plantuml_salt/widget_marks.rb +319 -0
  538. data/lib/etassia/notation/plantuml_salt.rb +66 -0
  539. data/lib/etassia/notation/plugin_failure.rb +42 -0
  540. data/lib/etassia/notation.rb +289 -0
  541. data/lib/etassia/parser/architecture.rb +25 -0
  542. data/lib/etassia/parser/atoms/delimited_run.rb +82 -0
  543. data/lib/etassia/parser/atoms/greedy_run.rb +116 -0
  544. data/lib/etassia/parser/atoms/joined.rb +47 -0
  545. data/lib/etassia/parser/atoms/trimmed_run.rb +47 -0
  546. data/lib/etassia/parser/base.rb +258 -0
  547. data/lib/etassia/parser/block.rb +32 -0
  548. data/lib/etassia/parser/builders/architecture.rb +135 -0
  549. data/lib/etassia/parser/builders/block.rb +288 -0
  550. data/lib/etassia/parser/builders/c4.rb +275 -0
  551. data/lib/etassia/parser/builders/capture_string.rb +17 -0
  552. data/lib/etassia/parser/builders/class_diagram.rb +539 -0
  553. data/lib/etassia/parser/builders/er_diagram.rb +308 -0
  554. data/lib/etassia/parser/builders/error.rb +51 -0
  555. data/lib/etassia/parser/builders/flowchart.rb +1150 -0
  556. data/lib/etassia/parser/builders/gantt.rb +267 -0
  557. data/lib/etassia/parser/builders/git_graph.rb +301 -0
  558. data/lib/etassia/parser/builders/info.rb +57 -0
  559. data/lib/etassia/parser/builders/kanban.rb +411 -0
  560. data/lib/etassia/parser/builders/mindmap.rb +271 -0
  561. data/lib/etassia/parser/builders/packet.rb +85 -0
  562. data/lib/etassia/parser/builders/pie.rb +105 -0
  563. data/lib/etassia/parser/builders/quadrant.rb +165 -0
  564. data/lib/etassia/parser/builders/radar.rb +157 -0
  565. data/lib/etassia/parser/builders/requirement.rb +230 -0
  566. data/lib/etassia/parser/builders/sankey.rb +142 -0
  567. data/lib/etassia/parser/builders/sequence.rb +431 -0
  568. data/lib/etassia/parser/builders/sequence_blocks.rb +117 -0
  569. data/lib/etassia/parser/builders/sequence_box_title.rb +57 -0
  570. data/lib/etassia/parser/builders/state_diagram.rb +364 -0
  571. data/lib/etassia/parser/builders/timeline.rb +133 -0
  572. data/lib/etassia/parser/builders/treemap.rb +138 -0
  573. data/lib/etassia/parser/builders/xy_chart.rb +148 -0
  574. data/lib/etassia/parser/c4.rb +37 -0
  575. data/lib/etassia/parser/class_diagram.rb +58 -0
  576. data/lib/etassia/parser/class_namespaces.rb +62 -0
  577. data/lib/etassia/parser/class_notes.rb +54 -0
  578. data/lib/etassia/parser/er_diagram.rb +28 -0
  579. data/lib/etassia/parser/error.rb +30 -0
  580. data/lib/etassia/parser/flowchart.rb +87 -0
  581. data/lib/etassia/parser/gantt.rb +43 -0
  582. data/lib/etassia/parser/git_graph.rb +106 -0
  583. data/lib/etassia/parser/grammars/architecture.rb +165 -0
  584. data/lib/etassia/parser/grammars/block.rb +190 -0
  585. data/lib/etassia/parser/grammars/c4.rb +229 -0
  586. data/lib/etassia/parser/grammars/class_diagram.rb +606 -0
  587. data/lib/etassia/parser/grammars/common.rb +107 -0
  588. data/lib/etassia/parser/grammars/er_diagram.rb +232 -0
  589. data/lib/etassia/parser/grammars/error.rb +32 -0
  590. data/lib/etassia/parser/grammars/flowchart.rb +1722 -0
  591. data/lib/etassia/parser/grammars/gantt.rb +198 -0
  592. data/lib/etassia/parser/grammars/git_graph.rb +234 -0
  593. data/lib/etassia/parser/grammars/info.rb +53 -0
  594. data/lib/etassia/parser/grammars/kanban.rb +417 -0
  595. data/lib/etassia/parser/grammars/mermaid_unicode_text.rb +108 -0
  596. data/lib/etassia/parser/grammars/mindmap.rb +220 -0
  597. data/lib/etassia/parser/grammars/packet.rb +71 -0
  598. data/lib/etassia/parser/grammars/pie.rb +128 -0
  599. data/lib/etassia/parser/grammars/quadrant.rb +202 -0
  600. data/lib/etassia/parser/grammars/radar.rb +158 -0
  601. data/lib/etassia/parser/grammars/requirement.rb +258 -0
  602. data/lib/etassia/parser/grammars/sankey.rb +104 -0
  603. data/lib/etassia/parser/grammars/sequence.rb +940 -0
  604. data/lib/etassia/parser/grammars/state_diagram.rb +382 -0
  605. data/lib/etassia/parser/grammars/timeline.rb +153 -0
  606. data/lib/etassia/parser/grammars/treemap.rb +122 -0
  607. data/lib/etassia/parser/grammars/xy_chart.rb +119 -0
  608. data/lib/etassia/parser/info.rb +32 -0
  609. data/lib/etassia/parser/kanban.rb +131 -0
  610. data/lib/etassia/parser/mermaid_shapes.rb +106 -0
  611. data/lib/etassia/parser/metadata_yaml.rb +452 -0
  612. data/lib/etassia/parser/mindmap.rb +92 -0
  613. data/lib/etassia/parser/native/builders/architecture.rb +103 -0
  614. data/lib/etassia/parser/native/builders/block.rb +68 -0
  615. data/lib/etassia/parser/native/builders/c4.rb +16 -0
  616. data/lib/etassia/parser/native/builders/class_diagram.rb +63 -0
  617. data/lib/etassia/parser/native/builders/er_diagram.rb +47 -0
  618. data/lib/etassia/parser/native/builders/flowchart.rb +58 -0
  619. data/lib/etassia/parser/native/builders/gantt.rb +35 -0
  620. data/lib/etassia/parser/native/builders/gitgraph.rb +35 -0
  621. data/lib/etassia/parser/native/builders/info.rb +30 -0
  622. data/lib/etassia/parser/native/builders/kanban.rb +37 -0
  623. data/lib/etassia/parser/native/builders/mindmap.rb +26 -0
  624. data/lib/etassia/parser/native/builders/packet.rb +63 -0
  625. data/lib/etassia/parser/native/builders/pie.rb +82 -0
  626. data/lib/etassia/parser/native/builders/quadrant.rb +27 -0
  627. data/lib/etassia/parser/native/builders/radar.rb +84 -0
  628. data/lib/etassia/parser/native/builders/requirement.rb +25 -0
  629. data/lib/etassia/parser/native/builders/sankey.rb +18 -0
  630. data/lib/etassia/parser/native/builders/sequence.rb +48 -0
  631. data/lib/etassia/parser/native/builders/state_diagram.rb +61 -0
  632. data/lib/etassia/parser/native/builders/timeline.rb +46 -0
  633. data/lib/etassia/parser/native/builders/treemap.rb +60 -0
  634. data/lib/etassia/parser/native/builders/user_journey.rb +83 -0
  635. data/lib/etassia/parser/native/builders/xy_chart.rb +94 -0
  636. data/lib/etassia/parser/native/declined.rb +12 -0
  637. data/lib/etassia/parser/native/mermaid.json +1 -0
  638. data/lib/etassia/parser/native/text.rb +31 -0
  639. data/lib/etassia/parser/native.rb +99 -0
  640. data/lib/etassia/parser/packet.rb +56 -0
  641. data/lib/etassia/parser/pie.rb +32 -0
  642. data/lib/etassia/parser/quadrant.rb +77 -0
  643. data/lib/etassia/parser/radar.rb +99 -0
  644. data/lib/etassia/parser/requirement.rb +32 -0
  645. data/lib/etassia/parser/sankey.rb +47 -0
  646. data/lib/etassia/parser/sequence.rb +34 -0
  647. data/lib/etassia/parser/state_diagram.rb +29 -0
  648. data/lib/etassia/parser/timeline.rb +40 -0
  649. data/lib/etassia/parser/treemap.rb +25 -0
  650. data/lib/etassia/parser/user_journey.rb +253 -0
  651. data/lib/etassia/parser/xy_chart.rb +98 -0
  652. data/lib/etassia/parser.rb +18 -0
  653. data/lib/etassia/renderer/architecture.rb +232 -0
  654. data/lib/etassia/renderer/architecture_edge_router.rb +379 -0
  655. data/lib/etassia/renderer/base.rb +295 -0
  656. data/lib/etassia/renderer/block.rb +157 -0
  657. data/lib/etassia/renderer/c4.rb +294 -0
  658. data/lib/etassia/renderer/class_diagram.rb +671 -0
  659. data/lib/etassia/renderer/er_diagram.rb +842 -0
  660. data/lib/etassia/renderer/error.rb +131 -0
  661. data/lib/etassia/renderer/flowchart.rb +315 -0
  662. data/lib/etassia/renderer/gantt.rb +177 -0
  663. data/lib/etassia/renderer/git_graph.rb +224 -0
  664. data/lib/etassia/renderer/info.rb +59 -0
  665. data/lib/etassia/renderer/kanban.rb +157 -0
  666. data/lib/etassia/renderer/line_break_text.rb +56 -0
  667. data/lib/etassia/renderer/markdown_text.rb +106 -0
  668. data/lib/etassia/renderer/mindmap.rb +290 -0
  669. data/lib/etassia/renderer/packet.rb +87 -0
  670. data/lib/etassia/renderer/pie.rb +113 -0
  671. data/lib/etassia/renderer/quadrant.rb +189 -0
  672. data/lib/etassia/renderer/radar.rb +191 -0
  673. data/lib/etassia/renderer/requirement.rb +209 -0
  674. data/lib/etassia/renderer/sankey.rb +114 -0
  675. data/lib/etassia/renderer/sequence/frame_drawing.rb +169 -0
  676. data/lib/etassia/renderer/sequence/note_drawing.rb +69 -0
  677. data/lib/etassia/renderer/sequence.rb +490 -0
  678. data/lib/etassia/renderer/state_diagram.rb +202 -0
  679. data/lib/etassia/renderer/timeline.rb +134 -0
  680. data/lib/etassia/renderer/timeline_palette.rb +18 -0
  681. data/lib/etassia/renderer/treemap/section_total.rb +33 -0
  682. data/lib/etassia/renderer/treemap.rb +128 -0
  683. data/lib/etassia/renderer/user_journey.rb +209 -0
  684. data/lib/etassia/renderer/xy_chart.rb +180 -0
  685. data/lib/etassia/renderer.rb +22 -0
  686. data/lib/etassia/runtime.rb +125 -0
  687. data/lib/etassia/source/frontmatter.rb +501 -0
  688. data/lib/etassia/source.rb +224 -0
  689. data/lib/etassia/svg/arrowhead.rb +205 -0
  690. data/lib/etassia/svg/circle.rb +19 -0
  691. data/lib/etassia/svg/document.rb +106 -0
  692. data/lib/etassia/svg/element.rb +209 -0
  693. data/lib/etassia/svg/ellipse.rb +23 -0
  694. data/lib/etassia/svg/escaping.rb +149 -0
  695. data/lib/etassia/svg/group.rb +64 -0
  696. data/lib/etassia/svg/line.rb +21 -0
  697. data/lib/etassia/svg/numbers.rb +53 -0
  698. data/lib/etassia/svg/path.rb +67 -0
  699. data/lib/etassia/svg/path_geometry.rb +378 -0
  700. data/lib/etassia/svg/polygon.rb +25 -0
  701. data/lib/etassia/svg/polyline.rb +28 -0
  702. data/lib/etassia/svg/rect.rb +34 -0
  703. data/lib/etassia/svg/style.rb +50 -0
  704. data/lib/etassia/svg/text.rb +181 -0
  705. data/lib/etassia/svg/tspan.rb +48 -0
  706. data/lib/etassia/svg.rb +38 -0
  707. data/lib/etassia/text_measurement/arial_advances.rb +122 -0
  708. data/lib/etassia/text_measurement/dejavu_advances.rb +111 -0
  709. data/lib/etassia/text_measurement/times_advances.rb +64 -0
  710. data/lib/etassia/text_measurement/trebuchet_advances.rb +53 -0
  711. data/lib/etassia/text_measurement/trebuchet_kerning.rb +29 -0
  712. data/lib/etassia/text_measurement.rb +198 -0
  713. data/lib/etassia/theme/builtin/dark.yml +229 -0
  714. data/lib/etassia/theme/builtin/default.yml +239 -0
  715. data/lib/etassia/theme/builtin/high_contrast.yml +231 -0
  716. data/lib/etassia/theme/builtin/light.yml +229 -0
  717. data/lib/etassia/theme/color_palette.rb +97 -0
  718. data/lib/etassia/theme/completion.rb +50 -0
  719. data/lib/etassia/theme/effect_styles.rb +34 -0
  720. data/lib/etassia/theme/registry.rb +45 -0
  721. data/lib/etassia/theme/shape_styles.rb +34 -0
  722. data/lib/etassia/theme/spacing_config.rb +30 -0
  723. data/lib/etassia/theme/typography.rb +40 -0
  724. data/lib/etassia/theme.rb +73 -0
  725. data/lib/etassia/version.rb +5 -0
  726. data/lib/etassia.rb +40 -0
  727. metadata +865 -0
@@ -0,0 +1,411 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ require_relative "../metadata_yaml"
6
+
7
+ module Etassia
8
+ module Parser
9
+ module Builders
10
+ # Transform for Kanban diagrams
11
+ class Kanban < Parslet::Transform
12
+ # Helper class to build kanban board from indented lines
13
+ class BoardBuilder
14
+ attr_reader :columns
15
+
16
+ def initialize
17
+ @columns = []
18
+ @current_column = nil
19
+ @items = []
20
+ @anonymous_index = 0
21
+ end
22
+
23
+ def add_line(line_data)
24
+ return apply_modifier_line(line_data) if modifier?(line_data)
25
+
26
+ @items << item_from(line_data)
27
+ end
28
+
29
+ def modifier?(line_data)
30
+ line_data[:icon] || line_data[:classes]
31
+ end
32
+
33
+ def apply_modifier_line(line_data)
34
+ if line_data[:icon]
35
+ return apply_modifier(:icon, line_data[:icon].to_s)
36
+ end
37
+
38
+ classes = line_data[:classes].to_s.strip.split(/\s+/)
39
+ apply_modifier(:classes, classes)
40
+ end
41
+
42
+ # An item with no bracket label displays its id. Metadata is parsed
43
+ # here so both add_card and add_column see it. An absent label is
44
+ # nil, so `|| id` safely falls back without hiding an empty string.
45
+ def item_from(line_data)
46
+ id = resolve_id(line_data[:id])
47
+ {
48
+ id: id,
49
+ text: line_data[:text]&.to_s || id,
50
+ indent: get_indent_size(line_data[:indent]),
51
+ metadata: parse_metadata(line_data[:metadata]),
52
+ }
53
+ end
54
+
55
+ def finalize
56
+ return if @items.empty?
57
+
58
+ # The first item's indent is the column level, as in mermaid's
59
+ # getSection. An item at that indent is a column; any other
60
+ # indent is a card of the latest column, including one LESS
61
+ # indented than the first. Mermaid rejects a less-indented item
62
+ # only once another item follows it.
63
+ build_columns(@items.first[:indent])
64
+ end
65
+
66
+ private
67
+
68
+ def build_columns(column_indent)
69
+ shallower = nil
70
+ @items.each do |item|
71
+ refuse_item_after(shallower) if shallower
72
+ shallower = item if item[:indent] < column_indent
73
+ item[:indent] == column_indent ? add_column(item) : add_card(item)
74
+ end
75
+ end
76
+
77
+ def refuse_item_after(item)
78
+ raise Parser::ParseError,
79
+ "Items without section detected, " \
80
+ "found section (\"#{column_title(item)}\")."
81
+ end
82
+
83
+ # A shape with no id (`(text)`) never captures `:id`. Mermaid
84
+ # still draws it - it auto-assigns one - so this does too,
85
+ # deterministically: parsing the same source twice must produce
86
+ # the same diagram, and a random id would break that on every
87
+ # call (see the `kanban-1` / `kanban-2` assertions in
88
+ # spec/etassia/parser/kanban_spec.rb). Unlike
89
+ # Builders::Block.anonymous_id, this id is never displayed -
90
+ # `unlabelled_shaped_item` always captures its own `:text`, so
91
+ # `resolve_id`'s output never reaches TextMeasurement and its
92
+ # length has no effect on layout (measured: extending it by
93
+ # 1,000 characters left the rendered SVG byte-identical). The
94
+ # hyphen keeps generated ids out of the author's namespace,
95
+ # since identifier_char is `[a-zA-Z0-9_]` and can never spell
96
+ # one.
97
+ def resolve_id(id_slice)
98
+ return id_slice.to_s if id_slice
99
+
100
+ @anonymous_index += 1
101
+ "kanban-#{@anonymous_index}"
102
+ end
103
+
104
+ def add_column(item)
105
+ column = {
106
+ id: item[:id],
107
+ title: column_title(item),
108
+ icon: item[:metadata][:icon],
109
+ classes: normalize_classes(item[:metadata][:classes]),
110
+ cards: [],
111
+ }
112
+
113
+ @columns << column
114
+ @current_column = column
115
+ end
116
+
117
+ # `::icon(...)`/`:::` lines modify whichever item was added last;
118
+ # raise rather than silently drop when there is none, matching
119
+ # mmdc 11.12.0 (which rejects that shape). Both keys are
120
+ # last-write-wins, matching mermaid's `decorateNode`, which
121
+ # plainly assigns `node.cssClasses = ...` rather than merging.
122
+ def apply_modifier(key, value)
123
+ unless @items.last
124
+ marker = key == :icon ? "::icon" : ":::"
125
+ raise Parser::ParseError, "#{marker} with no preceding item."
126
+ end
127
+
128
+ @items.last[:metadata][key] = value
129
+ end
130
+
131
+ # `classes` arrives here as an Array from a `:::a b` directive line
132
+ # (parse_metadata drops a scalar `classes:` key from `@{ ... }`
133
+ # metadata entirely - mermaid does not read one). `else` also covers a
134
+ # nil/absent value, normalized to an empty Array. The single point
135
+ # both add_column and add_card read from.
136
+ def normalize_classes(value)
137
+ case value
138
+ when Array then value
139
+ else value.to_s.strip.split(/\s+/)
140
+ end
141
+ end
142
+
143
+ # The card's own `id` and `text` beat a metadata entry of the same
144
+ # name, because mermaid ignores `@{ id: ... }` and `@{ text: ... }`.
145
+ # Merging the metadata FIRST makes that true by construction, with
146
+ # no exclusion list to keep in step as fields are added.
147
+ #
148
+ # A `label:` on a card is kept as its own attribute and drawn as a
149
+ # separate "Label:" row. That diverges from mermaid, which renders
150
+ # the label as the card's primary text and draws no such row. The
151
+ # divergence is pre-existing; correcting it belongs to the
152
+ # card-conformance bucket.
153
+ def add_card(item)
154
+ @current_column[:cards] << item[:metadata].merge(
155
+ id: item[:id],
156
+ text: item[:text],
157
+ classes: normalize_classes(item[:metadata][:classes]),
158
+ )
159
+ end
160
+
161
+ # A `label:` replaces a COLUMN's display text, which is what mermaid
162
+ # renders. A label mermaid would treat as unset is already gone by
163
+ # here - parse_metadata drops it - so falling back to the bracket
164
+ # text, or to the id, is a plain `||`.
165
+ def column_title(item)
166
+ item[:metadata][:label] || item[:text]
167
+ end
168
+
169
+ # `Kanban.metadata_fields` does the real work - a real YAML engine
170
+ # for structure and resolution - so this is left only to route each
171
+ # resolved key to the card/column field mermaid's `addNode` reads
172
+ # it into.
173
+ # Mermaid reads no `classes` key from metadata; only a `:::`
174
+ # modifier may set it. Every other resolved key is stored as-is.
175
+ def parse_metadata(metadata_data)
176
+ return {} if metadata_data.nil?
177
+
178
+ fields = Kanban.metadata_fields(metadata_data)
179
+ fields.each_with_object({}) do |(key, value), result|
180
+ next if key == "classes"
181
+
182
+ result[key.to_sym] = value
183
+ end
184
+ end
185
+
186
+ # The grammar captures the indent as a string, "" when absent.
187
+ def get_indent_size(indent_data)
188
+ indent_data.to_s.length
189
+ end
190
+ end
191
+
192
+ # The `@{ ... }` body, resolved by a real YAML engine
193
+ # (`MetadataYaml`) the way mermaid's js-yaml does: flow vs block is
194
+ # decided by whether the body contains a newline anywhere. Do not
195
+ # re-add hand-written grammar rules for YAML legality - duplicate
196
+ # keys and tab indentation are refused by the engine.
197
+ #
198
+ # @raise [Parser::ParseError] on YAML mermaid would also refuse
199
+ def self.metadata_fields(metadata_data)
200
+ document, unshield = shield_foreign_breaks(
201
+ metadata_document(metadata_data),
202
+ )
203
+ resolved = MetadataYaml.value(document)
204
+ raise Parser::ParseError, "Empty metadata." if resolved.nil?
205
+ return {} unless resolved.is_a?(Hash)
206
+
207
+ stored_fields(resolved)
208
+ .to_h { |key, text| [unshield.call(key), unshield.call(text)] }
209
+ end
210
+
211
+ # Mermaid's own wrapping: a single-line body becomes a flow
212
+ # mapping between braces, a multiline one is used as written -
213
+ # see Builders::Flowchart#metadata_document, the same rule.
214
+ def self.metadata_document(metadata_data)
215
+ text = metadata_body_text(metadata_data)
216
+ body = break_quoted_newlines(strip_metadata_comments(text))
217
+ body.include?("\n") ? "#{body}\n" : "{\n#{body}\n}"
218
+ end
219
+ private_class_method :metadata_document
220
+
221
+ # NEL, LS and PS are line breaks to Psych but ordinary characters
222
+ # to js-yaml, and `MetadataYaml` refuses them rather than read them
223
+ # the wrong way. Mermaid keeps them as text, so they are swapped for
224
+ # unused private-use characters while the engine reads the body, and
225
+ # swapped back in the stored fields.
226
+ #
227
+ # @return [Array(String, #call)] the shielded body and the function
228
+ # that restores a stored string
229
+ def self.shield_foreign_breaks(document)
230
+ present = FOREIGN_BREAKS.select { |char| document.include?(char) }
231
+ return [document, :itself.to_proc] if present.empty?
232
+
233
+ shields = present.zip(spare_characters(document, present.size)).to_h
234
+ [document.gsub(Regexp.union(present), shields), unshielder(shields)]
235
+ end
236
+ private_class_method :shield_foreign_breaks
237
+
238
+ def self.unshielder(shields)
239
+ restore = shields.invert
240
+ pattern = Regexp.union(restore.keys)
241
+ ->(text) { text.gsub(pattern, restore) }
242
+ end
243
+ private_class_method :unshielder
244
+
245
+ # A character is spare when the body neither contains it nor spells
246
+ # it as a `\xHH`, `\uHHHH` or `\UHHHHHHHH` escape - an escape
247
+ # would otherwise resolve to it and be swapped back by mistake.
248
+ def self.spare_characters(document, count)
249
+ taken = taken_code_points(document)
250
+ free = (0xE000..0xF8FF).lazy.reject { |code| taken.include?(code) }
251
+ spare = free.first(count).map { |code| code.chr(Encoding::UTF_8) }
252
+ return spare if spare.size == count
253
+
254
+ raise Parser::ParseError, "Unreadable line break."
255
+ end
256
+ private_class_method :spare_characters
257
+
258
+ def self.taken_code_points(document)
259
+ literal = document.scan(/[\ue000-\uf8ff]/).map(&:ord)
260
+ escaped = document.scan(YAML_CODE_ESCAPE).flatten.compact
261
+ literal + escaped.map { |hex| hex.to_i(16) }
262
+ end
263
+ private_class_method :taken_code_points
264
+
265
+ # A list or map is text only under the keys mermaid draws; `shape`
266
+ # refuses it (`validate_shape`) and any other key ignores it.
267
+ # `priority` is one of those: mermaid compares it by identity, so
268
+ # `[High]` is not the priority `High`. The one `walk` budget covers
269
+ # the whole body.
270
+ def self.stored_fields(resolved)
271
+ walk = { left: JS_LIST_ITEMS }
272
+ resolved.filter_map do |key, value|
273
+ next if dropped_by_mermaid?(value)
274
+
275
+ validate_shape(value) if key == "shape"
276
+ next if collection?(value) && !TEXT_KEYS.include?(key)
277
+
278
+ [key, js_text(value, walk)]
279
+ end.to_h
280
+ end
281
+ private_class_method :stored_fields
282
+
283
+ def self.collection?(value)
284
+ value.is_a?(Array) || value.is_a?(Hash)
285
+ end
286
+ private_class_method :collection?
287
+
288
+ def self.validate_shape(value)
289
+ unless value.is_a?(String)
290
+ raise Parser::ParseError,
291
+ "Unusable shape in metadata."
292
+ end
293
+ return if value == value.downcase && !value.include?("_")
294
+
295
+ raise Parser::ParseError,
296
+ "No such shape: #{value}. Shape names should be lowercase."
297
+ end
298
+ private_class_method :validate_shape
299
+
300
+ # A subclass grammar whose empty body captures as [] reads as "[]",
301
+ # which the YAML engine takes as a mapping key with no value:
302
+ # the same empty metadata as an empty string.
303
+ def self.metadata_body_text(metadata_data)
304
+ metadata_data[:body].to_s
305
+ end
306
+ private_class_method :metadata_body_text
307
+
308
+ # Mermaid strips comments before lexing, so metadata never sees
309
+ # them - `%%{` is a directive rather than a comment and stays.
310
+ # Uses JavaScript's whitespace set, matching Mermaid's comment pass.
311
+ def self.strip_metadata_comments(body)
312
+ body.gsub(/(?<=\n)#{JS_SPACE}*%%(?!\{)[^\n]+\n?/o, "")
313
+ end
314
+ private_class_method :strip_metadata_comments
315
+
316
+ # The kanban lexer rewrites line breaks inside double-quoted
317
+ # metadata before handing the body to js-yaml.
318
+ def self.break_quoted_newlines(body)
319
+ body.gsub(/"[^"]*"/) { |run| run.gsub(QUOTED_BREAK, "<br/>") }
320
+ end
321
+ private_class_method :break_quoted_newlines
322
+
323
+ JS_SPACE = '[\t\n\v\f\r \u00a0\u1680\u2000-\u200a' \
324
+ '\u2028\u2029\u202f\u205f\u3000\ufeff]'
325
+ QUOTED_BREAK = /\n#{JS_SPACE}*/
326
+ FOREIGN_BREAKS = ["\u0085", "\u2028", "\u2029"].freeze
327
+ YAML_CODE_ESCAPE = /\\(?:x(\h{2})|u(\h{4})|U(\h{8}))/
328
+ JS_LIST_ITEMS = 10_000
329
+ JS_LIST_DEPTH = 256
330
+ TEXT_KEYS = %w[assigned ticket icon label].freeze
331
+
332
+ private_constant :JS_SPACE, :QUOTED_BREAK, :FOREIGN_BREAKS,
333
+ :YAML_CODE_ESCAPE, :JS_LIST_ITEMS, :JS_LIST_DEPTH,
334
+ :TEXT_KEYS
335
+
336
+ # `null`, `false`, `0` and `""` are all falsy to mermaid, which
337
+ # skips the key rather than failing on it - the same table
338
+ # Builders::Flowchart#truthy? applies to its own resolved values.
339
+ def self.dropped_by_mermaid?(value)
340
+ case value
341
+ when nil, false, 0, "" then true
342
+ else value.is_a?(Float) && value.nan?
343
+ end
344
+ end
345
+ private_class_method :dropped_by_mermaid?
346
+
347
+ # Mermaid draws `.toString()` of what js-yaml resolved: numbers and
348
+ # booleans as JavaScript prints them (`0x10` is "16"), a list as its
349
+ # items joined by commas (`[one, two]` is "one,two"), a map as
350
+ # "[object Object]". An alias repeats a list by reference, so
351
+ # `JS_LIST_ITEMS` caps the items joined and `JS_LIST_DEPTH` the
352
+ # nesting, as `Source::Frontmatter` bounds its own walk; mmdc draws
353
+ # some bodies this refuses.
354
+ #
355
+ # @param walk [Hash] items still allowed to be joined, body-wide
356
+ # @param ancestors [Array<Array>] lists being joined; one holding
357
+ # itself joins as "" the way JavaScript's `join` does
358
+ def self.js_text(value, walk, ancestors = [])
359
+ case value
360
+ when Numeric then Etassia::JsNumber.stringify(value)
361
+ when Hash then "[object Object]"
362
+ when Array then js_join(value, walk, ancestors)
363
+ else value.to_s
364
+ end
365
+ end
366
+ private_class_method :js_text
367
+
368
+ def self.js_join(list, walk, ancestors)
369
+ return "" if ancestors.any? { |outer| outer.equal?(list) }
370
+
371
+ refuse_list("nested too deeply") if ancestors.size >= JS_LIST_DEPTH
372
+ list.map do |item|
373
+ walk[:left] -= 1
374
+ refuse_list("too large") if walk[:left].negative?
375
+
376
+ js_text(item, walk, ancestors + [list])
377
+ end.join(",")
378
+ end
379
+ private_class_method :js_join
380
+
381
+ def self.refuse_list(reason)
382
+ raise Parser::ParseError, "Metadata list #{reason}."
383
+ end
384
+ private_class_method :refuse_list
385
+
386
+ # Transform the lines array into columns and cards
387
+ rule(lines: subtree(:lines)) do
388
+ builder = BoardBuilder.new
389
+ lines_array = Array(lines)
390
+
391
+ lines_array.each do |line_data|
392
+ # An empty line never captures anything, so it contributes no
393
+ # entry at all to this array rather than a Hash lacking keys -
394
+ # this Hash check is the only filter empty lines need. It is
395
+ # NOT a stand-in for requiring `:id`: an unlabelled shaped item
396
+ # (`(text)`) is a Hash with no `:id` key and is a real item.
397
+ next unless line_data.is_a?(Hash)
398
+
399
+ builder.add_line(line_data)
400
+ end
401
+
402
+ builder.finalize
403
+
404
+ {
405
+ columns: builder.columns,
406
+ }
407
+ end
408
+ end
409
+ end
410
+ end
411
+ end
@@ -0,0 +1,271 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ module Etassia
6
+ module Parser
7
+ module Builders
8
+ # Transform for Mindmap diagrams
9
+ class Mindmap < Parslet::Transform
10
+ # Helper class to build mindmap tree from indented nodes
11
+ class TreeBuilder
12
+ # The first matching rule wins, in this order.
13
+ SHAPE_NAMES = %w[circle bang cloud hexagon square round].freeze
14
+ NODE_KEYS = %i[
15
+ content icon classes shape_circle shape_bang shape_cloud
16
+ shape_hexagon shape_square shape_round
17
+ ].freeze
18
+
19
+ attr_reader :root, :all_nodes
20
+
21
+ def initialize
22
+ @all_nodes = []
23
+ @root = nil
24
+ @level_stack = []
25
+ @pending_icon = nil
26
+ @pending_classes = []
27
+ @min_indent = nil
28
+ end
29
+
30
+ def add_node(node_data)
31
+ if node_data[:icon]
32
+ return apply_to_previous(:icon, node_data[:icon].to_s)
33
+ end
34
+ return apply_classes(node_data[:classes]) if node_data[:classes]
35
+
36
+ add_tree_node(node_data)
37
+ end
38
+
39
+ def node_data?(node_data)
40
+ NODE_KEYS.any? { |key| node_data[key] }
41
+ end
42
+
43
+ def apply_to_previous(attribute, value)
44
+ @all_nodes.last[attribute] = value if @all_nodes.last
45
+ end
46
+
47
+ def apply_classes(classes)
48
+ apply_to_previous(:classes, classes.to_s.split(/\s+/))
49
+ end
50
+
51
+ def add_tree_node(node_data)
52
+ indent_size = get_indent_size(node_data[:indent])
53
+ if @min_indent.nil? || indent_size < @min_indent
54
+ @min_indent = indent_size
55
+ end
56
+ level = calculate_level(node_data[:indent])
57
+ node = node_entry(node_data, level, indent_size)
58
+ @all_nodes << node
59
+ link_node(node, level)
60
+ end
61
+
62
+ def node_entry(node_data, level, indent_size)
63
+ {
64
+ id: "node-#{@all_nodes.size}",
65
+ content: extract_content(node_data), level: level,
66
+ shape: extract_shape(node_data), icon: nil, classes: [],
67
+ children: [], _indent_size: indent_size
68
+ }
69
+ end
70
+
71
+ def link_node(node, level)
72
+ if level.zero?
73
+ @root = node
74
+ @level_stack = [node]
75
+ else
76
+ attach_to_parent(node, find_parent(level))
77
+ @level_stack = @level_stack[0..(level - 1)] + [node]
78
+ end
79
+ end
80
+
81
+ def attach_to_parent(node, parent)
82
+ return unless parent
83
+
84
+ parent[:children] << node
85
+ node[:parent] = parent
86
+ end
87
+
88
+ def finalize
89
+ # Nothing to link if no real node was ever added.
90
+ return if @min_indent.nil?
91
+
92
+ # Rebuild hierarchy directly from each node's raw indentation,
93
+ # not a level number banded to a fixed 2- or 4-space step. The
94
+ # banded approach broke whenever one level's indent jumped by an
95
+ # amount the fixed band didn't expect (e.g. a first indent step
96
+ # of 4 columns from a zero-indent root): it produced a level
97
+ # with no entry below it in the stack, so the child was computed
98
+ # but never linked as anyone's child.
99
+ rebuild_hierarchy
100
+ end
101
+
102
+ # A `%%` comment line embedded inside a multi-line round-shape
103
+ # node's own content (e.g. "root(\n one\n %% hidden\n two\n)").
104
+ # Mermaid strips every such line, terminator included, before its
105
+ # node lexer ever runs, but only when the `%%` starts a REAL
106
+ # source line -- one already preceded by a newline -- and only
107
+ # when at least one character follows it; `root(%% keep this)`
108
+ # is one physical line (the `%%` sits after "root(", not at a
109
+ # line start) so mermaid renders it literally, and a bare "%%"
110
+ # with nothing after it is not a comment either. `^` matched at
111
+ # the start of the extracted content string too, which isn't a
112
+ # real line start unless something upstream already consumed a
113
+ # newline to get there; `(?<=\n)` requires an actual newline
114
+ # immediately before, without consuming it, so the string's own
115
+ # start is never mistaken for one. Mirrors
116
+ # Builders::Flowchart#strip_metadata_comments (flowchart.rb),
117
+ # the same rule for a node's `@{...}` metadata block -- including
118
+ # its indentation class: mermaid's own comment-strip regex uses
119
+ # JavaScript's `\s`, wider than ASCII space/tab (e.g. it matches a
120
+ # no-break space), so `[ \t]` alone left a comment unrecognised
121
+ # when it was indented with one.
122
+ ROUND_COMMENT_INDENT = '[\t\n\v\f\r \u00a0\u1680\u2000-\u200a' \
123
+ '\u2028\u2029\u202f\u205f\u3000\ufeff]'
124
+ ROUND_COMMENT_LINE = Regexp.new(
125
+ "(?<=\\n)#{ROUND_COMMENT_INDENT}*%%(?!\\{)" \
126
+ "[^\\r\\n]+\\r?\\n?",
127
+ )
128
+
129
+ private
130
+
131
+ def rebuild_hierarchy
132
+ @root = nil
133
+ # Stack of [indent_size, node] for the current ancestor chain:
134
+ # each new node pops every entry whose indent is >= its own
135
+ # (those are siblings or deeper nodes it isn't nested under),
136
+ # then attaches under whatever remains on top.
137
+ indent_stack = []
138
+ @all_nodes.each { |node| rebuild_node(node, indent_stack) }
139
+ end
140
+
141
+ def rebuild_node(node, indent_stack)
142
+ indent_size = prepare_for_rebuild(node)
143
+ discard_siblings(indent_stack, indent_size)
144
+ attach_rebuilt_node(node, indent_stack)
145
+ indent_stack << [indent_size, node]
146
+ end
147
+
148
+ def prepare_for_rebuild(node)
149
+ indent_size = node.delete(:_indent_size) || 0
150
+ node[:children] = []
151
+ node.delete(:parent)
152
+ indent_size
153
+ end
154
+
155
+ def discard_siblings(indent_stack, indent_size)
156
+ while indent_stack.any? && indent_stack.last[0] >= indent_size
157
+ indent_stack.pop
158
+ end
159
+ end
160
+
161
+ def attach_rebuilt_node(node, indent_stack)
162
+ return install_root(node) if indent_stack.empty?
163
+
164
+ parent = indent_stack.last[1]
165
+ node[:level] = parent[:level] + 1
166
+ attach_to_parent(node, parent)
167
+ end
168
+
169
+ # A second node with no ancestor is a second root. Mermaid rejects
170
+ # it rather than silently discarding the first root and its subtree.
171
+ def install_root(node)
172
+ if @root
173
+ raise Etassia::Parser::ParseError, "Multiple roots are illegal"
174
+ end
175
+
176
+ node[:level] = 0
177
+ @root = node
178
+ end
179
+
180
+ def get_indent_size(indent_data)
181
+ return 0 if indent_data.nil?
182
+ return 0 if indent_data.is_a?(Array) && indent_data.empty?
183
+
184
+ indent_string(indent_data).length
185
+ end
186
+
187
+ def calculate_level(indent_data)
188
+ return 0 if indent_data.nil?
189
+ return 0 if indent_data.is_a?(Array) && indent_data.empty?
190
+
191
+ indent_str = indent_string(indent_data)
192
+ return 0 if indent_str.empty?
193
+
194
+ spaces = indent_str.length
195
+ spaces.even? ? spaces / 2 : spaces / 4
196
+ end
197
+
198
+ def indent_string(indent_data)
199
+ separator = ""
200
+ return indent_data.join(separator) if indent_data.is_a?(Array)
201
+
202
+ indent_data.to_s
203
+ end
204
+
205
+ def extract_content(node_data)
206
+ return "" unless node_data[:content]
207
+
208
+ content = node_data[:content].to_s
209
+ return content unless node_data[:shape_round]
210
+
211
+ round_content(content, node_data[:round_quoted])
212
+ end
213
+
214
+ # Quoting protects the leading newline but not a real comment line:
215
+ # Mermaid strips those comments before quote lexing.
216
+ def round_content(content, quoted)
217
+ quoted ? strip_round_comments(content) : strip_round_extras(content)
218
+ end
219
+
220
+ def strip_round_comments(content)
221
+ content.gsub(ROUND_COMMENT_LINE, "")
222
+ end
223
+
224
+ def strip_round_extras(content)
225
+ # Drop embedded comment-only lines first (matching mermaid's own
226
+ # pre-lexer pass), then the one leading newline that a
227
+ # multi-line node's own opening "(\n" legitimately introduces --
228
+ # mermaid's rendered output drops exactly that line break and
229
+ # nothing else. `\r?` handles a caller that passes CRLF source
230
+ # straight to this parser without going through
231
+ # `Source.normalize` (Engine's real render path always does).
232
+ strip_round_comments(content).sub(/\A\r?\n/, "")
233
+ end
234
+
235
+ def extract_shape(node_data)
236
+ SHAPE_NAMES.find { |name| node_data[:"shape_#{name}"] } ||
237
+ "default"
238
+ end
239
+
240
+ def find_parent(level)
241
+ # Parent is the last node at level - 1; level 0 is the root and
242
+ # never asks.
243
+ @level_stack[level - 1]
244
+ end
245
+ end
246
+
247
+ # Transform the nodes array into a tree structure
248
+ rule(nodes: subtree(:nodes)) do
249
+ builder = TreeBuilder.new
250
+ nodes_array = Array(nodes)
251
+
252
+ nodes_array.each do |node_data|
253
+ next unless node_data.is_a?(Hash)
254
+
255
+ next unless builder.node_data?(node_data)
256
+
257
+ builder.add_node(node_data)
258
+ end
259
+
260
+ # Finalize to adjust levels and rebuild hierarchy
261
+ builder.finalize
262
+
263
+ {
264
+ root: builder.root,
265
+ nodes: builder.all_nodes,
266
+ }
267
+ end
268
+ end
269
+ end
270
+ end
271
+ end