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,940 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "common"
4
+
5
+ module Etassia
6
+ module Parser
7
+ module Grammars
8
+ # Parslet grammar for sequence diagrams.
9
+ #
10
+ # Handles all sequence diagram syntax including participants, messages,
11
+ # notes, activations, and control structures. The grammar properly handles
12
+ # complex arrow patterns with activation modifiers that cannot be parsed
13
+ # correctly by regex-based lexers.
14
+ class Sequence < Common
15
+ root(:diagram)
16
+
17
+ # Main diagram structure
18
+ rule(:diagram) do
19
+ ws? >>
20
+ header >>
21
+ ws? >>
22
+ statements.maybe >>
23
+ ws?
24
+ end
25
+
26
+ rule(:header) do
27
+ str("sequenceDiagram").as(:header) >> semicolon.maybe >> ws?
28
+ end
29
+
30
+ # A `;`-terminated statement can be followed by another bare `;`
31
+ # with nothing between them (`m;;m2`): the first `;` already ends
32
+ # the statement via `content_boundary`, and mermaid treats the
33
+ # second as an empty no-op statement rather than a syntax error
34
+ # (measured against mermaid 11.16.1: `Alice->>Bob: m;;Alice->>Bob:
35
+ # m2` gives 2 messages). Without this, `statements`' `repeat(1)`
36
+ # has nowhere to put that second `;` — no rule in `statement`
37
+ # starts on one — and the whole diagram fails to parse. Swallowing
38
+ # any run of extra `;`s after each statement mirrors that, the
39
+ # same way `ws?` already swallows blank space between statements.
40
+ rule(:statements) do
41
+ (statement >> ws? >> (semicolon >> ws?).repeat).repeat(1)
42
+ end
43
+
44
+ rule(:statement) do
45
+ comment_statement |
46
+ hash_comment_statement |
47
+ metadata_statement |
48
+ create_statement |
49
+ destroy_statement |
50
+ links_statement |
51
+ participant_declaration |
52
+ actor_declaration |
53
+ note_statement |
54
+ box_statement |
55
+ activation_command |
56
+ deactivation_command |
57
+ control_structure |
58
+ message
59
+ end
60
+
61
+ # A line starting with a bare `%` (not opening `%{`) is a whole-line
62
+ # comment to mermaid, contributing zero statements — mermaid's own
63
+ # comment rule takes priority over its actor rule at the start of a
64
+ # statement, so a leading `%` swallows the rest of the line whatever
65
+ # punctuation follows it. `message_actor_lead` already refuses to let
66
+ # a message actor START with a bare `%` (`mermaid_token_opener`,
67
+ # below), so it agrees such a line is never a message — but until
68
+ # this rule it had no statement to fall back to, so the whole
69
+ # diagram failed instead of skipping one line.
70
+ #
71
+ # No `.as(...)` in this rule. `root` is `diagram`, which always pairs
72
+ # `statements` with `header.as(:header)`, and Parslet drops an
73
+ # unnamed match when composing it alongside a named one rather than
74
+ # handing it up as an array entry — verified by parsing the real
75
+ # `diagram` root, not this rule in isolation (calling `statements`
76
+ # directly returns a bare `Parslet::Slice`, a code path the parser
77
+ # never actually takes): `g.parse("sequenceDiagram\n%c\nA->>B: m\n")`
78
+ # comes back `[{header: ...}, {from: "A", ...}]`, two entries, not
79
+ # three — the comment line leaves nothing behind for
80
+ # `Builders::Sequence`'s `is_a?(Hash)` guards in
81
+ # `apply`/`process_statements` to skip. Those guards are real and
82
+ # matter for other rules; they simply never see this one.
83
+ #
84
+ # A comment line does not swallow the statement after it
85
+ # (`%c\nA->>B: m\n` still yields participants `A`, `B`). A line
86
+ # starting `%%` also parses to zero participants, matching mermaid,
87
+ # but NOT through this rule — `diagram`'s own `ws?` right after
88
+ # `header` already consumes it via `common.rb`'s `comment` rule
89
+ # (`%%` to end of line) before `statements` ever gets a chance to
90
+ # try this one. Do not read that case as evidence for this rule.
91
+ rule(:comment_statement) do
92
+ str("%") >> str("{").absent? >>
93
+ (line_end.absent? >> any).repeat >> line_end
94
+ end
95
+
96
+ # `title`, `accTitle`, `accDescr` and `autonumber` are recognised so
97
+ # the diagram parses, and contribute no statement: like
98
+ # `comment_statement`, no `.as(...)`. The title, accessibility text
99
+ # and message numbering are not drawn. Each rule must end at
100
+ # `line_end` or `content_boundary` (which also accepts `;`), which
101
+ # keeps `autonumber->>B: m` and `title->>B: m` ordinary messages.
102
+ rule(:metadata_statement) do
103
+ title_statement | acc_title_statement | acc_descr_block |
104
+ acc_descr_statement | autonumber_statement
105
+ end
106
+
107
+ rule(:title_statement) do
108
+ str("title") >> ((colon >> space?) | space.repeat(1)) >>
109
+ match['^#;\n'].repeat >> content_boundary
110
+ end
111
+
112
+ rule(:acc_title_statement) do
113
+ str("accTitle") >> space? >> colon >> rest_of_line
114
+ end
115
+
116
+ rule(:acc_descr_statement) do
117
+ str("accDescr") >> space? >> colon >> rest_of_line
118
+ end
119
+
120
+ rule(:acc_descr_block) do
121
+ str("accDescr") >> space? >> lbrace >>
122
+ (rbrace.absent? >> any).repeat >> rbrace >> content_boundary
123
+ end
124
+
125
+ rule(:autonumber_statement) do
126
+ str("autonumber") >>
127
+ (space.repeat(1) >> (str("off") | numbering)).maybe >>
128
+ content_boundary
129
+ end
130
+
131
+ # Mermaid's NUM is digits with up to two decimals, or a leading-dot
132
+ # decimal.
133
+ rule(:number) do
134
+ (
135
+ match["0-9"].repeat(1) >>
136
+ (str(".") >> match["0-9"].repeat(1, 2)).maybe
137
+ ) |
138
+ (str(".") >> match["0-9"].repeat(1, 2))
139
+ end
140
+
141
+ rule(:numbering) do
142
+ number >> (space.repeat(1) >> number).maybe
143
+ end
144
+
145
+ # A `#` comment runs to the physical line end, `;` included —
146
+ # shared by every `content_boundary` caller below.
147
+ rule(:trailing_comment) do
148
+ (space? >> str("#") >> match['^\n'].repeat).maybe
149
+ end
150
+
151
+ # The boundary a `;`-aware text capture (`text_run` below) yields
152
+ # to: an optional trailing `#` comment, then a line end or an
153
+ # inline `;`. The bare `\r` + eof arm exists because `text_run`
154
+ # stops at every `\r`; only direct `Parser::Sequence#parse` calls
155
+ # reach it, since `Source#normalize` rewrites `\r\n?` first.
156
+ rule(:content_boundary) do
157
+ trailing_comment >> (line_end | semicolon | (str("\r") >> eof))
158
+ end
159
+
160
+ # A whole-line `#...` comment left behind once a label capture
161
+ # stops at an inline `;` (`alt -:<>,;# comment` splits into a
162
+ # label ending at the `;` and this bare comment line — only
163
+ # `alt`/`par` opening labels are `;`-aware; `loop`'s is not).
164
+ # Mirrors `comment_statement` above: no `.as(...)`, contributes
165
+ # zero statements. `char_ref.absent?` keeps a genuine character
166
+ # reference (`#9829;`) from ever being misread as the start of a
167
+ # comment line here.
168
+ rule(:hash_comment_statement) do
169
+ char_ref.absent? >> hash_char >>
170
+ (line_end.absent? >> any).repeat >> line_end
171
+ end
172
+
173
+ # mermaid encodes `/#\w+;/g` globally before its lexer ever runs
174
+ # (see the grammar's design doc for the full citation); this is
175
+ # the regex-equivalent grammar rule, needed wherever a `;`-aware
176
+ # boundary would otherwise split a character reference in half.
177
+ rule(:char_ref) do
178
+ hash_char >> match["a-zA-Z0-9_"].repeat(1) >> semicolon
179
+ end
180
+
181
+ # A `;`-aware text capture: a character reference is consumed
182
+ # whole, so its own `;` is never mistaken for a separator. Also
183
+ # stops at a bare `#`, leaving it for `content_boundary`'s
184
+ # trailing-comment consumption — a `#` opens a comment that runs to
185
+ # the physical line end, `;` included.
186
+ #
187
+ # `match['^;\n\r#']` is a bulk fast path: one native scan per run
188
+ # instead of one Parslet alternation per character. It keeps
189
+ # trailing spaces before the boundary inside the run rather than
190
+ # excluding them; every caller either strips via `extract_text` or
191
+ # never reads the label (`alt`/`par`), so that difference is moot.
192
+ rule(:text_run) do
193
+ (char_ref | match['^;\n\r#'].repeat(1)).repeat
194
+ end
195
+
196
+ # Runs to the physical line end: testing `line_end` at every
197
+ # character rescans an interior whitespace run each time.
198
+ rule(:rest_of_line) do
199
+ match['^\n'].repeat >> line_end
200
+ end
201
+
202
+ # An actor name is a bounded run of text, not a programming
203
+ # identifier: mermaid accepts spaces, parentheses, dashes, `=` and
204
+ # a leading digit (`Alice ()`, `Alice-in-Wonderland`, `1`).
205
+ #
206
+ # `actor_stop` names every boundary an actor name yields to. It
207
+ # includes `arrow_base` rather than `arrow` because the activation
208
+ # suffix is `.maybe`, so the two are equivalent as a boundary and
209
+ # `arrow_base` says what is meant. `%%` is required even though a
210
+ # bare `%` stays name material: `line_end` lets every statement
211
+ # carry a trailing comment, so an actor name has to yield to `%%`
212
+ # or `participant Alice %% who` would swallow the comment into the
213
+ # id. `alias_keyword` needs surrounding whitespace so it splits
214
+ # `participant 1 as text` at the keyword without eating a name like
215
+ # `Cast Away` (`t A` is not ` as `). `@{` (never a bare `@`) is the
216
+ # shape-metadata opener.
217
+ rule(:alias_keyword) { space.repeat(1) >> str("as") >> space.repeat(1) }
218
+
219
+ rule(:actor_stop) do
220
+ arrow_base | colon | comma | semicolon | str("%%") |
221
+ alias_keyword | str("@{") | newline
222
+ end
223
+
224
+ # `+` and `<` are never `actor_name` material (used by note/activate/
225
+ # deactivate references) — they would collide with the activation
226
+ # suffix (`A->>+B`) or an arrow spelling (`<<->>`). This does NOT
227
+ # generalise to a `participant`/`actor` DECLARATION: a declaration
228
+ # has no arrow to collide with, and mermaid accepts `participant
229
+ # A+B` outright (id "A+B") — see `declaration_stop` below, which
230
+ # bans `<>` but deliberately not `+`. Measured against mermaid
231
+ # 11.16.1's own parser directly; a stale version of this comment
232
+ # once claimed `participant A+B` was rejected too.
233
+ #
234
+ # A dash is consumed unless the very next thing is another dash or
235
+ # the start of a genuine arrow — that is the only real ambiguity a
236
+ # trailing dash creates (`A--->B` must not read as actor `A-` plus
237
+ # arrow `-->`). `newline` is deliberately absent from this set,
238
+ # unlike the general `actor_stop` gate above: a trailing dash at
239
+ # end-of-statement is exactly as legal as one at end-of-file (both
240
+ # already work — `participant A-` at EOF gives `"A-"`), and mmdc
241
+ # accepts `participant A-` followed by more statements the same
242
+ # way. The general gate still stops an ordinary character at a
243
+ # newline, so a name still cannot span multiple lines.
244
+ # `>` joins `<` as never `actor_name` material, matching the same
245
+ # fix applied to `message_actor_char` — it was missing here too.
246
+ # Measured against mermaid 11.16.1 directly: `activate A>B`,
247
+ # `deactivate A>B` and `Note over A>B: n` all raise on mermaid;
248
+ # base created actor `"A>B"` for all three, reading straight past
249
+ # the `>`. Round-2 Codex finding, in scope: same missing exclusion
250
+ # as the message-actor fix, just in the sibling rule that serves
251
+ # activation and note references instead of messages.
252
+ rule(:actor_char) do
253
+ actor_stop.absent? >>
254
+ (match["^+<>-"] |
255
+ (str("-") >>
256
+ (str("-") | arrow_base | colon | comma | semicolon |
257
+ str("%%") | alias_keyword | str("@{")).absent?))
258
+ end
259
+
260
+ # An arrow-tail character cannot open a name: `A->)B` is a bad
261
+ # arrow, not a message into an actor called `)B`. `/` joins the
262
+ # same set — it opens three reversed-arrow spellings (`/|-`,
263
+ # `/|--`, `//-`, `//--`), so `A->>/B` is a bad arrow rather than a
264
+ # message to `/B`; measured, mermaid rejects it there while
265
+ # accepting `/` elsewhere in a name.
266
+ rule(:actor_lead) { match[")|>/"].absent? >> actor_char }
267
+
268
+ # A character reference is one unit, so its own `;` does not end
269
+ # the name (`Note over A#65;: n`, `activate A#65;`).
270
+ rule(:actor_name) do
271
+ (char_ref | actor_lead) >> (char_ref | actor_char).repeat
272
+ end
273
+
274
+ # `actor_name` above is shared by every OTHER actor reference
275
+ # (notes, activate/deactivate) and stays untouched. Declarations
276
+ # and message endpoints get their own rules below: mermaid's real
277
+ # grammar treats the two very differently, and forcing one token
278
+ # to cover both is what created phantom actors and rejected valid
279
+ # input in three places at once (measured against mermaid
280
+ # 11.16.1's own parser and lexer directly).
281
+ #
282
+ # A declaration has no arrow next to it, so there is nothing for
283
+ # a `+`, a `-` run, or a leading `/` to be ambiguous with —
284
+ # `actor_char`'s bans on those exist only to protect a message's
285
+ # arrow and activation-suffix syntax. Measured: `participant
286
+ # A+B`, `participant /B`, `participant )B`, `participant |B` and
287
+ # `participant A--B` all succeed with those literal ids; only `<`
288
+ # and `>` are ever rejected.
289
+ # Bare `@` is banned outright, not just the `@{` shape-metadata
290
+ # opener — measured against mermaid 11.16.1: `participant A@B`
291
+ # raises `Expecting 'ACTOR', got 'INVALID'`. Banning only `@{`
292
+ # left a bare `@` as ordinary id material, which mermaid never
293
+ # accepts (`shape_metadata` below still matches `@{...}` right
294
+ # after this stop point when metadata is actually present).
295
+ # `#` is NOT here: mermaid only rejects a LEADING `#`
296
+ # (`declaration_lead`/`declaration_word_lead` below), and lets it
297
+ # through everywhere else in an id — measured against mermaid
298
+ # 11.16.1, `participant A#B` gives id "A#B".
299
+ rule(:declaration_stop) do
300
+ match["<>"] | colon | comma | semicolon | str("%%") |
301
+ str("@") | newline
302
+ end
303
+
304
+ rule(:declaration_char) { declaration_stop.absent? >> any }
305
+
306
+ # The first character of an id is the only place `#` is ever
307
+ # banned — measured against mermaid 11.16.1: `participant #B`
308
+ # raises, `participant A#B` does not. `char_ref` is tried first so
309
+ # a leading `#9829;` reference is consumed whole rather than
310
+ # rejected by the ban.
311
+ # Shared by `declaration_lead` and `declaration_word_lead` below —
312
+ # both are "char_ref, else a plain character not banned by a
313
+ # leading `#`", differing only in which character rule bounds the
314
+ # plain branch. A private method, not `rule()`: Parslet's `rule()`
315
+ # macro memoizes a fixed zero-argument atom and can't take one.
316
+ def hash_free_lead(char_rule)
317
+ char_ref | (hash_char.absent? >> char_rule)
318
+ end
319
+ private :hash_free_lead
320
+
321
+ rule(:declaration_lead) { hash_free_lead(declaration_char) }
322
+
323
+ # The `as`-alias split only fires when the id BEFORE it has no
324
+ # embedded whitespace — mermaid's own ID-then-AS lexer rule
325
+ # (`[^<>:\n,;@\s]+(?=\s+as\s)`) excludes whitespace from that id
326
+ # entirely. When the id DOES contain whitespace, `as` is not a
327
+ # keyword boundary and the whole thing — "as" included — stays one
328
+ # id. Measured against mermaid 11.16.1 directly:
329
+ # `participant A as C` -> id "A", label "C" (alias applies)
330
+ # `participant A B as C` -> id "A B as C", no label at all
331
+ # `declaration_word_char` is deliberately narrower than
332
+ # `declaration_char` (it also excludes whitespace) so the
333
+ # lookahead below only fires on a whitespace-free run, exactly
334
+ # matching that lexer rule.
335
+ rule(:declaration_word_char) do
336
+ (declaration_stop | space).absent? >> any
337
+ end
338
+
339
+ rule(:declaration_word_lead) { hash_free_lead(declaration_word_char) }
340
+
341
+ # Each branch's lead unit bans a leading bare `#`; the repeated
342
+ # tail does not, so a `#` past the first character stays ordinary
343
+ # id material. Both also gain a `char_ref` alternative ahead of
344
+ # their plain character rule, so a `#9829;` run is consumed as one
345
+ # atomic unit. Without it in the alias branch specifically,
346
+ # `#9829;B as Heart` falls through to the fallback branch and
347
+ # swallows the whole thing as an id with no label.
348
+ rule(:declaration_name) do
349
+ (declaration_word_lead >> (char_ref | declaration_word_char).repeat >>
350
+ alias_keyword.present?) |
351
+ (declaration_lead >> (char_ref | declaration_char).repeat)
352
+ end
353
+
354
+ # A message endpoint sits next to an arrow, so it keeps
355
+ # `actor_char`'s arrow-safety, but two of its restrictions are
356
+ # wrong for a message specifically:
357
+ #
358
+ # - `alias_keyword` must NOT stop a message name. Measured: `A as
359
+ # Z->>B: m` and `A->>B as Z: m` both parse in mermaid with `as`
360
+ # as ordinary text — ` as ` only splits id from label inside a
361
+ # `participant`/`actor` statement, never inside a message.
362
+ # - `(` and `)` must stop a message name instead of joining it.
363
+ # Unlike a declaration's literal `Alice ()`, a `()` touching a
364
+ # message endpoint is mermaid's own central-connection
365
+ # decoration (jison terminal `()`, `LINETYPE.CENTRAL_CONNECTION`
366
+ # /`_REVERSE`/`_DUAL`) and never becomes part of the actor's
367
+ # identity: `A ()->>B: m` and `A->>() B: m` both resolve to the
368
+ # two declared actors `A`/`B`, not to phantom actors `A ()` /
369
+ # `() B`.
370
+ # `#` is NOT here, matching `declaration_stop`: mermaid only
371
+ # rejects a LEADING `#` on a message endpoint, not one mid-name —
372
+ # measured, `A->>B#C: m` gives recipient "B#C". The leading ban
373
+ # lives in `message_actor_lead_char` below instead, so it applies
374
+ # only to the first character, not to every character that
375
+ # follows this rule via `message_actor_char`.
376
+ rule(:message_actor_stop) do
377
+ arrow_base | colon | comma | semicolon | str("%%") |
378
+ str("@{") | lparen | rparen | newline
379
+ end
380
+
381
+ # FOUR rounds of patching this rule each found a new gap, and the
382
+ # fourth (a differential fuzz against mermaid, 10,568 generated
383
+ # inputs, all 1,939 etassia-accepted ones cross-checked in Chrome
384
+ # — this grammar's own hand-written approximations had exhausted
385
+ # every gate we already had) found two more, both the SAME shape
386
+ # as the first three: a HAND-DERIVED stand-in for a piece of
387
+ # mermaid's real regex, each stand-in accurate on the cases it
388
+ # was checked against and wrong on one it wasn't. The fix each
389
+ # time was to stop deriving and start copying:
390
+ #
391
+ # - The regex has TWO SEPARATE things where this rule had
392
+ # conflated them into one per-character check: an ENTRY
393
+ # lookahead, evaluated ONCE at the position of the triggering
394
+ # dash, and a TAIL character class, evaluated per character
395
+ # while consuming the run that follows. Round 3's version
396
+ # re-ran a stop-check (including `arrow_base` and etassia's own
397
+ # `%%`/`@{` extensions) at EVERY tail character, which is not
398
+ # what the real regex does — its tail class
399
+ # (`[^\+<\->\->:\n,;]`) is a flat set with no arrow-awareness
400
+ # at all. Measured: `B->>A-(%%C: m` — mermaid consumes the
401
+ # embedded `%%` as ordinary tail material and sends message
402
+ # "m"; the per-character recheck stopped there instead,
403
+ # producing recipient "A-(" and an EMPTY message. Same root
404
+ # cause exposed `A-(//-B: m`: mermaid reads the whole
405
+ # "A-(//-B" as one actor and rejects the missing arrow; the
406
+ # recheck matched `arrow_base`'s reversed-arrow spelling
407
+ # mid-tail and stopped there instead, letting `->>` parse as a
408
+ # real arrow it should never have reached.
409
+ # `message_actor_continuation_tail_char` below is now the
410
+ # regex's OWN flat class, ported directly rather than built
411
+ # from etassia's existing rule references.
412
+ # - The entry lookahead itself was still incomplete: `arrow_base`
413
+ # (independently verified, unchanged across all four rounds)
414
+ # covers every COMPLETE arrow spelling, but mermaid's lookahead
415
+ # ALSO independently bans a bare `-/` and a bare `-\` even when
416
+ # nothing arrow-shaped ever follows — measured directly against
417
+ # the compiled lookahead: `"A-/ZZZ"` and `"A-\ZZZ"` both stop
418
+ # at `"A"`, same as `"A--ZZZ"`, while `"A-fooZZZ"`,
419
+ # `"A-(ZZZ"` and `"A-%ZZZ"` all continue past it. No
420
+ # `arrow_base` alternative is exactly "-/" or "-\" — every
421
+ # slash/backslash spelling in `reversed_arrow` needs a THIRD
422
+ # character etassia already covers, or a fourth like `-//`
423
+ # which `solid_arrow` already has. `-/` and `-\` alone were
424
+ # simply missing, so `B->>A-/(): m` and `B->>A-\(): m` fused
425
+ # the guard character into the identity instead of refusing.
426
+ # `message_actor_continuation_entry_stop` below adds exactly
427
+ # those two, verified empirically against every candidate
428
+ # prefix the compiled lookahead actually protects, not
429
+ # re-derived from the regex source text a second time (that is
430
+ # how `-/` was missed the first time — hand-reading nested
431
+ # backslash escapes in a 200-character alternation).
432
+ rule(:message_actor_continuation_entry_stop) do
433
+ arrow_base | str("--") | str("-/") | str("-\\")
434
+ end
435
+
436
+ # The regex's own tail class, `[^\+<\->\->:\n,;]`, transliterated
437
+ # directly: excludes only `+ < - > : \n , ;`. No arrow-awareness,
438
+ # no `%%`/`@{` — those are etassia's OWN extensions for comments
439
+ # and shape metadata, real everywhere ELSE in this grammar, but
440
+ # never part of mermaid's ACTOR token and specifically NOT part
441
+ # of an already-open fusion tail (finding 1's `%%` case above).
442
+ # `\r` sits alongside `\n` because etassia's own `newline` rule
443
+ # (used elsewhere) accepts CRLF; excluding both characters
444
+ # individually gives the same boundary a rule-based check would,
445
+ # without needing a rule reference inside a character class.
446
+ rule(:message_actor_continuation_tail_char) do
447
+ match["^+<>:\n\r,;-"]
448
+ end
449
+
450
+ rule(:message_actor_continuation) do
451
+ message_actor_continuation_entry_stop.absent? >> str("-") >>
452
+ message_actor_continuation_tail_char.repeat(1)
453
+ end
454
+
455
+ # CORRECTED (round 5): the EOF case this comment used to justify
456
+ # keeping this branch no longer reaches a successful parse at all —
457
+ # `A->>B-` at true end of file now raises (spec: "rejects a
458
+ # trailing dash recipient at true end of file"), because THIS diff
459
+ # makes `message_text` mandatory. `message_actor_stop` never
460
+ # matches at true EOF, so the branch still consumes the trailing
461
+ # dash, but nothing is left to satisfy the now-required `: text`.
462
+ # Whether that makes the branch fully unreachable was not
463
+ # re-derived here — removing it is a separate, unverified change
464
+ # from the four rules this diff scopes; left in place rather than
465
+ # deleted on an unverified claim.
466
+ # A dash followed by `/` or `\` ends the name, as it does for a
467
+ # continuation: `A->>B-/C: m` and `A->>B-\C: m` are rejected.
468
+ rule(:message_actor_char) do
469
+ message_actor_stop.absent? >>
470
+ (match["^+<>()-"] |
471
+ (str("-") >>
472
+ (str("-") | str("/") | str("\\") | message_actor_stop).absent?))
473
+ end
474
+
475
+ # `>` joins `<` as never message-actor material, in any position —
476
+ # not just leading. Measured: `A->>B>C: m` raises on mermaid
477
+ # (`got 'INVALID'`); base only excluded `>` from the immediate
478
+ # arrow-tail set below, not from `message_actor_char`, so it kept
479
+ # reading past an interior `>` and merged `B>C` into one actor.
480
+ #
481
+ # `\` joins the arrow-tail set for the same reason `/` is already
482
+ # there: a name cannot OPEN with it, because it also opens the
483
+ # reversed-arrow spellings (`\|-`, `\|--`, `\\-`, `\\--`).
484
+ # Measured: `A->>\B: m` raises on mermaid (`got 'INVALID'`); base
485
+ # accepted it and rendered actor `"\\B"`.
486
+ #
487
+ # `|` does NOT join this set: no arrow spelling starts with a bare
488
+ # `|` (the pipe-bearing spellings — `-|/`, `-|\`, `--|/`, `--|\` —
489
+ # all need a leading dash first, which `message_actor_lead` already
490
+ # never consumes). Measured against mermaid 11.16.1: `A-->|: m` and
491
+ # `A-x|: m` both give recipient `"|"` verbatim; a leading pipe is
492
+ # ordinary actor-name material, not a collision to protect against.
493
+ #
494
+ # The LEAD-only character rule for point 1 above: the plain
495
+ # character class only, deliberately WITHOUT `message_actor_char`'s
496
+ # dash branch, so a message actor name can never open with `-` —
497
+ # neither `-foo`, `-B`, `-()`, nor `- ()`. `hash_char.absent?` bans
498
+ # a LEADING bare `#` only — `message_actor_stop` no longer does,
499
+ # so a `#` past the first character stays ordinary name material
500
+ # (measured, `A->>B#C: m` gives recipient "B#C").
501
+ rule(:message_actor_lead_char) do
502
+ message_actor_stop.absent? >> hash_char.absent? >> match["^+<>()-"]
503
+ end
504
+
505
+ # `char_ref` is tried first so a name can OPEN with a character
506
+ # reference (`links #9829;B: {}`) — the reference is consumed
507
+ # whole, before `message_actor_lead_char`'s `#` ban would otherwise
508
+ # stop the plain branch at its very first character.
509
+ rule(:message_actor_lead) do
510
+ char_ref |
511
+ (match[")>/\\\\"].absent? >> mermaid_token_opener.absent? >>
512
+ message_actor_lead_char)
513
+ end
514
+
515
+ # Where mermaid's lexer starts a token, two of its rules run before
516
+ # its actor rule and take the text instead, so a message endpoint
517
+ # can never start with either:
518
+ #
519
+ # - `%` not opening `%{`: the rest of the line is a comment.
520
+ # `A->>%B: m` is rejected; `A->>%{x: m` is not.
521
+ # - a number followed by a space or newline: it is read as a
522
+ # number, not a name. `A->>8 : m` and `1 ->> 2: m` are rejected;
523
+ # `A->>8: m` and `A->>1.555 : m` (three decimals) are not.
524
+ #
525
+ # A `%%` right after an endpoint's first character needs no rule
526
+ # here: `%%` already ends the name, and the comment it opens leaves
527
+ # the message without its required `: text`.
528
+ rule(:mermaid_token_opener) do
529
+ (str("%") >> str("{").absent?) |
530
+ (lexed_number >> match[" \n"])
531
+ end
532
+
533
+ rule(:lexed_number) do
534
+ (match["0-9"].repeat(1) >>
535
+ (str(".") >> match["0-9"].repeat(1, 2)).maybe) |
536
+ (str(".") >> match["0-9"].repeat(1, 2))
537
+ end
538
+
539
+ # `message_actor_continuation` is tried only in the repeat below —
540
+ # never as part of `message_actor_lead` above — which combined
541
+ # with the lead using `message_actor_lead_char` (not
542
+ # `message_actor_char`) is what makes an empty prefix before a
543
+ # dash-fusion unreachable from either direction. Tried before the
544
+ # plain `message_actor_char` branch since it is the more specific
545
+ # case and Parslet alternation is first-match.
546
+ rule(:message_actor_name) do
547
+ message_actor_lead >> message_actor_run.repeat >>
548
+ message_actor_slash_fusion.maybe
549
+ end
550
+
551
+ rule(:message_actor_run) do
552
+ char_ref | message_actor_continuation |
553
+ (match["/\\\\"].absent? >> message_actor_char)
554
+ end
555
+
556
+ # Mermaid tests for a `/` or `\` arrow (`\|-`, `\\-`, ...) only
557
+ # where its name token ends or begins a run, which for these
558
+ # characters means the FIRST one in the name. A first one that
559
+ # opens no arrow joins the name and its flat tail, so a later
560
+ # spelling is name text: `Alice \\|- John: m` has no arrow and is
561
+ # rejected (oracle: sequencediagram_spec-97601112).
562
+ rule(:message_actor_slash_fusion) do
563
+ reversed_arrow.absent? >> match["/\\\\"] >>
564
+ (char_ref | message_actor_continuation |
565
+ (message_actor_fused_stop.absent? >> match["^+<>()-"])).repeat
566
+ end
567
+
568
+ rule(:message_actor_fused_stop) do
569
+ colon | comma | semicolon | str("%%") | str("@{") | lparen |
570
+ rparen | newline
571
+ end
572
+
573
+ # The decoration is parsed and discarded, not modeled — Etassia
574
+ # does not draw the central-connection marker, so only the
575
+ # phantom-actor bug needs closing here. No embedded whitespace
576
+ # rule of its own: the message rule wraps it in `space?` on both
577
+ # sides, matching jison's `()` token lexing in the
578
+ # whitespace-skipping INITIAL state (`A()->>B` and `A ()->>B`
579
+ # are both legal).
580
+ rule(:central_connection) { str("()") }
581
+
582
+ # Shape metadata (`@{"type":"boundary"}`) is captured whole as
583
+ # `:shape_metadata`; the builder reads only its `type`. It is not
584
+ # validated as JSON — a nested `{...}` on one line still ends
585
+ # the payload at its first `}` (no corpus case has one). Bounded
586
+ # to one line — `newline.absent?` is load-bearing, not tidiness:
587
+ # an unclosed `@{` would otherwise scan into a later statement's
588
+ # closing brace and silently discard everything between, rather
589
+ # than raising. `repeat(1)`, not `repeat`: mermaid rejects an
590
+ # empty `@{}`, so the payload must not be empty either.
591
+ rule(:shape_metadata) do
592
+ str("@{") >>
593
+ (str("}").absent? >> newline.absent? >> any).repeat(1) >>
594
+ str("}")
595
+ end
596
+
597
+ # Participant declarations. `keyword` and `id`/`label` are shared
598
+ # by the plain declarations below and by `create_statement`, so
599
+ # `create participant Carl` inherits `declaration_name`'s char-ref
600
+ # fix for free the same way a bare `participant Carl` does — a
601
+ # private method, not `rule()`, since only the TERMINATOR differs
602
+ # between the two callers (see `create_statement`'s comment).
603
+ def declaration_body(keyword)
604
+ declaration_keyword(keyword) >> declaration_identity >>
605
+ declaration_label.maybe
606
+ end
607
+
608
+ def declaration_keyword(keyword)
609
+ str(keyword) >> space.repeat(1)
610
+ end
611
+
612
+ def declaration_identity
613
+ declaration_name.as(:id) >>
614
+ shape_metadata.as(:shape_metadata).maybe >> space?
615
+ end
616
+
617
+ def declaration_label
618
+ str("as") >> space.repeat(1) >> label.as(:label)
619
+ end
620
+ private :declaration_body, :declaration_keyword,
621
+ :declaration_identity, :declaration_label
622
+
623
+ # Ends at `content_boundary`, not `line_end` — a declaration can be
624
+ # followed by another statement on the same line after a `;`, and
625
+ # `line_end` only accepts one right before a newline. Measured
626
+ # against mermaid 11.16.1: `participant A;participant B;A->>B: m`
627
+ # gives 2 participants, 1 message; `line_end` alone rejected it
628
+ # outright, since it has nowhere to put the text still left after
629
+ # its own leading `;`.
630
+ rule(:participant_declaration) do
631
+ declaration_body("participant") >> content_boundary.as(:participant)
632
+ end
633
+
634
+ rule(:actor_declaration) do
635
+ declaration_body("actor") >> content_boundary.as(:actor)
636
+ end
637
+
638
+ # `.as(:create)` is required: the builder's lifecycle check (see
639
+ # `Builders::Sequence`) branches on this key to register the
640
+ # pending create; without it a `create` is silently unchecked.
641
+ # Same `content_boundary` terminator as the two plain declarations
642
+ # above, for the same inline-`;` reason. Measured against mermaid
643
+ # 11.16.1: `create participant B;A->>B: m` gives 2 participants,
644
+ # 1 message.
645
+ rule(:create_statement) do
646
+ str("create").as(:create) >> space.repeat(1) >>
647
+ (
648
+ (declaration_body("participant") >>
649
+ content_boundary.as(:participant)) |
650
+ (declaration_body("actor") >> content_boundary.as(:actor))
651
+ )
652
+ end
653
+
654
+ # Draws no destroy marker (etassia's existing "not modeled" stance
655
+ # on decorative constructs) — `.as(:destroy)` still captures the
656
+ # target id because the lifecycle check needs it to register the
657
+ # pending destroy against the very next message.
658
+ #
659
+ # Ends at `content_boundary` for the same inline-`;` reason as
660
+ # `create_statement` above: `destroy B;A->>B: m` gives 2
661
+ # participants, 1 message on mermaid 11.16.1; `line_end` alone
662
+ # rejected it.
663
+ rule(:destroy_statement) do
664
+ str("destroy") >> space.repeat(1) >>
665
+ declaration_name.as(:destroy) >> content_boundary
666
+ end
667
+
668
+ # Target uses `message_actor_name`, not `declaration_name` like
669
+ # `participant`/`actor`/`destroy` — don't "fix" that; its bans on
670
+ # `()`/`#`/leading dash come from `message_actor_stop`/
671
+ # `message_actor_lead_char`. `.as(:links)` looks unread but isn't:
672
+ # the builder uses it to call `ensure_participant`. Payload is
673
+ # unvalidated JSON, like `shape_metadata`, and stops at
674
+ # `content_boundary` (not `rest_of_line`) so an inline `;` always
675
+ # yields the following message instead of swallowing it.
676
+ rule(:links_statement) do
677
+ str("links") >> space.repeat(1) >>
678
+ message_actor_name.as(:links) >> colon >> text_run >>
679
+ content_boundary
680
+ end
681
+
682
+ # Messages with arrows (order matters: longest patterns first)
683
+ rule(:message) do
684
+ message_actor_name.as(:from) >> space? >>
685
+ message_signal.as(:arrow) >> space? >>
686
+ message_actor_name.as(:to) >> space? >>
687
+ message_text.as(:text) >>
688
+ content_boundary
689
+ end
690
+
691
+ # A central-connection decoration and an activation suffix never
692
+ # coexist on the same message — measured against mermaid 11.16.1
693
+ # directly: `A->>+()B`, `A->>()+B`, `A()->>+B`, `A<<->>+()B` and
694
+ # `A<<->>()+B` all raise ("Expecting 'ACTOR'"/"'+'"/"'-'"),
695
+ # whichever side carries the decoration and whichever carries the
696
+ # suffix. `A-()->>+B` only looks like a counterexample: there the
697
+ # `()` is fused into the FROM actor's own identity by
698
+ # `message_actor_paren_unit` above, so no real central connection
699
+ # reaches this rule at all, and the plain third branch below
700
+ # applies instead.
701
+ #
702
+ # A branch with `()` present therefore uses `arrow_base` alone —
703
+ # no `activation_suffix` component exists to try. Parslet
704
+ # alternation is first-match, so the connection branches are tried
705
+ # before the plain one; `central_connection` stays uncaptured
706
+ # here, same as before this rule existed — it is parsed and
707
+ # discarded either way.
708
+ rule(:message_signal) do
709
+ (central_connection >> space? >> arrow_base.as(:arrow_base) >>
710
+ space? >> central_connection.maybe) |
711
+ (arrow_base.as(:arrow_base) >> space? >> central_connection) |
712
+ arrow
713
+ end
714
+
715
+ # Arrow types including activation modifiers
716
+ # Critical: These must be tried in order from longest to shortest
717
+ rule(:arrow) do
718
+ arrow_base.as(:arrow_base) >> activation_suffix.maybe.as(:activation)
719
+ end
720
+
721
+ # Every spelling mmdc 11.12.0 renders, and only those. Half and
722
+ # stick heads come in a reversed spelling too, which puts the marker
723
+ # on the source end: `A//-B` and `A-//B` draw the same head at
724
+ # opposite ends of the line.
725
+ #
726
+ # `->|` and `-->|` are NOT arrows. mmdc reads `A->|B` as `->` into
727
+ # an actor named `|B`, so treating the pipe as part of the arrow
728
+ # named the wrong participant.
729
+ #
730
+ # Parslet alternation is first-match, so a genuine prefix pair has
731
+ # to be listed longest-first: `-->` before `-->>` would swallow it,
732
+ # and so would `//-` before `//--`. Nothing else here shadows —
733
+ # no solid arrow's second character is a dash, so the families are
734
+ # order-independent between themselves.
735
+ rule(:arrow_base) do
736
+ str("<<-->>") | str("<<->>") |
737
+ dotted_arrow | solid_arrow | reversed_arrow
738
+ end
739
+
740
+ rule(:dotted_arrow) do
741
+ str("-->>") | str("--|/") | str("--|\\") | str("--//") |
742
+ str("--\\\\") | str("--x") | str("--X") | str("--)") | str("-->")
743
+ end
744
+
745
+ rule(:solid_arrow) do
746
+ str("->>") | str("-|/") | str("-|\\") | str("-//") |
747
+ str("-\\\\") | str("-x") | str("-X") | str("-)") | str("->")
748
+ end
749
+
750
+ rule(:reversed_arrow) do
751
+ str("/|--") | str("/|-") | str('\\|--') | str('\\|-') |
752
+ str("//--") | str("//-") | str('\\\\--') | str('\\\\-')
753
+ end
754
+
755
+ rule(:activation_suffix) { space? >> match["+-"] }
756
+
757
+ rule(:message_text) do
758
+ colon >> space? >>
759
+ text_run.as(:message_text)
760
+ end
761
+
762
+ # Notes
763
+ rule(:note_statement) do
764
+ (str("note") | str("Note")) >> space.repeat(1) >>
765
+ note_position.as(:position) >> space.repeat(1) >>
766
+ note_participants.as(:participants) >> space? >>
767
+ colon >> space? >>
768
+ text_run.as(:note_text) >>
769
+ content_boundary
770
+ end
771
+
772
+ rule(:note_position) do
773
+ (str("left") >> space.repeat(1) >> str("of")).as(:left_of) |
774
+ (str("right") >> space.repeat(1) >> str("of")).as(:right_of) |
775
+ str("over").as(:over)
776
+ end
777
+
778
+ rule(:note_participants) do
779
+ actor_name.as(:participant) >>
780
+ (space? >> comma >> space? >>
781
+ actor_name.as(:participant)).repeat
782
+ end
783
+
784
+ # Activation/Deactivation commands. Widened to `actor_name` for
785
+ # construct completeness: an actor name is one construct, and
786
+ # leaving these two sites on `identifier` would make
787
+ # `participant 1` parse while `activate 1` failed on the very same
788
+ # actor.
789
+ rule(:activation_command) do
790
+ str("activate") >> space.repeat(1) >>
791
+ actor_name.as(:activate) >>
792
+ line_end
793
+ end
794
+
795
+ rule(:deactivation_command) do
796
+ str("deactivate") >> space.repeat(1) >>
797
+ actor_name.as(:deactivate) >>
798
+ line_end
799
+ end
800
+
801
+ # Box grouping
802
+ rule(:box_statement) do
803
+ str("box") >> space.repeat(1) >>
804
+ text_run.as(:box_label) >> content_boundary >>
805
+ ws? >>
806
+ statements.as(:box_statements) >>
807
+ ws? >>
808
+ str("end") >> line_end
809
+ end
810
+
811
+ # Control structures
812
+ rule(:control_structure) do
813
+ loop_structure |
814
+ alt_structure |
815
+ opt_structure |
816
+ rect_structure |
817
+ par_structure |
818
+ critical_structure |
819
+ break_structure
820
+ end
821
+
822
+ rule(:loop_structure) do
823
+ str("loop") >> space? >>
824
+ text_run.as(:loop_label) >> content_boundary >>
825
+ ws? >>
826
+ statements.as(:loop_statements) >>
827
+ ws? >>
828
+ str("end") >> line_end
829
+ end
830
+
831
+ # Every block-opening/continuation label (this one through
832
+ # `break_label`) must use `text_run`, never its own end-of-line
833
+ # scan: mermaid's real lexer tokenizes all of them — `loop`, `alt`,
834
+ # `else`, `opt`, `par`, `and`, `critical`, `option`, `break`, `box`
835
+ # — through one shared state whose label rule stops at `;`. `rect`
836
+ # is a separate rule, see `rect_structure`.
837
+ rule(:alt_structure) do
838
+ str("alt") >> space? >>
839
+ text_run.as(:alt_label) >> content_boundary >>
840
+ ws? >>
841
+ statements.as(:alt_statements) >>
842
+ ws? >>
843
+ (
844
+ str("else") >> space? >>
845
+ text_run.as(:else_label) >> content_boundary >>
846
+ ws? >>
847
+ statements.as(:else_statements) >>
848
+ ws?
849
+ ).repeat.as(:else_blocks) >>
850
+ str("end") >> line_end
851
+ end
852
+
853
+ # `match['a-zA-Z0-9_'].absent?` after the literal `opt` is a real
854
+ # word-boundary guard, not just a bare literal: without it, this
855
+ # rule matches `opt` as a PREFIX of `option` (case 041's
856
+ # `option Network timeout` line inside a `critical` block was
857
+ # misread as a nested `opt` with label `"ion Network timeout"`,
858
+ # consuming the real closing `end` and breaking the whole parse).
859
+ # Verified mermaid also rejects a glued `optFoo`.
860
+ rule(:opt_structure) do
861
+ str("opt") >> match["a-zA-Z0-9_"].absent? >> space? >>
862
+ text_run.as(:opt_label) >> content_boundary >>
863
+ ws? >>
864
+ statements.as(:opt_statements) >>
865
+ ws? >>
866
+ str("end") >> line_end
867
+ end
868
+
869
+ # `rect <colour>` highlights the statements it wraps. The colour is
870
+ # parsed like any block label and dropped, as for `opt`, `loop` and
871
+ # the other frames: only the statements inside are kept.
872
+ rule(:rect_structure) do
873
+ str("rect") >> match["a-zA-Z0-9_"].absent? >> space? >>
874
+ text_run.as(:rect_label) >> content_boundary >>
875
+ ws? >>
876
+ statements.as(:rect_statements) >>
877
+ ws? >>
878
+ str("end") >> line_end
879
+ end
880
+
881
+ rule(:par_structure) do
882
+ str("par") >> space? >>
883
+ text_run.as(:par_label) >> content_boundary >>
884
+ ws? >>
885
+ statements.as(:par_statements) >>
886
+ ws? >>
887
+ (
888
+ str("and") >> space? >>
889
+ text_run.as(:and_label) >> content_boundary >>
890
+ ws? >>
891
+ statements.as(:and_statements) >>
892
+ ws?
893
+ ).repeat.as(:and_blocks) >>
894
+ str("end") >> line_end
895
+ end
896
+
897
+ rule(:critical_structure) do
898
+ str("critical") >> space? >>
899
+ text_run.as(:critical_label) >> content_boundary >>
900
+ ws? >>
901
+ statements.as(:critical_statements) >>
902
+ ws? >>
903
+ (
904
+ str("option") >> space? >>
905
+ text_run.as(:option_label) >> content_boundary >>
906
+ ws? >>
907
+ statements.as(:option_statements) >>
908
+ ws?
909
+ ).repeat.as(:option_blocks) >>
910
+ str("end") >> line_end
911
+ end
912
+
913
+ rule(:break_structure) do
914
+ str("break") >> space? >>
915
+ text_run.as(:break_label) >> content_boundary >>
916
+ ws? >>
917
+ statements.as(:break_statements) >>
918
+ ws? >>
919
+ str("end") >> line_end
920
+ end
921
+
922
+ # mmdc keeps the quotes of `as "x"` in the label (measured, see
923
+ # spec/etassia/parser/sequence_alias_quotes_spec.rb), so a quoted
924
+ # alias is ordinary text, never a `string`.
925
+ rule(:label) { unquoted_label }
926
+
927
+ # Only caller is `declaration_body`'s alias branch, shared by
928
+ # `participant`/`actor`/`create`. Stops at an inline `;` for the
929
+ # same reason `declaration_body`'s own terminator does below —
930
+ # without it, `participant B as Bee;A->>B: m` reads the whole
931
+ # `;A->>B: m` into the label instead of leaving it for the next
932
+ # statement. Measured against mermaid 11.16.1: `B` gets label
933
+ # "Bee", and `A->>B: m` parses as a separate message.
934
+ rule(:unquoted_label) do
935
+ (char_ref | (line_end.absent? >> semicolon.absent? >> any)).repeat(1)
936
+ end
937
+ end
938
+ end
939
+ end
940
+ end