ratatui_ruby 0.7.0 → 0.7.2

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 (284) 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 +8 -1
  7. data/CHANGELOG.md +34 -0
  8. data/README.md +5 -5
  9. data/Rakefile +1 -1
  10. data/doc/{application_architecture.md → concepts/application_architecture.md} +30 -0
  11. data/doc/concepts/async.md +160 -0
  12. data/doc/{event_handling.md → concepts/event_handling.md} +1 -1
  13. data/doc/contributors/auditing/parity.md +233 -0
  14. data/doc/contributors/developing_examples.md +3 -3
  15. data/doc/contributors/v1.0.0_blockers.md +8 -8
  16. data/doc/{quickstart.md → getting_started/quickstart.md} +26 -26
  17. data/doc/{why.md → getting_started/why.md} +1 -1
  18. data/doc/index.md +23 -9
  19. data/doc/troubleshooting/debugging.md +71 -0
  20. data/doc/{terminal_limitations.md → troubleshooting/terminal_limitations.md} +33 -0
  21. data/examples/app_all_events/README.md +1 -0
  22. data/examples/app_all_events/app.rb +2 -0
  23. data/examples/app_all_events/model/app_model.rb +2 -0
  24. data/examples/app_all_events/model/event_color_cycle.rb +2 -0
  25. data/examples/app_all_events/model/event_entry.rb +2 -0
  26. data/examples/app_all_events/model/msg.rb +2 -0
  27. data/examples/app_all_events/model/timestamp.rb +2 -0
  28. data/examples/app_all_events/update.rb +2 -0
  29. data/examples/app_all_events/view/app_view.rb +2 -0
  30. data/examples/app_all_events/view/controls_view.rb +2 -0
  31. data/examples/app_all_events/view/counts_view.rb +2 -0
  32. data/examples/app_all_events/view/live_view.rb +2 -0
  33. data/examples/app_all_events/view/log_view.rb +2 -0
  34. data/examples/app_all_events/view.rb +2 -0
  35. data/examples/app_color_picker/README.md +2 -0
  36. data/examples/app_color_picker/app.rb +2 -0
  37. data/examples/app_color_picker/clipboard.rb +2 -0
  38. data/examples/app_color_picker/color.rb +2 -0
  39. data/examples/app_color_picker/controls.rb +2 -0
  40. data/examples/app_color_picker/copy_dialog.rb +2 -0
  41. data/examples/app_color_picker/export_pane.rb +2 -0
  42. data/examples/app_color_picker/harmony.rb +2 -0
  43. data/examples/app_color_picker/input.rb +2 -0
  44. data/examples/app_color_picker/main_container.rb +2 -0
  45. data/examples/app_color_picker/palette.rb +2 -0
  46. data/examples/app_login_form/README.md +3 -0
  47. data/examples/app_login_form/app.rb +2 -0
  48. data/examples/app_stateful_interaction/README.md +2 -0
  49. data/examples/app_stateful_interaction/app.rb +2 -0
  50. data/examples/timeout_demo.rb +2 -0
  51. data/examples/verify_quickstart_dsl/README.md +2 -2
  52. data/examples/verify_quickstart_dsl/app.rb +2 -0
  53. data/examples/verify_quickstart_layout/README.md +2 -2
  54. data/examples/verify_quickstart_layout/app.rb +2 -0
  55. data/examples/verify_quickstart_lifecycle/README.md +2 -2
  56. data/examples/verify_quickstart_lifecycle/app.rb +2 -0
  57. data/examples/verify_readme_usage/app.rb +2 -0
  58. data/examples/{widget_barchart_demo → widget_barchart}/README.md +5 -3
  59. data/examples/{widget_barchart_demo → widget_barchart}/app.rb +7 -5
  60. data/examples/{widget_block_demo → widget_block}/README.md +5 -3
  61. data/examples/{widget_block_demo → widget_block}/app.rb +6 -4
  62. data/examples/{widget_box_demo → widget_box}/README.md +7 -4
  63. data/examples/{widget_box_demo → widget_box}/app.rb +7 -5
  64. data/examples/{widget_calendar_demo → widget_calendar}/README.md +6 -3
  65. data/examples/{widget_calendar_demo → widget_calendar}/app.rb +6 -4
  66. data/examples/{widget_canvas_demo → widget_canvas}/README.md +2 -2
  67. data/examples/{widget_canvas_demo → widget_canvas}/app.rb +6 -4
  68. data/examples/{widget_cell_demo → widget_cell}/README.md +6 -3
  69. data/examples/{widget_cell_demo → widget_cell}/app.rb +7 -5
  70. data/examples/{widget_center_demo → widget_center}/README.md +2 -2
  71. data/examples/{widget_center_demo → widget_center}/app.rb +6 -4
  72. data/examples/{widget_chart_demo → widget_chart}/README.md +7 -4
  73. data/examples/{widget_chart_demo → widget_chart}/app.rb +7 -5
  74. data/examples/{widget_gauge_demo → widget_gauge}/README.md +6 -3
  75. data/examples/{widget_gauge_demo → widget_gauge}/app.rb +7 -5
  76. data/examples/widget_layout_split/README.md +5 -2
  77. data/examples/widget_layout_split/app.rb +3 -1
  78. data/examples/{widget_line_gauge_demo → widget_line_gauge}/README.md +6 -3
  79. data/examples/{widget_line_gauge_demo → widget_line_gauge}/app.rb +7 -5
  80. data/examples/{widget_list_demo → widget_list}/README.md +7 -4
  81. data/examples/{widget_list_demo → widget_list}/app.rb +7 -5
  82. data/examples/{widget_map_demo → widget_map}/README.md +7 -4
  83. data/examples/{widget_map_demo → widget_map}/app.rb +4 -2
  84. data/examples/{widget_overlay_demo → widget_overlay}/README.md +6 -3
  85. data/examples/{widget_overlay_demo → widget_overlay}/app.rb +5 -3
  86. data/examples/{widget_popup_demo → widget_popup}/README.md +7 -4
  87. data/examples/{widget_popup_demo → widget_popup}/app.rb +6 -4
  88. data/examples/{widget_ratatui_logo_demo → widget_ratatui_logo}/README.md +6 -3
  89. data/examples/{widget_ratatui_logo_demo → widget_ratatui_logo}/app.rb +8 -6
  90. data/examples/{widget_ratatui_mascot_demo → widget_ratatui_mascot}/README.md +6 -3
  91. data/examples/{widget_ratatui_mascot_demo → widget_ratatui_mascot}/app.rb +6 -4
  92. data/examples/widget_rect/README.md +5 -2
  93. data/examples/widget_rect/app.rb +2 -0
  94. data/examples/widget_render/README.md +4 -1
  95. data/examples/widget_render/app.rb +2 -0
  96. data/examples/widget_rich_text/README.md +4 -1
  97. data/examples/widget_rich_text/app.rb +2 -0
  98. data/examples/widget_scroll_text/README.md +4 -1
  99. data/examples/widget_scroll_text/app.rb +3 -1
  100. data/examples/{widget_scrollbar_demo → widget_scrollbar}/README.md +7 -4
  101. data/examples/{widget_scrollbar_demo → widget_scrollbar}/app.rb +6 -4
  102. data/examples/{widget_sparkline_demo → widget_sparkline}/README.md +6 -3
  103. data/examples/{widget_sparkline_demo → widget_sparkline}/app.rb +7 -5
  104. data/examples/widget_style_colors/README.md +4 -1
  105. data/examples/widget_style_colors/app.rb +2 -0
  106. data/examples/{widget_table_demo → widget_table}/README.md +7 -4
  107. data/examples/{widget_table_demo → widget_table}/app.rb +4 -2
  108. data/examples/{widget_tabs_demo → widget_tabs}/README.md +6 -3
  109. data/examples/{widget_tabs_demo → widget_tabs}/app.rb +7 -5
  110. data/examples/widget_text_width/README.md +5 -2
  111. data/examples/widget_text_width/app.rb +2 -0
  112. data/exe/.gitkeep +0 -0
  113. data/ext/ratatui_ruby/Cargo.lock +1 -1
  114. data/ext/ratatui_ruby/Cargo.toml +1 -1
  115. data/ext/ratatui_ruby/extconf.rb +2 -0
  116. data/ext/ratatui_ruby/src/style.rs +25 -9
  117. data/ext/ratatui_ruby/src/widgets/barchart.rs +8 -6
  118. data/ext/ratatui_ruby/src/widgets/chart.rs +26 -4
  119. data/ext/ratatui_ruby/src/widgets/table.rs +13 -5
  120. data/ext/ratatui_ruby/src/widgets/tabs.rs +49 -9
  121. data/lib/ratatui_ruby/buffer/cell.rb +2 -0
  122. data/lib/ratatui_ruby/buffer.rb +2 -0
  123. data/lib/ratatui_ruby/cell.rb +2 -0
  124. data/lib/ratatui_ruby/event/focus_gained.rb +2 -0
  125. data/lib/ratatui_ruby/event/focus_lost.rb +2 -0
  126. data/lib/ratatui_ruby/event/key/character.rb +2 -0
  127. data/lib/ratatui_ruby/event/key/media.rb +2 -0
  128. data/lib/ratatui_ruby/event/key/modifier.rb +2 -0
  129. data/lib/ratatui_ruby/event/key/navigation.rb +2 -0
  130. data/lib/ratatui_ruby/event/key/system.rb +2 -0
  131. data/lib/ratatui_ruby/event/key.rb +2 -0
  132. data/lib/ratatui_ruby/event/mouse.rb +2 -0
  133. data/lib/ratatui_ruby/event/none.rb +2 -0
  134. data/lib/ratatui_ruby/event/paste.rb +2 -0
  135. data/lib/ratatui_ruby/event/resize.rb +2 -0
  136. data/lib/ratatui_ruby/event.rb +2 -0
  137. data/lib/ratatui_ruby/frame.rb +2 -0
  138. data/lib/ratatui_ruby/layout/constraint.rb +2 -0
  139. data/lib/ratatui_ruby/layout/layout.rb +2 -0
  140. data/lib/ratatui_ruby/layout/rect.rb +2 -0
  141. data/lib/ratatui_ruby/layout.rb +2 -0
  142. data/lib/ratatui_ruby/list_state.rb +2 -0
  143. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -0
  144. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +2 -0
  145. data/lib/ratatui_ruby/schema/bar_chart.rb +4 -2
  146. data/lib/ratatui_ruby/schema/block.rb +4 -2
  147. data/lib/ratatui_ruby/schema/calendar.rb +4 -2
  148. data/lib/ratatui_ruby/schema/canvas.rb +2 -0
  149. data/lib/ratatui_ruby/schema/center.rb +2 -0
  150. data/lib/ratatui_ruby/schema/chart.rb +4 -2
  151. data/lib/ratatui_ruby/schema/clear.rb +2 -0
  152. data/lib/ratatui_ruby/schema/constraint.rb +2 -0
  153. data/lib/ratatui_ruby/schema/cursor.rb +2 -0
  154. data/lib/ratatui_ruby/schema/draw.rb +2 -0
  155. data/lib/ratatui_ruby/schema/gauge.rb +4 -2
  156. data/lib/ratatui_ruby/schema/layout.rb +2 -0
  157. data/lib/ratatui_ruby/schema/line_gauge.rb +4 -2
  158. data/lib/ratatui_ruby/schema/list.rb +3 -1
  159. data/lib/ratatui_ruby/schema/list_item.rb +2 -0
  160. data/lib/ratatui_ruby/schema/overlay.rb +2 -0
  161. data/lib/ratatui_ruby/schema/paragraph.rb +2 -0
  162. data/lib/ratatui_ruby/schema/ratatui_logo.rb +4 -2
  163. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +4 -2
  164. data/lib/ratatui_ruby/schema/rect.rb +2 -0
  165. data/lib/ratatui_ruby/schema/row.rb +2 -0
  166. data/lib/ratatui_ruby/schema/scrollbar.rb +4 -2
  167. data/lib/ratatui_ruby/schema/shape/label.rb +2 -0
  168. data/lib/ratatui_ruby/schema/sparkline.rb +4 -2
  169. data/lib/ratatui_ruby/schema/style.rb +2 -0
  170. data/lib/ratatui_ruby/schema/table.rb +2 -0
  171. data/lib/ratatui_ruby/schema/tabs.rb +4 -2
  172. data/lib/ratatui_ruby/schema/text.rb +2 -0
  173. data/lib/ratatui_ruby/scrollbar_state.rb +2 -0
  174. data/lib/ratatui_ruby/style/style.rb +2 -0
  175. data/lib/ratatui_ruby/style.rb +2 -0
  176. data/lib/ratatui_ruby/table_state.rb +2 -0
  177. data/lib/ratatui_ruby/test_helper/event_injection.rb +2 -0
  178. data/lib/ratatui_ruby/test_helper/snapshot.rb +2 -0
  179. data/lib/ratatui_ruby/test_helper/style_assertions.rb +2 -0
  180. data/lib/ratatui_ruby/test_helper/terminal.rb +2 -0
  181. data/lib/ratatui_ruby/test_helper/test_doubles.rb +2 -0
  182. data/lib/ratatui_ruby/test_helper.rb +5 -3
  183. data/lib/ratatui_ruby/tui/buffer_factories.rb +2 -0
  184. data/lib/ratatui_ruby/tui/canvas_factories.rb +2 -0
  185. data/lib/ratatui_ruby/tui/core.rb +2 -0
  186. data/lib/ratatui_ruby/tui/layout_factories.rb +2 -0
  187. data/lib/ratatui_ruby/tui/state_factories.rb +2 -0
  188. data/lib/ratatui_ruby/tui/style_factories.rb +2 -0
  189. data/lib/ratatui_ruby/tui/text_factories.rb +2 -0
  190. data/lib/ratatui_ruby/tui/widget_factories.rb +2 -0
  191. data/lib/ratatui_ruby/tui.rb +2 -0
  192. data/lib/ratatui_ruby/version.rb +3 -1
  193. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -0
  194. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -0
  195. data/lib/ratatui_ruby/widgets/bar_chart.rb +4 -2
  196. data/lib/ratatui_ruby/widgets/block.rb +4 -2
  197. data/lib/ratatui_ruby/widgets/calendar.rb +4 -2
  198. data/lib/ratatui_ruby/widgets/canvas.rb +2 -0
  199. data/lib/ratatui_ruby/widgets/cell.rb +2 -0
  200. data/lib/ratatui_ruby/widgets/center.rb +2 -0
  201. data/lib/ratatui_ruby/widgets/chart.rb +4 -2
  202. data/lib/ratatui_ruby/widgets/clear.rb +2 -0
  203. data/lib/ratatui_ruby/widgets/cursor.rb +2 -0
  204. data/lib/ratatui_ruby/widgets/gauge.rb +4 -2
  205. data/lib/ratatui_ruby/widgets/line_gauge.rb +4 -2
  206. data/lib/ratatui_ruby/widgets/list.rb +3 -1
  207. data/lib/ratatui_ruby/widgets/list_item.rb +2 -0
  208. data/lib/ratatui_ruby/widgets/overlay.rb +2 -0
  209. data/lib/ratatui_ruby/widgets/paragraph.rb +2 -0
  210. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +4 -2
  211. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +4 -2
  212. data/lib/ratatui_ruby/widgets/row.rb +2 -0
  213. data/lib/ratatui_ruby/widgets/scrollbar.rb +4 -2
  214. data/lib/ratatui_ruby/widgets/shape/label.rb +2 -0
  215. data/lib/ratatui_ruby/widgets/sparkline.rb +4 -2
  216. data/lib/ratatui_ruby/widgets/table.rb +2 -0
  217. data/lib/ratatui_ruby/widgets/tabs.rb +12 -8
  218. data/lib/ratatui_ruby/widgets.rb +2 -0
  219. data/lib/ratatui_ruby.rb +2 -0
  220. data/tasks/autodoc/examples.rb +2 -0
  221. data/tasks/autodoc/member.rb +2 -0
  222. data/tasks/autodoc/name.rb +2 -0
  223. data/tasks/autodoc.rake +2 -0
  224. data/tasks/bump/cargo_lockfile.rb +2 -0
  225. data/tasks/bump/changelog.rb +10 -0
  226. data/tasks/bump/header.rb +2 -0
  227. data/tasks/bump/history.rb +2 -0
  228. data/tasks/bump/links.rb +2 -0
  229. data/tasks/bump/manifest.rb +2 -0
  230. data/tasks/bump/ruby_gem.rb +14 -0
  231. data/tasks/bump/sem_ver.rb +2 -0
  232. data/tasks/bump/unreleased_section.rb +18 -0
  233. data/tasks/bump.rake +2 -0
  234. data/tasks/doc.rake +268 -0
  235. data/tasks/extension.rake +2 -0
  236. data/tasks/lint.rake +115 -0
  237. data/tasks/rdoc_config.rb +18 -4
  238. data/tasks/sourcehut.rake +2 -0
  239. data/tasks/terminal_preview/app_screenshot.rb +2 -0
  240. data/tasks/terminal_preview/crash_report.rb +2 -0
  241. data/tasks/terminal_preview/example_app.rb +2 -0
  242. data/tasks/terminal_preview/launcher_script.rb +2 -0
  243. data/tasks/terminal_preview/preview_collection.rb +2 -0
  244. data/tasks/terminal_preview/preview_timing.rb +2 -0
  245. data/tasks/terminal_preview/safety_confirmation.rb +2 -0
  246. data/tasks/terminal_preview/saved_screenshot.rb +2 -0
  247. data/tasks/terminal_preview/system_appearance.rb +2 -0
  248. data/tasks/terminal_preview/terminal_window.rb +2 -0
  249. data/tasks/terminal_preview/window_id.rb +2 -0
  250. data/tasks/terminal_preview.rake +2 -0
  251. data/tasks/test.rake +2 -0
  252. data/tasks/website/index_page.rb +2 -0
  253. data/tasks/website/version.rb +12 -2
  254. data/tasks/website/version_menu.rb +2 -0
  255. data/tasks/website/versioned_documentation.rb +2 -0
  256. data/tasks/website/website.rb +2 -0
  257. data/tasks/website.rake +2 -0
  258. metadata +72 -72
  259. data/doc/contributors/architectural_overhaul/chat_conversations.md +0 -4952
  260. data/doc/contributors/architectural_overhaul/implementation_plan.md +0 -60
  261. data/doc/contributors/architectural_overhaul/task.md +0 -37
  262. /data/doc/{application_testing.md → concepts/application_testing.md} +0 -0
  263. /data/doc/{interactive_design.md → concepts/interactive_design.md} +0 -0
  264. /data/doc/images/{widget_barchart_demo.png → widget_barchart.png} +0 -0
  265. /data/doc/images/{widget_block_demo.png → widget_block.png} +0 -0
  266. /data/doc/images/{widget_box_demo.png → widget_box.png} +0 -0
  267. /data/doc/images/{widget_calendar_demo.png → widget_calendar.png} +0 -0
  268. /data/doc/images/{widget_canvas_demo.png → widget_canvas.png} +0 -0
  269. /data/doc/images/{widget_cell_demo.png → widget_cell.png} +0 -0
  270. /data/doc/images/{widget_center_demo.png → widget_center.png} +0 -0
  271. /data/doc/images/{widget_chart_demo.png → widget_chart.png} +0 -0
  272. /data/doc/images/{widget_gauge_demo.png → widget_gauge.png} +0 -0
  273. /data/doc/images/{widget_line_gauge_demo.png → widget_line_gauge.png} +0 -0
  274. /data/doc/images/{widget_list_demo.png → widget_list.png} +0 -0
  275. /data/doc/images/{widget_map_demo.png → widget_map.png} +0 -0
  276. /data/doc/images/{widget_overlay_demo.png → widget_overlay.png} +0 -0
  277. /data/doc/images/{widget_popup_demo.png → widget_popup.png} +0 -0
  278. /data/doc/images/{widget_ratatui_logo_demo.png → widget_ratatui_logo.png} +0 -0
  279. /data/doc/images/{widget_ratatui_mascot_demo.png → widget_ratatui_mascot.png} +0 -0
  280. /data/doc/images/{widget_scrollbar_demo.png → widget_scrollbar.png} +0 -0
  281. /data/doc/images/{widget_sparkline_demo.png → widget_sparkline.png} +0 -0
  282. /data/doc/images/{widget_table_demo.png → widget_table.png} +0 -0
  283. /data/doc/images/{widget_tabs_demo.png → widget_tabs.png} +0 -0
  284. /data/doc/{v0.7.0_migration.md → migration/v0_7_0.md} +0 -0
