ratatui_ruby 0.5.0 → 0.7.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 (311) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +10 -4
  7. data/CHANGELOG.md +79 -7
  8. data/README.md +37 -5
  9. data/REUSE.toml +2 -7
  10. data/doc/application_architecture.md +96 -22
  11. data/doc/application_testing.md +76 -30
  12. data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
  13. data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
  14. data/doc/contributors/architectural_overhaul/task.md +37 -0
  15. data/doc/contributors/design/ruby_frontend.md +288 -56
  16. data/doc/contributors/design/rust_backend.md +349 -54
  17. data/doc/contributors/developing_examples.md +134 -49
  18. data/doc/contributors/index.md +7 -5
  19. data/doc/contributors/v1.0.0_blockers.md +1729 -0
  20. data/doc/event_handling.md +11 -3
  21. data/doc/images/app_all_events.png +0 -0
  22. data/doc/images/app_color_picker.png +0 -0
  23. data/doc/images/app_login_form.png +0 -0
  24. data/doc/images/app_stateful_interaction.png +0 -0
  25. data/doc/images/verify_quickstart_dsl.png +0 -0
  26. data/doc/images/verify_quickstart_layout.png +0 -0
  27. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  28. data/doc/images/verify_readme_usage.png +0 -0
  29. data/doc/images/widget_barchart_demo.png +0 -0
  30. data/doc/images/widget_block_demo.png +0 -0
  31. data/doc/images/widget_canvas_demo.png +0 -0
  32. data/doc/images/widget_cell_demo.png +0 -0
  33. data/doc/images/widget_center_demo.png +0 -0
  34. data/doc/images/widget_chart_demo.png +0 -0
  35. data/doc/images/widget_list_demo.png +0 -0
  36. data/doc/images/widget_overlay_demo.png +0 -0
  37. data/doc/images/widget_render.png +0 -0
  38. data/doc/images/widget_rich_text.png +0 -0
  39. data/doc/images/widget_scroll_text.png +0 -0
  40. data/doc/images/widget_sparkline_demo.png +0 -0
  41. data/doc/images/widget_table_demo.png +0 -0
  42. data/doc/images/widget_tabs_demo.png +0 -0
  43. data/doc/images/widget_text_width.png +0 -0
  44. data/doc/index.md +11 -6
  45. data/doc/interactive_design.md +2 -2
  46. data/doc/quickstart.md +127 -165
  47. data/doc/terminal_limitations.md +92 -0
  48. data/doc/v0.7.0_migration.md +236 -0
  49. data/doc/why.md +93 -0
  50. data/examples/app_all_events/README.md +47 -27
  51. data/examples/app_all_events/app.rb +38 -35
  52. data/examples/app_all_events/model/app_model.rb +157 -0
  53. data/examples/app_all_events/model/event_entry.rb +17 -0
  54. data/examples/app_all_events/model/msg.rb +37 -0
  55. data/examples/app_all_events/update.rb +73 -0
  56. data/examples/app_all_events/view/app_view.rb +9 -9
  57. data/examples/app_all_events/view/controls_view.rb +9 -7
  58. data/examples/app_all_events/view/counts_view.rb +13 -9
  59. data/examples/app_all_events/view/live_view.rb +9 -8
  60. data/examples/app_all_events/view/log_view.rb +11 -16
  61. data/examples/app_color_picker/README.md +84 -42
  62. data/examples/app_color_picker/app.rb +24 -62
  63. data/examples/app_color_picker/controls.rb +90 -0
  64. data/examples/app_color_picker/copy_dialog.rb +45 -49
  65. data/examples/app_color_picker/export_pane.rb +126 -0
  66. data/examples/app_color_picker/input.rb +99 -67
  67. data/examples/app_color_picker/main_container.rb +178 -0
  68. data/examples/app_color_picker/palette.rb +55 -26
  69. data/examples/app_login_form/README.md +49 -0
  70. data/examples/app_login_form/app.rb +2 -3
  71. data/examples/app_stateful_interaction/README.md +33 -0
  72. data/examples/app_stateful_interaction/app.rb +272 -0
  73. data/examples/timeout_demo.rb +43 -0
  74. data/examples/verify_quickstart_dsl/README.md +49 -0
  75. data/examples/verify_quickstart_dsl/app.rb +2 -0
  76. data/examples/verify_quickstart_layout/README.md +71 -0
  77. data/examples/verify_quickstart_layout/app.rb +2 -0
  78. data/examples/verify_quickstart_lifecycle/README.md +56 -0
  79. data/examples/verify_quickstart_lifecycle/app.rb +10 -4
  80. data/examples/verify_readme_usage/README.md +43 -0
  81. data/examples/verify_readme_usage/app.rb +8 -2
  82. data/examples/widget_barchart_demo/README.md +50 -0
  83. data/examples/widget_barchart_demo/app.rb +5 -5
  84. data/examples/widget_block_demo/README.md +36 -0
  85. data/examples/widget_block_demo/app.rb +256 -0
  86. data/examples/widget_box_demo/README.md +45 -0
  87. data/examples/widget_calendar_demo/README.md +39 -0
  88. data/examples/widget_calendar_demo/app.rb +5 -1
  89. data/examples/widget_canvas_demo/README.md +27 -0
  90. data/examples/widget_canvas_demo/app.rb +123 -0
  91. data/examples/widget_cell_demo/README.md +36 -0
  92. data/examples/widget_cell_demo/app.rb +31 -24
  93. data/examples/widget_center_demo/README.md +29 -0
  94. data/examples/widget_center_demo/app.rb +116 -0
  95. data/examples/widget_chart_demo/README.md +41 -0
  96. data/examples/widget_chart_demo/app.rb +7 -2
  97. data/examples/widget_gauge_demo/README.md +41 -0
  98. data/examples/widget_layout_split/README.md +44 -0
  99. data/examples/widget_line_gauge_demo/README.md +41 -0
  100. data/examples/widget_list_demo/README.md +49 -0
  101. data/examples/widget_list_demo/app.rb +91 -107
  102. data/examples/widget_map_demo/README.md +39 -0
  103. data/examples/{app_map_demo → widget_map_demo}/app.rb +4 -4
  104. data/examples/widget_overlay_demo/README.md +36 -0
  105. data/examples/widget_overlay_demo/app.rb +248 -0
  106. data/examples/widget_popup_demo/README.md +36 -0
  107. data/examples/widget_ratatui_logo_demo/README.md +34 -0
  108. data/examples/widget_ratatui_logo_demo/app.rb +1 -1
  109. data/examples/widget_ratatui_mascot_demo/README.md +34 -0
  110. data/examples/widget_rect/README.md +38 -0
  111. data/examples/widget_render/README.md +37 -0
  112. data/examples/widget_render/app.rb +3 -3
  113. data/examples/widget_rich_text/README.md +35 -0
  114. data/examples/widget_rich_text/app.rb +62 -33
  115. data/examples/widget_scroll_text/README.md +37 -0
  116. data/examples/widget_scroll_text/app.rb +0 -1
  117. data/examples/widget_scrollbar_demo/README.md +37 -0
  118. data/examples/widget_sparkline_demo/README.md +42 -0
  119. data/examples/widget_sparkline_demo/app.rb +4 -3
  120. data/examples/widget_style_colors/README.md +34 -0
  121. data/examples/widget_table_demo/README.md +48 -0
  122. data/examples/{app_table_select → widget_table_demo}/app.rb +65 -12
  123. data/examples/widget_tabs_demo/README.md +41 -0
  124. data/examples/widget_tabs_demo/app.rb +15 -1
  125. data/examples/widget_text_width/README.md +35 -0
  126. data/examples/widget_text_width/app.rb +113 -0
  127. data/exe/.gitkeep +0 -0
  128. data/ext/ratatui_ruby/Cargo.lock +11 -4
  129. data/ext/ratatui_ruby/Cargo.toml +2 -1
  130. data/ext/ratatui_ruby/src/events.rs +238 -26
  131. data/ext/ratatui_ruby/src/frame.rs +116 -3
  132. data/ext/ratatui_ruby/src/lib.rs +37 -6
  133. data/ext/ratatui_ruby/src/rendering.rs +22 -21
  134. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  135. data/ext/ratatui_ruby/src/terminal.rs +39 -15
  136. data/ext/ratatui_ruby/src/text.rs +13 -4
  137. data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
  138. data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
  139. data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
  140. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
  141. data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
  142. data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
  143. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  144. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
  145. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  146. data/ext/ratatui_ruby/src/widgets/table.rs +191 -34
  147. data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
  148. data/lib/ratatui_ruby/buffer/cell.rb +168 -0
  149. data/lib/ratatui_ruby/buffer.rb +15 -0
  150. data/lib/ratatui_ruby/cell.rb +4 -4
  151. data/lib/ratatui_ruby/event/key/character.rb +35 -0
  152. data/lib/ratatui_ruby/event/key/media.rb +44 -0
  153. data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
  154. data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
  155. data/lib/ratatui_ruby/event/key/system.rb +45 -0
  156. data/lib/ratatui_ruby/event/key.rb +111 -51
  157. data/lib/ratatui_ruby/event/mouse.rb +3 -3
  158. data/lib/ratatui_ruby/event/paste.rb +1 -1
  159. data/lib/ratatui_ruby/frame.rb +100 -4
  160. data/lib/ratatui_ruby/layout/constraint.rb +95 -0
  161. data/lib/ratatui_ruby/layout/layout.rb +106 -0
  162. data/lib/ratatui_ruby/layout/rect.rb +118 -0
  163. data/lib/ratatui_ruby/layout.rb +19 -0
  164. data/lib/ratatui_ruby/list_state.rb +88 -0
  165. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
  166. data/lib/ratatui_ruby/schema/cursor.rb +5 -0
  167. data/lib/ratatui_ruby/schema/gauge.rb +3 -1
  168. data/lib/ratatui_ruby/schema/layout.rb +1 -1
  169. data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
  170. data/lib/ratatui_ruby/schema/list.rb +25 -4
  171. data/lib/ratatui_ruby/schema/list_item.rb +41 -0
  172. data/lib/ratatui_ruby/schema/rect.rb +43 -0
  173. data/lib/ratatui_ruby/schema/row.rb +66 -0
  174. data/lib/ratatui_ruby/schema/style.rb +24 -4
  175. data/lib/ratatui_ruby/schema/table.rb +29 -11
  176. data/lib/ratatui_ruby/schema/text.rb +96 -3
  177. data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
  178. data/lib/ratatui_ruby/style/style.rb +81 -0
  179. data/lib/ratatui_ruby/style.rb +15 -0
  180. data/lib/ratatui_ruby/table_state.rb +90 -0
  181. data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
  182. data/lib/ratatui_ruby/test_helper/snapshot.rb +414 -0
  183. data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
  184. data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
  185. data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
  186. data/lib/ratatui_ruby/test_helper.rb +65 -358
  187. data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
  188. data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
  189. data/lib/ratatui_ruby/tui/core.rb +38 -0
  190. data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
  191. data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
  192. data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
  193. data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
  194. data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
  195. data/lib/ratatui_ruby/tui.rb +75 -0
  196. data/lib/ratatui_ruby/version.rb +1 -1
  197. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
  198. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
  199. data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
  200. data/lib/ratatui_ruby/widgets/block.rb +192 -0
  201. data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
  202. data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
  203. data/lib/ratatui_ruby/widgets/cell.rb +47 -0
  204. data/lib/ratatui_ruby/widgets/center.rb +59 -0
  205. data/lib/ratatui_ruby/widgets/chart.rb +185 -0
  206. data/lib/ratatui_ruby/widgets/clear.rb +54 -0
  207. data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
  208. data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
  209. data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
  210. data/lib/ratatui_ruby/widgets/list.rb +127 -0
  211. data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
  212. data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
  213. data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
  214. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
  215. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
  216. data/lib/ratatui_ruby/widgets/row.rb +68 -0
  217. data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
  218. data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
  219. data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
  220. data/lib/ratatui_ruby/widgets/table.rb +141 -0
  221. data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
  222. data/lib/ratatui_ruby/widgets.rb +40 -0
  223. data/lib/ratatui_ruby.rb +64 -57
  224. data/sig/examples/app_all_events/view.rbs +1 -1
  225. data/sig/examples/app_all_events/view_state.rbs +1 -1
  226. data/sig/examples/app_stateful_interaction/app.rbs +33 -0
  227. data/sig/examples/widget_block_demo/app.rbs +32 -0
  228. data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
  229. data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
  230. data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
  231. data/sig/ratatui_ruby/event.rbs +11 -1
  232. data/sig/ratatui_ruby/frame.rbs +2 -0
  233. data/sig/ratatui_ruby/list_state.rbs +13 -0
  234. data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
  235. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
  236. data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
  237. data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
  238. data/sig/ratatui_ruby/schema/list.rbs +4 -2
  239. data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
  240. data/sig/ratatui_ruby/schema/rect.rbs +3 -0
  241. data/sig/ratatui_ruby/schema/row.rbs +22 -0
  242. data/sig/ratatui_ruby/schema/style.rbs +3 -3
  243. data/sig/ratatui_ruby/schema/table.rbs +3 -1
  244. data/sig/ratatui_ruby/schema/text.rbs +9 -6
  245. data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
  246. data/sig/ratatui_ruby/session.rbs +41 -48
  247. data/sig/ratatui_ruby/table_state.rbs +15 -0
  248. data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
  249. data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
  250. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
  251. data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
  252. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
  253. data/sig/ratatui_ruby/test_helper.rbs +5 -4
  254. data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
  255. data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
  256. data/sig/ratatui_ruby/tui/core.rbs +14 -0
  257. data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
  258. data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
  259. data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
  260. data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
  261. data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
  262. data/sig/ratatui_ruby/tui.rbs +19 -0
  263. data/tasks/autodoc/examples.rb +79 -0
  264. data/tasks/autodoc.rake +7 -35
  265. data/tasks/bump/changelog.rb +3 -3
  266. data/tasks/bump/links.rb +67 -0
  267. data/tasks/sourcehut.rake +64 -21
  268. data/tasks/terminal_preview/app_screenshot.rb +13 -3
  269. data/tasks/terminal_preview/saved_screenshot.rb +4 -3
  270. metadata +169 -48
  271. data/doc/contributors/dwim_dx.md +0 -366
  272. data/doc/images/app_analytics.png +0 -0
  273. data/doc/images/app_custom_widget.png +0 -0
  274. data/doc/images/app_mouse_events.png +0 -0
  275. data/doc/images/app_table_select.png +0 -0
  276. data/doc/images/widget_block_padding.png +0 -0
  277. data/doc/images/widget_block_titles.png +0 -0
  278. data/doc/images/widget_list_styles.png +0 -0
  279. data/doc/images/widget_table_flex.png +0 -0
  280. data/examples/app_all_events/model/events.rb +0 -180
  281. data/examples/app_all_events/model/highlight.rb +0 -57
  282. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
  283. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
  284. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
  285. data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
  286. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
  287. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
  288. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
  289. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
  290. data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
  291. data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
  292. data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
  293. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
  294. data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
  295. data/examples/app_all_events/view_state.rb +0 -42
  296. data/examples/app_color_picker/scene.rb +0 -201
  297. data/examples/widget_block_padding/app.rb +0 -67
  298. data/examples/widget_block_titles/app.rb +0 -69
  299. data/examples/widget_list_styles/app.rb +0 -141
  300. data/examples/widget_table_flex/app.rb +0 -95
  301. data/lib/ratatui_ruby/session/autodoc.rb +0 -417
  302. data/lib/ratatui_ruby/session.rb +0 -163
  303. data/sig/examples/widget_block_padding/app.rbs +0 -11
  304. data/sig/examples/widget_block_titles/app.rbs +0 -11
  305. data/sig/examples/widget_list_styles/app.rbs +0 -11
  306. data/tasks/autodoc/inventory.rb +0 -61
  307. data/tasks/autodoc/notice.rb +0 -26
  308. data/tasks/autodoc/rbs.rb +0 -38
  309. data/tasks/autodoc/rdoc.rb +0 -45
  310. data/tasks/bump/comparison_links.rb +0 -41
  311. /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module Autodoc
