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,814 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "gantt_ticks"
5
+ require_relative "gantt_duration"
6
+ require_relative "gantt_exclusions"
7
+ require_relative "../diagram/gantt"
8
+ require_relative "../notation/mermaid/ir_adapters/gantt"
9
+ require "date"
10
+
11
+ module Etassia
12
+ module Layout
13
+ # Gantt chart transformer for converting models to renderable structure.
14
+ #
15
+ # Handles date calculations, dependency resolution, and positioning.
16
+ #
17
+ # @example Transform a Gantt chart
18
+ # transform = Gantt.new
19
+ # data = transform.to_graph(gantt_diagram)
20
+ class Gantt < Base
21
+ MARGIN_LEFT = 200
22
+ MARGIN_TOP = 80
23
+ MARGIN_RIGHT = 50
24
+ MARGIN_BOTTOM = 50
25
+ ROW_HEIGHT = 24
26
+ SECTION_HEIGHT = 30
27
+ TASK_BAR_HEIGHT = 20
28
+ TIMELINE_WIDTH = 800
29
+ PLOT_WIDTH = 634
30
+ TIMELINE_HEIGHT = 40
31
+ TITLE_Y = 40
32
+ TIMELINE_PADDING_DAYS = 1
33
+ DAY_SECONDS = 86_400
34
+ DEFAULT_AXIS_FORMAT = "%Y-%m-%d"
35
+ SCHEDULE_SETTINGS = %i[
36
+ date_format axis_format tick_interval weekend inclusive_end_dates
37
+ today_marker
38
+ ].freeze
39
+ TASK_PLACEMENTS = {
40
+ id: "source_id", start_date: "start_date", end_date: "end_date",
41
+ duration: "duration", after_task: "after_tasks",
42
+ until_task: "until_task", click_href: "click_href",
43
+ click_callback: "click_callback"
44
+ }.freeze
45
+
46
+ SourceDocument = Struct.new(
47
+ :id, :title, :date_format, :axis_format, :tick_interval, :excludes,
48
+ :weekend, :inclusive_end_dates, :today_marker, :acc_title,
49
+ :acc_description, :sections, keyword_init: true
50
+ )
51
+ SourceSection = Struct.new(:name, :tasks, keyword_init: true)
52
+ SourceTask = Struct.new(
53
+ :description, :id, :start_date, :end_date, :duration, :after_task,
54
+ :until_task, :tags, :click_href, :click_callback, :calculated_start,
55
+ :calculated_end, :render_end, keyword_init: true
56
+ ) do
57
+ def done?
58
+ tags.include?("done")
59
+ end
60
+
61
+ def active?
62
+ tags.include?("active")
63
+ end
64
+
65
+ def critical?
66
+ tags.include?("crit")
67
+ end
68
+
69
+ def milestone?
70
+ tags.include?("milestone")
71
+ end
72
+ end
73
+ private_constant :SourceDocument, :SourceSection, :SourceTask
74
+
75
+ class Label < Lutaml::Model::Serializable
76
+ attribute :text, :string
77
+ attribute :x, :float
78
+ attribute :y, :float
79
+ attribute :text_anchor, :string
80
+ attribute :font_size, :float
81
+ attribute :font_weight, :string
82
+ attribute :dominant_baseline, :string
83
+ end
84
+
85
+ class Rect < Lutaml::Model::Serializable
86
+ attribute :x, :float
87
+ attribute :y, :float
88
+ attribute :width, :float
89
+ attribute :height, :float
90
+ attribute :corner_radius, :float
91
+ attribute :kind, :string
92
+ end
93
+
94
+ class Line < Lutaml::Model::Serializable
95
+ attribute :x1, :float
96
+ attribute :y1, :float
97
+ attribute :x2, :float
98
+ attribute :y2, :float
99
+ end
100
+
101
+ class Task < Lutaml::Model::Serializable
102
+ attribute :label, Label
103
+ attribute :bar, Rect
104
+ attribute :milestone_points, :string
105
+ attribute :id_label, Label
106
+ attribute :status, :string
107
+ attribute :start_date, :date
108
+ attribute :end_date, :date
109
+ end
110
+
111
+ class Section < Lutaml::Model::Serializable
112
+ attribute :background, Rect
113
+ attribute :label, Label
114
+ attribute :tasks, Task, collection: true, default: -> { [] }
115
+ end
116
+
117
+ class Timeline < Lutaml::Model::Serializable
118
+ attribute :background, Rect
119
+ attribute :labels, Label, collection: true, default: -> { [] }
120
+ attribute :grid_lines, Line, collection: true, default: -> { [] }
121
+ end
122
+
123
+ class Scene < Layout::Scene
124
+ attribute :id, :string
125
+ attribute :view_box, :string
126
+ attribute :acc_title, :string
127
+ attribute :acc_description, :string
128
+ attribute :title, Label
129
+ attribute :timeline, Timeline
130
+ attribute :sections, Section, collection: true, default: -> { [] }
131
+ end
132
+
133
+ # Converts a Gantt diagram to a layout structure with calculated
134
+ # positions.
135
+ #
136
+ # @param diagram [Diagram::Gantt] the Gantt diagram to transform
137
+ # @return [Hash] data structure for rendering
138
+ def build_graph(diagram)
139
+ @diagram = source_document(ir_document(diagram))
140
+ @task_map = build_task_map(@diagram)
141
+ calculate_task_dates
142
+ timeline = calculate_timeline
143
+ schedule_attributes.merge(
144
+ sections: transform_sections(@diagram, timeline),
145
+ timeline: timeline,
146
+ metadata: graph_metadata,
147
+ )
148
+ end
149
+
150
+ private
151
+
152
+ def scene(diagram)
153
+ graph = build_graph(diagram)
154
+ width = MARGIN_LEFT + TIMELINE_WIDTH + MARGIN_RIGHT
155
+ height = scene_height(graph[:sections])
156
+ Scene.new(**scene_attributes(graph, width, height))
157
+ end
158
+
159
+ def scene_attributes(graph, width, height)
160
+ {
161
+ id: graph[:id], width: width, height: height,
162
+ view_box: "0 0 #{width} #{height}",
163
+ acc_title: graph[:acc_title],
164
+ acc_description: graph[:acc_description],
165
+ title: title_geometry(graph[:title]),
166
+ timeline: displayed_timeline(graph),
167
+ sections: section_geometry(graph[:sections])
168
+ }
169
+ end
170
+
171
+ def ir_document(diagram)
172
+ return diagram if diagram.is_a?(IR::Prepositioned)
173
+
174
+ Notation::Mermaid::IRAdapters::Gantt.call(diagram)
175
+ end
176
+
177
+ def source_document(document)
178
+ settings = document.items.find do |item|
179
+ item.role == "schedule_settings"
180
+ end
181
+ SourceDocument.new(
182
+ **root_source_attributes(document),
183
+ **schedule_setting_attributes(settings),
184
+ excludes: children(document, settings&.id, "exclusion").map(&:label),
185
+ sections: source_sections(document),
186
+ )
187
+ end
188
+
189
+ def root_source_attributes(document)
190
+ {
191
+ id: document.id, title: document.label,
192
+ acc_title: document.accessibility_title,
193
+ acc_description: document.accessibility_description
194
+ }
195
+ end
196
+
197
+ def schedule_setting_attributes(settings)
198
+ SCHEDULE_SETTINGS.to_h do |name|
199
+ [name, placement_value(settings, name.to_s)]
200
+ end
201
+ end
202
+
203
+ def source_sections(document)
204
+ children(document, nil, "section").sort_by { |item| source_order(item) }
205
+ .map do |section|
206
+ tasks = children(document, section.id, "task")
207
+ .sort_by { |item| source_order(item) }
208
+ .map { |task| source_task(document, task) }
209
+ SourceSection.new(name: section.label, tasks: tasks)
210
+ end
211
+ end
212
+
213
+ def source_task(document, item)
214
+ attributes = TASK_PLACEMENTS.to_h do |name, dimension|
215
+ [name, placement_value(item, dimension)]
216
+ end
217
+ attributes[:description] = item.label
218
+ attributes[:tags] = source_tags(document, item)
219
+ SourceTask.new(**attributes)
220
+ end
221
+
222
+ def source_tags(document, item)
223
+ children(document, item.id, "status_flag")
224
+ .sort_by { |status| source_order(status) }.map(&:label)
225
+ end
226
+
227
+ def schedule_attributes
228
+ @diagram.to_h.slice(
229
+ :id, :title, :acc_title, :acc_description, *SCHEDULE_SETTINGS,
230
+ :excludes
231
+ )
232
+ end
233
+
234
+ def graph_metadata
235
+ {
236
+ section_count: @diagram.sections.length,
237
+ task_count: total_task_count(@diagram),
238
+ }
239
+ end
240
+
241
+ def children(document, parent_id, role)
242
+ document.items.select do |item|
243
+ item.parent_id == parent_id && item.role == role
244
+ end
245
+ end
246
+
247
+ def source_order(item)
248
+ placement_value(item, "source_order") || 0
249
+ end
250
+
251
+ def placement_value(item, dimension)
252
+ placement = item&.placements&.find do |candidate|
253
+ candidate.dimension == dimension
254
+ end
255
+ placement&.value&.value
256
+ end
257
+
258
+ def scene_height(sections)
259
+ task_extent(task_row_count(sections)) + MARGIN_BOTTOM
260
+ end
261
+
262
+ def title_geometry(title)
263
+ return unless title
264
+
265
+ Label.new(text: title, x: MARGIN_LEFT + (TIMELINE_WIDTH / 2),
266
+ y: TITLE_Y, text_anchor: "middle",
267
+ font_size: large_font_size, font_weight: "bold")
268
+ end
269
+
270
+ def displayed_timeline(graph)
271
+ return if graph.dig(:metadata, :task_count).zero?
272
+
273
+ timeline_geometry(graph)
274
+ end
275
+
276
+ def timeline_geometry(graph)
277
+ timeline = graph[:timeline]
278
+ rows = task_row_count(graph[:sections])
279
+ Timeline.new(
280
+ background: timeline_background,
281
+ labels: date_labels(timeline, graph[:axis_format],
282
+ graph[:tick_interval], task_extent(rows) + 5),
283
+ grid_lines: timeline_grid_lines(timeline, graph[:sections],
284
+ graph[:tick_interval]),
285
+ )
286
+ end
287
+
288
+ def timeline_background
289
+ Rect.new(x: MARGIN_LEFT, y: MARGIN_TOP,
290
+ width: TIMELINE_WIDTH, height: TIMELINE_HEIGHT,
291
+ kind: "timeline")
292
+ end
293
+
294
+ def date_labels(timeline, format, interval = nil,
295
+ y_position = MARGIN_TOP + TIMELINE_HEIGHT + 5)
296
+ tick_positions(timeline, interval).map do |tick, position|
297
+ date_label(tick, position, format, y_position)
298
+ end
299
+ end
300
+
301
+ # [tick time, fraction of the timeline] pairs. The timeline is padded
302
+ # by a day each side; mermaid's axis spans only the tasks, so ticks
303
+ # are chosen from the unpadded range.
304
+ def tick_positions(timeline, interval)
305
+ start, stop, origin, days, width = tick_axis(timeline)
306
+ return [] if days <= 0
307
+
308
+ GanttTicks.times(start, stop, interval).map do |tick|
309
+ fraction = (tick - origin) / (days * DAY_SECONDS)
310
+ pixels = (fraction * width).round + 0.5
311
+ [tick, pixels / TIMELINE_WIDTH]
312
+ end
313
+ end
314
+
315
+ def tick_axis(timeline)
316
+ origin, days, width = task_axis(timeline)
317
+ return [origin, origin + (days * DAY_SECONDS), origin, days, width] if
318
+ days < timeline[:total_days]
319
+
320
+ pad = TIMELINE_PADDING_DAYS * DAY_SECONDS
321
+ [origin + pad, origin + (days * DAY_SECONDS) - pad,
322
+ origin, days, width]
323
+ end
324
+
325
+ def date_label(date, position, format, y_position)
326
+ Label.new(
327
+ text: format_date(date, format),
328
+ x: MARGIN_LEFT + (position * TIMELINE_WIDTH),
329
+ y: y_position,
330
+ text_anchor: "middle", font_size: small_font_size
331
+ )
332
+ end
333
+
334
+ def timeline_grid_lines(timeline, sections, interval)
335
+ rows = task_row_count(sections)
336
+ tick_positions(timeline, interval).map do |_tick, position|
337
+ timeline_grid_line(position, rows)
338
+ end
339
+ end
340
+
341
+ def timeline_grid_line(position, rows)
342
+ x = MARGIN_LEFT + (position * TIMELINE_WIDTH)
343
+ y = MARGIN_TOP + TIMELINE_HEIGHT
344
+ Line.new(x1: x, y1: y, x2: x, y2: task_extent(rows))
345
+ end
346
+
347
+ def section_geometry(sections)
348
+ next_y = MARGIN_TOP + TIMELINE_HEIGHT
349
+ sections.map do |section|
350
+ result = typed_section(section, next_y)
351
+ next_y += section_height(section)
352
+ result
353
+ end
354
+ end
355
+
356
+ def typed_section(section, section_y)
357
+ Section.new(
358
+ background: section_background(section_y, section_height(section)),
359
+ label: section_label(section[:name], section_y,
360
+ section_height(section)),
361
+ tasks: typed_tasks(section[:tasks], section_y),
362
+ )
363
+ end
364
+
365
+ def section_background(section_y, height)
366
+ Rect.new(
367
+ x: 0, y: section_y,
368
+ width: MARGIN_LEFT + TIMELINE_WIDTH + MARGIN_RIGHT,
369
+ height: height, kind: "section"
370
+ )
371
+ end
372
+
373
+ def section_label(name, section_y, height)
374
+ Label.new(
375
+ text: name, x: 10, y: section_y + (height / 2),
376
+ font_size: normal_font_size, font_weight: "bold",
377
+ dominant_baseline: "middle"
378
+ )
379
+ end
380
+
381
+ def task_row_count(sections)
382
+ sections.sum { |section| section[:tasks].length }
383
+ end
384
+
385
+ def section_height(section)
386
+ section[:tasks].length * ROW_HEIGHT
387
+ end
388
+
389
+ def task_extent(rows)
390
+ MARGIN_TOP + TIMELINE_HEIGHT + (rows * ROW_HEIGHT)
391
+ end
392
+
393
+ def typed_tasks(tasks, first_y)
394
+ tasks.map.with_index do |task, index|
395
+ task_geometry(task, first_y + (index * ROW_HEIGHT))
396
+ end
397
+ end
398
+
399
+ def task_geometry(task, row_position)
400
+ x_coordinate, y_coordinate = task_position(task, row_position)
401
+ Task.new(
402
+ label: task_label(task, row_position),
403
+ bar: task_bar(task, x_coordinate, y_coordinate),
404
+ milestone_points: task_milestone(task, x_coordinate, y_coordinate),
405
+ id_label: task_id_label(task, x_coordinate, y_coordinate),
406
+ status: task_status(task),
407
+ start_date: task[:start_date], end_date: task[:end_date]
408
+ )
409
+ end
410
+
411
+ def task_position(task, row_position)
412
+ x_coordinate = MARGIN_LEFT + task[:start_x]
413
+ y_coordinate = row_position + ((ROW_HEIGHT - TASK_BAR_HEIGHT) / 2)
414
+ [x_coordinate, y_coordinate]
415
+ end
416
+
417
+ def task_label(task, row_position)
418
+ Label.new(
419
+ text: task[:description], x: 10,
420
+ y: row_position + (ROW_HEIGHT / 2),
421
+ font_size: small_font_size, dominant_baseline: "middle"
422
+ )
423
+ end
424
+
425
+ def task_bar(task, x_coordinate, y_coordinate)
426
+ return if milestone?(task)
427
+
428
+ Rect.new(
429
+ x: x_coordinate, y: y_coordinate, width: task[:width],
430
+ height: TASK_BAR_HEIGHT, corner_radius: 3, kind: "task"
431
+ )
432
+ end
433
+
434
+ def task_milestone(task, x_coordinate, y_coordinate)
435
+ return unless milestone?(task)
436
+
437
+ milestone_points(x_coordinate, y_coordinate)
438
+ end
439
+
440
+ def milestone?(task)
441
+ task[:milestone]
442
+ end
443
+
444
+ def milestone_points(x_coordinate, y_coordinate)
445
+ center_y = y_coordinate + (TASK_BAR_HEIGHT / 2)
446
+ size = 12
447
+ [[x_coordinate, center_y],
448
+ [x_coordinate + size, center_y - size],
449
+ [x_coordinate + (size * 2), center_y],
450
+ [x_coordinate + size, center_y + size]]
451
+ .map { |point| point.join(",") }.join(" ")
452
+ end
453
+
454
+ def task_id_label(task, x_coordinate, y_coordinate)
455
+ return unless task[:id] && task[:width] > 40
456
+
457
+ Label.new(
458
+ text: task[:id], x: x_coordinate + (task[:width] / 2),
459
+ y: y_coordinate + (TASK_BAR_HEIGHT / 2), text_anchor: "middle",
460
+ font_size: small_font_size, dominant_baseline: "middle"
461
+ )
462
+ end
463
+
464
+ def task_status(task)
465
+ return "critical" if task[:critical]
466
+ return "done" if task[:done]
467
+ return "active" if task[:active]
468
+
469
+ "default"
470
+ end
471
+
472
+ def format_date(date, format)
473
+ return date.strftime(DEFAULT_AXIS_FORMAT) unless format
474
+
475
+ date.strftime(format)
476
+ rescue StandardError
477
+ date.strftime(DEFAULT_AXIS_FORMAT)
478
+ end
479
+
480
+ def large_font_size
481
+ theme.typography&.font_size_large ||
482
+ Theme::Registry.get(:default).typography.font_size_large
483
+ end
484
+
485
+ def normal_font_size
486
+ theme.typography&.font_size_normal ||
487
+ Theme::Registry.get(:default).typography.font_size_normal
488
+ end
489
+
490
+ def small_font_size
491
+ theme.typography&.font_size_small ||
492
+ Theme::Registry.get(:default).typography.font_size_small
493
+ end
494
+
495
+ def build_task_map(diagram)
496
+ map = {}
497
+ diagram.sections.each do |section|
498
+ section.tasks.each do |task|
499
+ map[task.id] = task if task.id
500
+ end
501
+ end
502
+ map
503
+ end
504
+
505
+ def calculate_task_dates
506
+ tasks = @diagram.sections.flat_map(&:tasks)
507
+ calculate_explicit_dates(tasks)
508
+ resolve_dependencies(tasks)
509
+ end
510
+
511
+ def calculate_explicit_dates(tasks)
512
+ tasks.each do |task|
513
+ calculate_task_date(task) if task.start_date && !task.after_task
514
+ end
515
+ end
516
+
517
+ # Resolve declared and implicit dependencies to a fixed point. An
518
+ # implicit duration starts at the previous task's end, including across
519
+ # sections and when that predecessor was itself resolved by dependency.
520
+ def resolve_dependencies(tasks)
521
+ 100.times do
522
+ break unless resolve_dependency_pass(tasks)
523
+ end
524
+ end
525
+
526
+ def resolve_dependency_pass(tasks)
527
+ changed = false
528
+ tasks.each_with_index do |task, index|
529
+ changed = true if resolve_unsettled_task?(task, tasks, index)
530
+ end
531
+ changed
532
+ end
533
+
534
+ def resolve_unsettled_task?(task, tasks, index)
535
+ return false unless unresolved?(task)
536
+ if task.after_task || task.until_task
537
+ return resolve_task_dependency?(task)
538
+ end
539
+ return false unless task.duration && index.positive?
540
+
541
+ chain_from_previous_task?(task, tasks[index - 1])
542
+ end
543
+
544
+ # A task with a start date but an "until" end is scheduled by the
545
+ # first pass yet still waits for the until task's start.
546
+ def unresolved?(task)
547
+ return true unless task.calculated_start
548
+
549
+ dated_until?(task) && task.calculated_end.nil?
550
+ end
551
+
552
+ def dated_until?(task)
553
+ task.until_task && task.start_date && !task.after_task
554
+ end
555
+
556
+ # The predecessor is found by POSITION, not by id — the previous
557
+ # task may have no id at all (ids are optional). Runs inside the
558
+ # same fixed-point loop as `resolve_task_dependency?` so a chain
559
+ # whose predecessor is itself still resolving (e.g. via its own
560
+ # "after") converges once the predecessor does, instead of reading
561
+ # a stale nil on the first pass.
562
+ def chain_from_previous_task?(task, previous_task)
563
+ return false unless previous_task.calculated_end
564
+
565
+ task.calculated_start = previous_task.calculated_end
566
+ settle_end(task, end_for(task.calculated_start, task.duration))
567
+ true
568
+ end
569
+
570
+ def calculate_task_date(task)
571
+ task.calculated_start = midnight(parse_date(task.start_date))
572
+ settle_explicit_task_end(task)
573
+ end
574
+
575
+ def settle_explicit_task_end(task)
576
+ if task.end_date
577
+ settle_end(task, explicit_end(task))
578
+ elsif task.duration && task.calculated_start
579
+ settle_end(task, end_for(task.calculated_start, task.duration))
580
+ end
581
+ end
582
+
583
+ def resolve_task_dependency?(task)
584
+ return resolve_after_dependency?(task) if task.after_task
585
+
586
+ resolve_until_dependency?(task)
587
+ end
588
+
589
+ def resolve_after_dependency?(task)
590
+ ref_task = latest_dependency(task.after_task)
591
+ return false unless ref_task
592
+
593
+ task.calculated_start = ref_task.calculated_end
594
+ settle_dependency_end(task)
595
+ true
596
+ end
597
+
598
+ def settle_dependency_end(task)
599
+ if task.duration
600
+ settle_end(task, end_for(task.calculated_start, task.duration))
601
+ elsif task.end_date
602
+ settle_end(task, explicit_end(task))
603
+ elsif task.until_task
604
+ settle_until_end(task)
605
+ end
606
+ end
607
+
608
+ def settle_until_end(task)
609
+ until_task = @task_map[task.until_task]
610
+ settle_end(task, until_task.calculated_start) if until_task
611
+ end
612
+
613
+ def resolve_until_dependency?(task)
614
+ return false unless task.until_task && task.start_date
615
+
616
+ task.calculated_start = midnight(parse_date(task.start_date))
617
+ until_task = @task_map[task.until_task]
618
+ return false unless until_task&.calculated_start
619
+
620
+ settle_end(task, until_task.calculated_start)
621
+ true
622
+ end
623
+
624
+ # "after a c" names every task this one waits on, space-separated —
625
+ # mermaid starts it once ALL of them are done, i.e. after the LATEST
626
+ # of their ends. Returns nil while any referenced id is unknown or has
627
+ # not yet resolved its own end, so the caller's fixed-point loop tries
628
+ # again on a later iteration instead of starting early on a partial
629
+ # answer. A single id is just the one-element case of the same rule.
630
+ def latest_dependency(after_task)
631
+ ref_tasks = after_task.split.map { |id| @task_map[id] }
632
+ return nil unless ref_tasks.all? { |ref_task| ref_task&.calculated_end }
633
+
634
+ ref_tasks.max_by(&:calculated_end)
635
+ end
636
+
637
+ # Date.parse fills whatever the input omits from the system clock, so
638
+ # "08/17" under `dateFormat MM/DD` picked up the real year and defeated
639
+ # the injected reference date. Date._parse reports which fields the
640
+ # input actually carried, so nothing here reads the clock.
641
+ #
642
+ # Which field comes from where: the year always comes from the reference
643
+ # date when absent, and so does the month when no year was given. A
644
+ # missing day becomes the 1st, and a missing month becomes January when
645
+ # the year IS explicit — both to preserve what Date.parse returned, so
646
+ # "2024/01" still reads as 2024-01-01 rather than moving with the pin.
647
+ def parse_date(date_str, fallback: today)
648
+ return fallback if date_str.to_s.include?("\\")
649
+
650
+ parts = Date._parse(date_str.to_s)
651
+ return ordinal_date(parts) if parts[:yday]
652
+ return fallback unless parts[:mon] || parts[:mday]
653
+
654
+ Date.new(*calendar_date_parts(parts))
655
+ rescue ArgumentError, TypeError
656
+ fallback
657
+ end
658
+
659
+ # Mermaid collapses an invalid explicit end onto its already-resolved
660
+ # start instead of extending the task to the current date.
661
+ def explicit_end(task)
662
+ fallback = task.calculated_start.to_date
663
+ midnight(parse_date(task.end_date, fallback: fallback))
664
+ end
665
+
666
+ # Ordinal dates carry a day-of-year instead of a month and day.
667
+ def ordinal_date(parts)
668
+ Date.ordinal(parts[:year] || today.year, parts[:yday])
669
+ end
670
+
671
+ # An explicit year defaults the month to January; without one, the
672
+ # injected reference date supplies both year and month.
673
+ def calendar_date_parts(parts)
674
+ default_month = parts[:year] ? 1 : today.mon
675
+ [parts[:year] || today.year, parts[:mon] || default_month,
676
+ parts[:mday] || 1]
677
+ end
678
+
679
+ def end_for(start_time, duration_str)
680
+ GanttDuration.add(start_time, duration_str)
681
+ end
682
+
683
+ def midnight(date)
684
+ Time.utc(date.year, date.month, date.day)
685
+ end
686
+
687
+ # An end written as a YYYY-MM-DD date is final; every other end is
688
+ # pushed past excluded days, as mermaid's checkTaskDates does.
689
+ def settle_end(task, stop)
690
+ if task.end_date.to_s.match?(/\A\d{4}-\d{2}-\d{2}\z/)
691
+ task.calculated_end = stop
692
+ task.render_end = nil
693
+ return
694
+ end
695
+
696
+ task.calculated_end, task.render_end =
697
+ exclusions.settle(task.calculated_start, stop)
698
+ end
699
+
700
+ def exclusions
701
+ @exclusions ||= GanttExclusions.new(
702
+ @diagram.excludes, @diagram.weekend, @diagram.date_format
703
+ )
704
+ end
705
+
706
+ def calculate_timeline
707
+ min_date, max_date = timeline_bounds
708
+ min_date ||= midnight(today)
709
+ max_date ||= midnight(today + 30)
710
+ padded_timeline(min_date, max_date)
711
+ end
712
+
713
+ def timeline_bounds
714
+ tasks = @diagram.sections.flat_map(&:tasks)
715
+ starts = tasks.filter_map(&:calculated_start)
716
+ finishes = tasks.filter_map(&:calculated_end)
717
+ [starts.min, finishes.max]
718
+ end
719
+
720
+ def padded_timeline(min_date, max_date)
721
+ start_date = min_date - DAY_SECONDS
722
+ end_date = max_date + DAY_SECONDS
723
+ {
724
+ start_date: start_date, end_date: end_date,
725
+ total_days: (end_date - start_date) / DAY_SECONDS
726
+ }
727
+ end
728
+
729
+ def transform_sections(diagram, timeline)
730
+ diagram.sections.map.with_index do |section, section_index|
731
+ {
732
+ id: "section_#{section_index}",
733
+ name: section.name,
734
+ tasks: transform_tasks(section, timeline, section_index),
735
+ }
736
+ end
737
+ end
738
+
739
+ def transform_tasks(section, axis, section_index)
740
+ section.tasks.map.with_index do |task, task_index|
741
+ transformed_task(task, axis, section_index, task_index)
742
+ end
743
+ end
744
+
745
+ def transformed_task(task, axis, section_index, task_index)
746
+ {
747
+ id: task.id || "task_#{section_index}_#{task_index}",
748
+ description: task.description, tags: task.tags,
749
+ **task_flags(task), **task_timing(task, axis),
750
+ click_href: task.click_href, click_callback: task.click_callback
751
+ }
752
+ end
753
+
754
+ def task_flags(task)
755
+ {
756
+ done: task.done?, active: task.active?,
757
+ critical: task.critical?, milestone: task.milestone?
758
+ }
759
+ end
760
+
761
+ def task_timing(task, axis)
762
+ start_x = calculate_x_position(task.calculated_start, axis)
763
+ width = task_width(task, axis)
764
+ snapped_start = start_x.round
765
+ {
766
+ start_date: task.calculated_start&.to_date,
767
+ end_date: task.calculated_end&.to_date,
768
+ start_x: snapped_start,
769
+ width: (start_x + width).round - snapped_start,
770
+ }
771
+ end
772
+
773
+ def calculate_x_position(date, timeline)
774
+ return 0 unless date
775
+
776
+ origin, days, width = task_axis(timeline)
777
+ return 0 if days.zero?
778
+
779
+ days_from_start = (date - origin) / DAY_SECONDS
780
+ (days_from_start.to_f / days) * width
781
+ end
782
+
783
+ def task_width(task, timeline)
784
+ start_time = task.calculated_start
785
+ end_time = task.render_end || task.calculated_end
786
+ return 20 unless start_time && end_time
787
+
788
+ _origin, days, width = task_axis(timeline)
789
+ duration_days = (end_time - start_time) / DAY_SECONDS
790
+ return 0 if duration_days <= 0
791
+
792
+ task_width = (duration_days.to_f / days) * width
793
+ task_width < 1 ? 0 : task_width
794
+ end
795
+
796
+ def task_axis(timeline)
797
+ padding = TIMELINE_PADDING_DAYS * 2
798
+ task_days = timeline[:total_days] - padding
799
+ return padded_task_axis(timeline) unless task_days.positive?
800
+
801
+ origin = timeline[:start_date] + (TIMELINE_PADDING_DAYS * DAY_SECONDS)
802
+ [origin, task_days, PLOT_WIDTH]
803
+ end
804
+
805
+ def padded_task_axis(timeline)
806
+ [timeline[:start_date], timeline[:total_days], TIMELINE_WIDTH]
807
+ end
808
+
809
+ def total_task_count(diagram)
810
+ diagram.sections.sum { |s| s.tasks.length }
811
+ end
812
+ end
813
+ end
814
+ end