data/lib/ratatui_ruby.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require_relative "ratatui_ruby/version"
7
9
 
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module Autodoc
7
9
  class Examples
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module Autodoc
7
9
  module Member
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module Autodoc
7
9
  class Name < Data.define(:string)
data/tasks/autodoc.rake CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require_relative "autodoc/examples"
7
9
 
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  # Lockfiles need to be refreshed by a tool after Manifests are changed.
7
9
  class CargoLockfile < Data.define(:path, :dir, :name)
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require_relative "links"
7
9
  require_relative "unreleased_section"
@@ -34,4 +36,12 @@ class Changelog
34
36
  File.write(@path, "#{header}#{UnreleasedSection.fresh}\n\n#{history}\n#{links}")
35
37
  nil
36
38
  end
39
+
40
+ def commit_message(version)
41
+ content = File.read(@path)
42
+ unreleased = UnreleasedSection.parse(content)
43
+ return nil unless unreleased
44
+
45
+ "chore: release v#{version}\n\n#{unreleased.commit_body}"
46
+ end
37
47
  end
data/tasks/bump/header.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  # Header manages the header section of the changelog.
7
9
  class Header
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  # History manages the versioned history of the changelog.
7
9
  class History
data/tasks/bump/links.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  # Manages the version comparison links at the botton of the changelog.
7
9
  #
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  # Manifests hold a copy of the version number and should be changed manually.
7
9
  # Use Regexp lookarounds in `pattern` to match the version number.
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  class RubyGem
7
9
  def initialize(manifests:, lockfile:, changelog:)