7
+ class Examples
8
+ def self.sync
9
+ new.sync
10
+ end
11
+
12
+ def sync
13
+ Dir.glob("{README.md,doc/*.md,examples/*/README.md}").each do |readme_path|
14
+ sync_readme(readme_path)
15
+ end
16
+ end
17
+
18
+ private def sync_readme(readme_path)
19
+ content = File.read(readme_path)
20
+ dir = File.dirname(readme_path)
21
+
22
+ new_content = content.gsub(/<!-- SYNC:START:([^ ]+) -->.*?<!-- SYNC:END -->/m) do
23
+ marker_info = $1
24
+ source_rel_path, segment_id = marker_info.split(":")
25
+ source_path = File.join(dir, source_rel_path)
26
+
27
+ unless File.exist?(source_path)
28
+ warn "Warning: Source file not found: #{source_path}"
29
+ next $&
30
+ end
31
+
32
+ source_content = File.read(source_path)
33
+ extracted_content = if segment_id
34
+ extract_segment(source_content, segment_id, source_path)
35
+ else
36
+ source_content
37
+ end
38
+
39
+ # Detect language from extension
40
+ ext = File.extname(source_path).delete(".")
41
+ lang = (ext == "rb") ? "ruby" : ext
42
+
43
+ # Build replacement
44
+ "<!-- SYNC:START:#{marker_info} -->\n```#{lang}\n#{extracted_content}```\n<!-- SYNC:END -->"
45
+ end
46
+
47
+ if new_content != content
48
+ puts "Syncing #{readme_path}..."
49
+ File.write(readme_path, new_content)
50
+ end
51
+ end
52
+
53
+ def extract_segment(content, segment_id, source_path)
54
+ start_marker = /#\s*\[SYNC:START:#{segment_id}\]/
55
+ end_marker = /#\s*\[SYNC:END:#{segment_id}\]/
56
+
57
+ lines = content.lines
58
+ start_idx = lines.find_index { |l| l =~ start_marker }
59
+ end_idx = lines.find_index { |l| l =~ end_marker }
60
+
61
+ if start_idx && end_idx
62
+ "#{unindent(lines[(start_idx + 1)...end_idx].join).strip}\n"
63
+ else
64
+ warn "Warning: Segment '#{segment_id}' not found in #{source_path}"
65
+ content # Fallback to full content or error? Let's fallback to original for now.
66
+ end
67
+ end
68
+
69
+ def unindent(text)
70
+ lines = text.lines
71
+ # Don't unindent if empty or just one line
72
+ return text if lines.empty?
73
+
74
+ # Find common leading whitespace
75
+ indentation = lines.grep(/\S/).map { |l| l[/^\s*/].length }.min || 0
76
+ lines.map { |l| (l.length > indentation) ? l[indentation..-1] : "#{l.strip}\n" }.join
77
+ end
78
+ end
79
+ end
data/tasks/autodoc.rake CHANGED
@@ -3,45 +3,17 @@
3
3
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
- require_relative "autodoc/inventory"
7
- require_relative "autodoc/notice"
8
- require_relative "autodoc/rbs"
9
- require_relative "autodoc/rdoc"
6
+ require_relative "autodoc/examples"
10
7
 
11
8
  namespace :autodoc do
12
- desc "Generate all autodoc findings"
13
- task all: [:rbs, :rdoc]
9
+ desc "Update all automatically generated documentation"
10
+ task all: [:examples]
14
11
 
15
- desc "Generate all RBS findings"
16
- task rbs: ["rbs:session"]
17
-
18
- desc "Generate all RDoc findings"
19
- task rdoc: ["rdoc:session"]
20
-
21
- namespace :rbs do
22
- desc "Generate RBS for RatatuiRuby::Session"
23
- task :session do
24
- require_relative "../lib/ratatui_ruby"
25
-
26
- Autodoc::Rbs.new(
27
- path: File.expand_path("../sig/ratatui_ruby/session.rbs", __dir__),
28
- notice: Autodoc::Notice.new("autodoc:rbs:session")
29
- ).write(Autodoc::Inventory.new)
30
- end
31
- end
32
-
33
- namespace :rdoc do
34
- desc "Generate RDoc autodoc for RatatuiRuby::Session"
35
- task :session do
36
- require_relative "../lib/ratatui_ruby"
37
-
38
- Autodoc::Rdoc.new(
39
- path: File.expand_path("../lib/ratatui_ruby/session/autodoc.rb", __dir__),
40
- notice: Autodoc::Notice.new("autodoc:rdoc:session")
41
- ).write(Autodoc::Inventory.new)
42
- end
12
+ desc "Sync code snippets in example READMEs with source files"
13
+ task :examples do
14
+ Autodoc::Examples.sync
43
15
  end