@@ -17,19 +19,31 @@ class RubyGem
17
19
 
18
20
  def bump(segment)
19
21
  target = version.next(segment)
22
+ commit_message = @changelog.commit_message(target)
20
23
 
21
24
  puts "Bumping #{segment}: #{version} -> #{target}"
22
25
  @changelog.release(target)
23
26
  @manifests.each { |manifest| manifest.write(target) }
24
27
  @lockfile.refresh
28
+
29
+ puts_commit_message(commit_message)
25
30
  end
26
31
 
27
32
  def set(version_string)
28
33
  target = SemVer.parse(version_string)
34
+ commit_message = @changelog.commit_message(target)
29
35
 
30
36
  puts "Setting version: #{version} -> #{target}"
31
37
  @changelog.release(target)
32
38
  @manifests.each { |manifest| manifest.write(target) }
33
39
  @lockfile.refresh
40
+
41
+ puts_commit_message(commit_message)
42
+ end
43
+
44
+ private def puts_commit_message(message)
45
+ puts "=" * 80
46
+ puts message
47
+ puts "=" * 80
34
48
  end
35
49
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  # See https://semver.org/spec/v2.0.0.html
7
9
  class SemVer
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "date"
9
+ require "rdoc"
7
10
 
8
11
  # UnreleasedSection manages the [Unreleased] section of the changelog.