44
16
  end
45
17
 
46
- desc "Generate all autodoc findings"
18
+ desc "Update all automatically generated documentation"
47
19
  task autodoc: "autodoc:all"
@@ -3,7 +3,7 @@
3
3
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
- require_relative "comparison_links"
6
+ require_relative "links"
7
7
  require_relative "unreleased_section"
8
8
  require_relative "history"
9
9
  require_relative "header"
@@ -22,13 +22,13 @@ class Changelog
22
22
 
23
23
  header = Header.parse(content)
24
24
  unreleased = UnreleasedSection.parse(content)
25
- links = ComparisonLinks.parse(content)
25
+ links = Links.from_markdown(content)
26
26
 
27
27
  raise "Could not parse CHANGELOG.md" unless header && unreleased && links
28
28
 
29
29
  history = History.parse(content, header.length, unreleased.to_s.length, links.to_s)
30
30
 
31
- links.update(new_version)
31
+ links.release(new_version)
32
32
  history.add(unreleased.as_version(new_version))
33
33
 
34
34
  File.write(@path, "#{header}#{UnreleasedSection.fresh}\n\n#{history}\n#{links}")
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ # Manages the version comparison links at the botton of the changelog.
7
+ #
8
+ # Release automation needs to update links. Manually calculating git diff URLs
9
+ # for every release is tedious and error-prone. SourceHut does not have
10
+ # standard comparison views, complicating matters further.
11
+ #
12
+ # This class manages the collection of links. It parses them from the markdown.
13
+ # It generates the correct tree links for SourceHut. It properly shifts the
14
+ # "Unreleased" pointer.
15
+ #
16
+ # Use it to update the changelog during a release.
17
+ class Links
18
+ PATTERN = /^(\[Unreleased\]: .*)$/m
19
+ UNRELEASED_PATTERN = %r{^\[Unreleased\]: (.*?/refs/)HEAD$}
20
+
21
+ # Creates a Links object from the full markdown content.
22
+ #
23
+ # [content] String. The full text of the changelog.
24
+ def self.from_markdown(content)
25
+ match = content.match(PATTERN)
26
+ return unless match
27
+
28
+ new(match[1].strip)
29
+ end
30
+
31
+ # Returns the raw text of the links.
32
+ attr_reader :text
33
+
34
+ # Creates a new Links object.
35
+ #
36
+ # [text] String. The raw text of the links section.
37
+ def initialize(text)
38
+ @text = text.dup
39
+ end
40
+
41
+ # Releases a new version.
42
+ #
43
+ # Updates the "Unreleased" link to point to the new head. Adds a new link for
44
+ # the just-released version pointing to its specific tag.
45
+ #
46
+ # [version] String. The new version number (e.g., <tt>"0.5.0"</tt>).
47
+ def release(version)
48
+ return unless base_url
49
+
50
+ new_unreleased = "[Unreleased]: #{base_url}HEAD" # .../HEAD
51
+ new_version_link = "[#{version}]: #{base_url}v#{version}" # .../v1.0.0
52
+
53
+ @text.sub!(UNRELEASED_PATTERN, "#{new_unreleased}\n#{new_version_link}")
54
+ self
55
+ end
56
+
57
+ # Returns the string representation of the links.
58
+ def to_s
59
+ @text
60
+ end
61
+
62
+ # The base URL for the repository's references.
63
+ private def base_url
64
+ match = @text.match(UNRELEASED_PATTERN)
65
+ match[1] if match
66
+ end
67
+ end
data/tasks/sourcehut.rake CHANGED
@@ -4,35 +4,78 @@
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
6
  desc "Generate SourceHut build manifests from template"
7
- task :sourcehut do
8
- require "erb"
9
- require "yaml"
7
+ task sourcehut: "sourcehut:build"
10
8
 
11
- spec = Gem::Specification.load("ratatui_ruby.gemspec")
9
+ namespace :sourcehut do
10
+ desc "Build SourceHut manifests"
11
+ task build: "sourcehut:build:manifest"
12
12
 
13
- # Read version directly from file to ensure we get the latest version
14
- # even if it was just bumped in the same Rake execution
15
- version_content = File.read("lib/ratatui_ruby/version.rb")
16
- version = version_content.match(/VERSION = "(.+?)"/)[1]
13
+ namespace :build do
14
+ desc "Generate SourceHut build manifests from template"
15
+ task :manifest do
16
+ require "erb"
17
+ require "yaml"
17
18
 
18
- gem_filename = "#{spec.name}-#{version}.gem"
19
+ spec = Gem::Specification.load("ratatui_ruby.gemspec")
19
20
 
20
- rubies = YAML.load_file("tasks/resources/rubies.yml")
21
+ # Read version directly from file to ensure we get the latest version
22
+ # even if it was just bumped in the same Rake execution
23
+ version_content = File.read("lib/ratatui_ruby/version.rb")
24
+ version = version_content.match(/VERSION = "(.+?)"/)[1]
21
25
 