9
12
  class UnreleasedSection
@@ -35,4 +38,19 @@ class UnreleasedSection
35
38
  def to_s
36
39
  @content
37
40
  end
41
+
42
+ def commit_body
43
+ formatter = Class.new { include RDoc::Text }.new
44
+ @content
45
+ .sub(/^## \[Unreleased\].*$/, "")
46
+ .gsub(/^### (Added|Changed|Fixed|Removed)\n*$/, "")
47
+ .gsub(/^- \*\*([^*]+)\*\*:/, '\1:')
48
+ .gsub(/`([^`]+)`/, '\1')
49
+ .strip
50
+ .lines
51
+ .map { |line| line.gsub(/^- /, "").strip }
52
+ .reject(&:empty?)
53
+ .map { |line| formatter.wrap(line, 72) }
54
+ .join("\n\n")
55
+ end
38
56
  end
data/tasks/bump.rake CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "rubygems"
7
9
 
data/tasks/doc.rake CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "rdoc/task"
7
9
 
@@ -611,9 +613,275 @@ end
611
613
  Rake::Task[:rdoc].enhance do
612
614
  Rake::Task[:copy_doc_images].invoke
613
615
  Rake::Task[:copy_examples].invoke
616
+ Rake::Task[:rewrite_examples_link].invoke
614
617
  end
615
618
 
616
619
  Rake::Task[:rerdoc].enhance do
617
620
  Rake::Task[:copy_doc_images].invoke
618
621
  Rake::Task[:copy_examples].invoke
622
+ Rake::Task[:rewrite_examples_link].invoke
623
+ end
624
+
625
+ task :rewrite_examples_link do
626
+ require "nokogiri"
627
+
628
+ rdoc_dir = ENV["RDOC_OUTPUT"] || "tmp/rdoc"
629
+
630
+ # Build a mapping of example READMEs to their H1 titles and categories
631
+ examples_by_category = { "Apps" => [], "Widgets" => [] }
632
+
633
+ Dir.glob("examples/*/README.md").each do |readme_path|
634
+ dir_name = File.dirname(readme_path).sub("examples/", "")
635
+
636
+ # Skip verify examples entirely
637
+ next if dir_name.start_with?("verify_")
638
+
639
+ content = File.read(readme_path)
640
+ if content =~ /^#\s+(.+)$/
641
+ title = $1.strip.sub(/ Example$/, "") # Remove trailing " Example"
642
+ rdoc_path = "examples/#{dir_name}/README_md.html"
643
+
644
+ # Categorize by prefix
645
+ category = if dir_name.start_with?("app_")
646
+ "Apps"
647
+ elsif dir_name.start_with?("widget_")
648
+ title = title.sub(/ Widget$/, "") # Also strip trailing " Widget" for widgets
649
+ "Widgets"
650
+ else
651
+ nil
652
+ end
653
+
654
+ if category
655
+ examples_by_category[category] << { title:, rdoc_path:, dir_name: }
656
+ end
657
+ end
658
+ end
659
+
660
+ # Sort each category alphabetically by title
661
+ examples_by_category.each_value { |list| list.sort_by! { |e| e[:title] } }
662
+
663
+ # Process all HTML files
664
+ Dir.glob("#{rdoc_dir}/**/*.html").each do |file|
665
+ content = File.read(file)
666
+ modified = false
667
+
668
+ doc = Nokogiri::HTML(content)
669
+
670
+ # Find the examples details section to remove from Pages
671
+ examples_detail = doc.css("details summary").find { |s| s.text.strip.downcase == "examples" }&.parent
672
+
673
+ # Find the classindex-section to insert Examples section before it
674
+ classindex_section = doc.at_css("#classindex-section")
675
+
676
+ if examples_detail && classindex_section
677
+ # Remove examples from Pages section
678
+ examples_detail.remove
679
+
680
+ # Build the new Examples section as a top-level nav-section
681
+ current_depth = file.sub("#{rdoc_dir}/", "").count("/")
682
+ prefix = "../" * current_depth
683
+
684
+ examples_section = Nokogiri::XML::Node.new("div", doc)
685
+ examples_section["id"] = "exampleindex-section"
686
+ examples_section["class"] = "nav-section"
687
+
688
+ examples_section.inner_html = <<~HTML
689
+ <details class="nav-section-collapsible" open>
690
+ <summary class="nav-section-header">
691
+ <span class="nav-section-icon">
692
+ <svg><use href="#icon-layers"></use></svg>
693
+ </span>
694
+ <span class="nav-section-title">Examples</span>
695
+ <span class="nav-section-chevron">
696
+ <svg><use href="#icon-chevron"></use></svg>
697
+ </span>
698
+ </summary>
699
+ <ul class="link-list nav-list">
700
+ </ul>
701
+ </details>
702
+ HTML
703
+
704
+ # Build the category structure
705
+ examples_ul = examples_section.at_css("ul.link-list")
706
+
707
+ examples_by_category.each do |category_name, examples|
708
+ next if examples.empty?
709
+
710
+ cat_li = Nokogiri::XML::Node.new("li", doc)
711
+ cat_details = Nokogiri::XML::Node.new("details", doc)
712
+ # Subcategories closed by default
713
+ cat_summary = Nokogiri::XML::Node.new("summary", doc)
714
+ cat_summary.content = category_name
715
+ cat_details.add_child(cat_summary)
716
+
717
+ cat_ul = Nokogiri::XML::Node.new("ul", doc)
718
+ cat_ul["class"] = "link-list nav-list"
719
+
720
+ examples.each do |example|
721
+ li = Nokogiri::XML::Node.new("li", doc)
722
+ a = Nokogiri::XML::Node.new("a", doc)
723
+ a["href"] = "#{prefix}#{example[:rdoc_path]}"
724
+ a.content = example[:title]
725
+ li.add_child(a)
726
+ cat_ul.add_child(li)
727
+ end
728
+
729
+ cat_details.add_child(cat_ul)
730
+ cat_li.add_child(cat_details)
731
+ examples_ul.add_child(cat_li)
732
+ end
733
+
734
+ # Insert Examples section before Classes and Modules
735
+ classindex_section.add_previous_sibling(examples_section)
736
+
737
+ # --- GUIDES SECTION ---
738
+ # Build dynamic hierarchical tree from doc/ folder structure
739
+ guides_tree = build_guides_tree
740
+
741
+ # Find and remove the doc details section from Pages
742
+ doc_detail = doc.css("details summary").find { |s| s.text.strip.downcase == "doc" }&.parent
743
+ doc_detail&.remove
744
+
745
+ # Create the Guides section
746
+ guides_section = Nokogiri::XML::Node.new("div", doc)
747
+ guides_section["id"] = "guidesindex-section"
748
+ guides_section["class"] = "nav-section"
749
+
750
+ guides_section.inner_html = <<~HTML
751
+ <details class="nav-section-collapsible" open>
752
+ <summary class="nav-section-header">
753
+ <span class="nav-section-icon">
754
+ <svg><use href="#icon-file"></use></svg>
755
+ </span>
756
+ <span class="nav-section-title">Guides</span>
757
+ <span class="nav-section-chevron">
758
+ <svg><use href="#icon-chevron"></use></svg>
759
+ </span>
760
+ </summary>
761
+ <ul class="link-list nav-list">
762
+ </ul>
763
+ </details>
764
+ HTML
765
+
766
+ # Get current file path relative to rdoc_dir (e.g. "doc/getting_started/quickstart_md.html")
767
+ current_file_rel = file.sub("#{rdoc_dir}/", "")
768
+
769
+ guides_ul = guides_section.at_css("ul.link-list")
770
+ build_guides_nav(guides_ul, guides_tree, doc, prefix, current_file_rel, "doc")
771
+
772
+ # Insert Guides section before Examples
773
+ examples_section.add_previous_sibling(guides_section)
774
+
775
+ content = doc.to_html
776
+ modified = true
777
+ end
778
+
779
+ # Also rewrite examples_md.html to examples/index.html
780
+ if content.include?("examples_md.html")
781
+ content = content.gsub(/href="([^"]*?)examples_md\.html"/, 'href="\1examples/index.html"')
782
+ modified = true
783
+ end
784
+
785
+ File.write(file, content) if modified
786
+ end
787
+
788
+ # Delete the now-unused examples_md.html
789
+ examples_page = "#{rdoc_dir}/examples_md.html"
790
+ FileUtils.rm_f(examples_page)
791
+
792
+ puts "Created Examples and Guides sections in sidebar"
793
+ end
794
+
795
+ # Build a hierarchical tree structure from doc/**/*.md files
796
+ def build_guides_tree
797
+ tree = { files: [], subdirs: {} }
798
+
799
+ Dir.glob("doc/**/*.md").each do |md_path|
800
+ # Skip images folder
801
+ next if md_path.include?("/images/")
802
+
803
+ relative = md_path.sub("doc/", "")
804
+ parts = relative.split("/")
805
+ filename = parts.pop
806
+
807
+ # Get title from H1
808
+ content = File.read(md_path)
809
+ title = if content =~ /^#\s+(.+)$/
810
+ $1.strip
811
+ else
812
+ filename.sub(/\.md$/, "").tr("_-", " ").split.map(&:capitalize).join(" ")
813
+ end
814
+
815
+ # Convert to RDoc path
816
+ rdoc_path = "doc/#{relative.gsub('.', '_')}.html"
817
+
818
+ # Navigate to correct position in tree
819
+ current = tree
820
+ parts.each do |dir|
821
+ current[:subdirs][dir] ||= { files: [], subdirs: {} }
822
+ current = current[:subdirs][dir]
823
+ end
824
+
825
+ current[:files] << { title:, rdoc_path:, filename: }
826
+ end
827
+
828
+ # Sort files in each level alphabetically by title
829
+ sort_guides_tree(tree)
830
+ tree
831
+ end
832
+
833
+ def sort_guides_tree(node)
834
+ node[:files].sort_by! { |f| f[:title] }
835
+ node[:subdirs].each_value { |subdir| sort_guides_tree(subdir) }
836
+ end
837
+
838
+ # Recursively build navigation elements from the tree
839
+ # current_file_rel: path of current HTML file relative to rdoc_dir (e.g. "doc/getting_started/quickstart_md.html")
840
+ # current_tree_path: path in the tree we're building (e.g. "doc", "doc/getting_started")
841
+ def build_guides_nav(parent_ul, tree, doc, prefix, current_file_rel, current_tree_path)
842
+ # Add files at this level first
843
+ tree[:files].each do |file|
844
+ # Check if this file is the current page
845
+ is_current = (file[:rdoc_path] == current_file_rel)
846
+
847
+ li = Nokogiri::XML::Node.new("li", doc)
848
+ a = Nokogiri::XML::Node.new("a", doc)
849
+ a["href"] = "#{prefix}#{file[:rdoc_path]}"
850
+ if is_current
851
+ a["class"] = "active"
852
+ strong = Nokogiri::XML::Node.new("strong", doc)
853
+ strong.content = file[:title]
854
+ a.add_child(strong)
855
+ else
856
+ a.content = file[:title]
857
+ end
858
+ li.add_child(a)
859
+ parent_ul.add_child(li)
860
+ end
861
+
862
+ # Add subdirectories as collapsible details
863
+ tree[:subdirs].each do |dir_name, subtree|
864
+ subdir_path = "#{current_tree_path}/#{dir_name}"
865
+
866
+ # Check if current file is inside this subdirectory
867
+ # current_file_rel might be "doc/getting_started/quickstart_md.html"
868
+ # subdir_path would be "doc/getting_started"
869
+ is_current_in_subdir = current_file_rel.start_with?("#{subdir_path}/")
870
+
871
+ li = Nokogiri::XML::Node.new("li", doc)
872
+ details = Nokogiri::XML::Node.new("details", doc)
873
+ # Open if current file is inside this subdir
874
+ details["open"] = "open" if is_current_in_subdir
875
+ summary = Nokogiri::XML::Node.new("summary", doc)
876
+ summary.content = dir_name.tr("_-", " ").split.map(&:capitalize).join(" ")
877
+ details.add_child(summary)
878
+
879
+ subdir_ul = Nokogiri::XML::Node.new("ul", doc)
880
+ subdir_ul["class"] = "link-list nav-list"
881
+ build_guides_nav(subdir_ul, subtree, doc, prefix, current_file_rel, subdir_path)
882
+
883
+ details.add_child(subdir_ul)
884
+ li.add_child(details)
885
+ parent_ul.add_child(li)
886
+ end
619
887
  end
data/tasks/extension.rake CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "rake/extensiontask"
7
9
 
data/tasks/lint.rake CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "rubocop/rake_task"
7
9
  require "rubycritic/rake_task"
@@ -35,6 +37,95 @@ namespace :reuse do
35
37
  task :lint do
36
38
  sh "reuse lint"
37
39
  end
40
+
41
+ desc "Add SPDX headers to files missing them (per AGENTS.md standards)"
42
+ task :fix do
43
+ copyright = "Kerrick Long <me@kerricklong.com>"
44
+
45
+ # Code files: AGPL-3.0-or-later
46
+ code_extensions = %w[rb rs rake gemspec rbs toml yml yaml json lock].freeze
47
+ code_license = "AGPL-3.0-or-later"
48
+
49
+ # Documentation files: CC-BY-SA-4.0
50
+ doc_extensions = %w[md txt].freeze
51
+ doc_license = "CC-BY-SA-4.0"
52
+
53
+ # Find files missing headers (listed after "no copyright and licensing" message)
54
+ puts "Checking for files missing REUSE headers..."
55
+ output = `reuse lint 2>&1`
56
+ in_missing_section = false
57
+ missing_files = output.lines.filter_map do |line|
58
+ in_missing_section = true if line.include?("no copyright and licensing")
59
+ in_missing_section = false if line.start_with?("# ") && !line.include?("copyright")
60
+ next unless in_missing_section
61
+
62
+ line.match(/^\* (.+)/)&.[](1)
63
+ end
64
+
65
+ if missing_files.empty?
66
+ puts "All files have REUSE headers!"
67
+ else
68
+ missing_files.each do |file|
69
+ ext = File.extname(file).delete(".")
70
+ license = if code_extensions.include?(ext)
71
+ code_license
72
+ elsif doc_extensions.include?(ext)
73
+ doc_license
74
+ else
75
+ puts " Skipping #{file} (unknown extension: .#{ext})"
76
+ next
77
+ end
78
+
79
+ puts " Annotating #{file} with #{license}"
80
+ sh "reuse annotate --license #{license} --copyright '#{copyright}' --skip-existing '#{file}'", verbose: false
81
+ end
82
+ end
83
+ end
84
+
85
+ desc "Normalize Ruby files: frozen_string_literal at top, SPDX in #--/#++ block"
86
+ task :normalize_ruby do
87
+ ruby_extensions = %w[rb rake gemspec].freeze
88
+ ruby_files = Dir.glob("**/*.{#{ruby_extensions.join(',')}}")
89
+ .reject { |f| f.start_with?("vendor/", "tmp/", ".") }
90
+
91
+ fixed_count = 0
92
+ ruby_files.each do |file|
93
+ content = File.read(file)
94
+ original = content.dup
95
+
96
+ # Skip if no SPDX header
97
+ next unless content.match?(/# SPDX-/)
98
+
99
+ # Extract components
100
+ frozen = content.match?(/^# frozen_string_literal: true/)
101
+ spdx_match = content.match(/(# SPDX-FileCopyrightText:[^\n]+\n(?:#[^\n]*\n)*# SPDX-License-Identifier:[^\n]+\n)/m)
102
+ next unless spdx_match
103
+
104
+ spdx_block = spdx_match[1]
105
+
106
+ # Remove existing frozen_string_literal and SPDX block (and any #--/#+++)
107
+ cleaned = content
108
+ .sub(/^# frozen_string_literal: true\n+/, "")
109
+ .sub(/^#--\s*\n/, "")
110
+ .sub(spdx_block, "")
111
+ .sub(/^#\+\+\s*\n/, "")
112
+ .sub(/\A\n+/, "") # Remove leading blank lines
113
+
114
+ # Rebuild file in correct order: frozen, blank, #--, SPDX, #++, rest
115
+ new_content = ""
116
+ new_content += "# frozen_string_literal: true\n\n" if frozen
117
+ new_content += "#--\n#{spdx_block}#++\n\n"
118
+ new_content += cleaned.sub(/\A\n+/, "") # Ensure no double blank lines
119
+
120
+ if new_content != original
121
+ File.write(file, new_content)
122
+ puts " Normalized #{file}"
123
+ fixed_count += 1
124
+ end
125
+ end
126
+
127
+ puts fixed_count.zero? ? "All Ruby files properly normalized!" : "Fixed #{fixed_count} files."
128
+ end
38
129
  end
39
130
  task(:reuse) { Rake::Task["reuse:lint"].invoke }
40
131
 
@@ -47,7 +138,31 @@ namespace :lint do
47
138
  task code: %w[rubocop rubycritic cargo:fmt cargo:clippy cargo:test]
48
139
  task licenses: %w[reuse:lint]
49
140
  task all: %w[docs code licenses]
141
+
142
+ namespace :fix do
143
+ desc "Auto-fix RuboCop offenses (most aggressive)"
144
+ task :rubocop do
145
+ sh "bundle exec rubocop --autocorrect-all"
146
+ end
147
+
148
+ desc "Auto-fix Clippy warnings (most aggressive: --fix --allow-dirty --allow-staged)"
149
+ task :clippy do
150
+ sh "cd ext/ratatui_ruby && cargo clippy --fix --allow-dirty --allow-staged"
151
+ end
152
+
153
+ desc "Add SPDX headers and normalize Ruby file structure"
154
+ task reuse: %w[reuse:fix reuse:normalize_ruby]
155
+
156
+ desc "Run all auto-fix tasks"
157
+ task all: %w[lint:fix:rubocop lint:fix:clippy lint:fix:reuse]
158
+ end
50
159
  end
51
160
 
161
+ desc "Run all lint auto-fix tasks"
162
+ task("lint:fix") { Rake::Task["lint:fix:all"].invoke }
163
+
164
+ # Aliases for convenience
165
+ task "rubocop:autocorrect_all" => "lint:fix:rubocop"
166
+
52
167
  desc "Run all lint tasks"
53
168
  task(:lint) { Rake::Task["lint:all"].invoke }