22
- bundler_version = File.read("Gemfile.lock").match(/BUNDLED WITH\n\s+([\d.]+)/)[1]
26
+ gem_filename = "#{spec.name}-#{version}.gem"
23
27
 
24
- template = File.read("tasks/resources/build.yml.erb")
25
- erb = ERB.new(template, trim_mode: "-")
28
+ rubies = YAML.load_file("tasks/resources/rubies.yml")
26
29
 
27
- FileUtils.mkdir_p ".builds"
30
+ bundler_version = File.read("Gemfile.lock").match(/BUNDLED WITH\n\s+([\d.]+)/)[1]
28
31
 
29
- # Remove old generated files to ensure a clean state
30
- Dir.glob(".builds/*.yml").each { |f| File.delete(f) }
32
+ template = File.read("tasks/resources/build.yml.erb")
33
+ erb = ERB.new(template, trim_mode: "-")
31
34
 
32
- rubies.each do |ruby_version|
33
- filename = ".builds/ruby-#{ruby_version}.yml"
34
- puts "Generating #{filename}..."
35
- content = erb.result_with_hash(ruby_version:, gem_filename:, bundler_version:)
36
- File.write(filename, content)
35
+ FileUtils.mkdir_p ".builds"
36
+
37
+ # Remove old generated files to ensure a clean state
38
+ Dir.glob(".builds/*.yml").each { |f| File.delete(f) }
39
+
40
+ rubies.each do |ruby_version|
41
+ filename = ".builds/ruby-#{ruby_version}.yml"
42
+ puts "Generating #{filename}..."
43
+ content = erb.result_with_hash(ruby_version:, gem_filename:, bundler_version:)
44
+ File.write(filename, content)
45
+ end
46
+ end
47
+ end
48
+
49
+ desc "Update stable branch to match release and set as default"
50
+ task :update_stable do
51
+ # Read version to determine tag
52
+ version_content = File.read("lib/ratatui_ruby/version.rb")
53
+ version = version_content.match(/VERSION = "(.+?)"/)[1]
54
+ tag_name = "v#{version}"
55
+
56
+ # Verify that the version file matches the actual git tag
57
+ # This prevents updating stable to the wrong version if the release failed
58
+ latest_tag = `git describe --tags --abbrev=0`.strip
59
+ if latest_tag != tag_name
60
+ abort "Fatal: Version mismatch! 'lib/ratatui_ruby/version.rb' says #{tag_name}, but the latest git tag is #{latest_tag}."
61
+ end
62
+
63
+ puts "Updating stable branch to point to #{tag_name}..."
64
+ # Resolve the tag to a commit hash (peel annotated tags)
65
+ # This renders a commit SHA that can be pushed to a branch head
66
+ commit_sha = `git rev-parse #{tag_name}^{}`.strip
67
+
68
+ # Update local stable branch to match
69
+ sh "git branch -f stable #{commit_sha}"
70
+
71
+ # Push the commit to remote stable branch
72
+ # This creates 'stable' if it doesn't exist, or fast-forwards it.
73
+ sh "git push origin #{commit_sha}:stable"
74
+ end
75
+ end
76
+
77
+ if Rake::Task.task_defined?("release")
78
+ Rake::Task["release"].enhance do
79
+ Rake::Task["sourcehut:update_stable"].invoke
37
80
  end
38
81
  end
@@ -15,8 +15,18 @@ class AppScreenshot < Data.define(:app, :output_path)
15
15
  LauncherScript.new(app.app_path, Dir.pwd).run do |launcher|
16
16
  TerminalWindow.new(launcher.path, launcher.pid_file).open do |window|
17
17
  take_snapshot(window.window_id)
18
- puts " done."
19
- true
18
+
19
+ if File.size?(output_path)
20
+ puts " done."
21
+ true
22
+ else
23
+ FileUtils.rm_f(output_path)
24
+ puts " FAILED"
25
+ puts
26
+ puts " Window rendered nothing (app may have crashed before drawing)"
27
+ puts
28
+ false
29
+ end
20
30
  end
21
31
  end
22
32
  rescue => e
@@ -28,6 +38,6 @@ class AppScreenshot < Data.define(:app, :output_path)
28
38
  end
29
39
 
30
40
  private def take_snapshot(window_id)
31
- system("screencapture -l #{window_id} -o -x '#{output_path}'")
41
+ system("screencapture", "-l", window_id.to_s, "-o", "-x", output_path)
32
42
  end
33
43
  end
@@ -11,13 +11,14 @@ class SavedScreenshot < Data.define(:app, :path)
11
11
  end
12
12
 
13
13
  def stale?
14
- return true unless exists?
14
+ return true unless valid?
15
15
 
16
16
  app_last_modified > screenshot_last_commit_time
17
17
  end
18
18
 
19
- private def exists?
20
- File.exist?(path)
19
+ private def valid?
20
+ # File must exist and have content (not 0 bytes)
21
+ File.size?(path) || false
21
22
  end
22
23
 
23
24
  private def app_last_modified