ratatui_ruby 0.5.0 → 0.6.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 (234) 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 +6 -0
  7. data/CHANGELOG.md +44 -7
  8. data/README.md +11 -4
  9. data/REUSE.toml +2 -7
  10. data/doc/application_architecture.md +84 -10
  11. data/doc/application_testing.md +75 -29
  12. data/doc/contributors/design/ruby_frontend.md +39 -3
  13. data/doc/contributors/design/rust_backend.md +1 -0
  14. data/doc/contributors/developing_examples.md +129 -44
  15. data/doc/contributors/examples_audit/p1_high.md +21 -0
  16. data/doc/contributors/examples_audit/p2_moderate.md +81 -0
  17. data/doc/contributors/examples_audit.md +41 -0
  18. data/doc/event_handling.md +11 -3
  19. data/doc/images/app_all_events.png +0 -0
  20. data/doc/images/app_color_picker.png +0 -0
  21. data/doc/images/app_login_form.png +0 -0
  22. data/doc/images/app_stateful_interaction.png +0 -0
  23. data/doc/images/verify_quickstart_dsl.png +0 -0
  24. data/doc/images/verify_quickstart_layout.png +0 -0
  25. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  26. data/doc/images/verify_readme_usage.png +0 -0
  27. data/doc/images/widget_barchart_demo.png +0 -0
  28. data/doc/images/widget_block_demo.png +0 -0
  29. data/doc/images/widget_canvas_demo.png +0 -0
  30. data/doc/images/widget_cell_demo.png +0 -0
  31. data/doc/images/widget_center_demo.png +0 -0
  32. data/doc/images/widget_chart_demo.png +0 -0
  33. data/doc/images/widget_list_demo.png +0 -0
  34. data/doc/images/widget_overlay_demo.png +0 -0
  35. data/doc/images/widget_render.png +0 -0
  36. data/doc/images/widget_rich_text.png +0 -0
  37. data/doc/images/widget_scroll_text.png +0 -0
  38. data/doc/images/widget_sparkline_demo.png +0 -0
  39. data/doc/images/widget_table_demo.png +0 -0
  40. data/doc/images/widget_tabs_demo.png +0 -0
  41. data/doc/images/widget_text_width.png +0 -0
  42. data/doc/quickstart.md +69 -76
  43. data/doc/terminal_limitations.md +92 -0
  44. data/examples/app_all_events/README.md +45 -27
  45. data/examples/app_all_events/app.rb +38 -35
  46. data/examples/app_all_events/model/app_model.rb +157 -0
  47. data/examples/app_all_events/model/event_entry.rb +17 -0
  48. data/examples/app_all_events/model/msg.rb +37 -0
  49. data/examples/app_all_events/update.rb +73 -0
  50. data/examples/app_all_events/view/app_view.rb +8 -8
  51. data/examples/app_all_events/view/controls_view.rb +8 -6
  52. data/examples/app_all_events/view/counts_view.rb +12 -8
  53. data/examples/app_all_events/view/live_view.rb +8 -7
  54. data/examples/app_all_events/view/log_view.rb +10 -15
  55. data/examples/app_color_picker/README.md +84 -44
  56. data/examples/app_color_picker/app.rb +24 -62
  57. data/examples/app_color_picker/controls.rb +90 -0
  58. data/examples/app_color_picker/copy_dialog.rb +45 -49
  59. data/examples/app_color_picker/export_pane.rb +126 -0
  60. data/examples/app_color_picker/input.rb +99 -67
  61. data/examples/app_color_picker/main_container.rb +178 -0
  62. data/examples/app_color_picker/palette.rb +55 -26
  63. data/examples/app_login_form/README.md +47 -0
  64. data/examples/app_login_form/app.rb +2 -3
  65. data/examples/app_stateful_interaction/README.md +31 -0
  66. data/examples/app_stateful_interaction/app.rb +272 -0
  67. data/examples/timeout_demo.rb +43 -0
  68. data/examples/verify_quickstart_dsl/README.md +48 -0
  69. data/examples/verify_quickstart_dsl/app.rb +2 -0
  70. data/examples/verify_quickstart_layout/README.md +71 -0
  71. data/examples/verify_quickstart_layout/app.rb +2 -0
  72. data/examples/verify_quickstart_lifecycle/README.md +56 -0
  73. data/examples/verify_quickstart_lifecycle/app.rb +8 -2
  74. data/examples/verify_readme_usage/README.md +43 -0
  75. data/examples/verify_readme_usage/app.rb +8 -2
  76. data/examples/widget_barchart_demo/README.md +49 -0
  77. data/examples/widget_barchart_demo/app.rb +5 -5
  78. data/examples/widget_block_demo/README.md +34 -0
  79. data/examples/widget_block_demo/app.rb +256 -0
  80. data/examples/widget_box_demo/README.md +45 -0
  81. data/examples/widget_calendar_demo/README.md +39 -0
  82. data/examples/widget_canvas_demo/README.md +27 -0
  83. data/examples/widget_canvas_demo/app.rb +123 -0
  84. data/examples/widget_cell_demo/README.md +36 -0
  85. data/examples/widget_cell_demo/app.rb +31 -24
  86. data/examples/widget_center_demo/README.md +29 -0
  87. data/examples/widget_center_demo/app.rb +116 -0
  88. data/examples/widget_chart_demo/README.md +41 -0
  89. data/examples/widget_chart_demo/app.rb +7 -2
  90. data/examples/widget_gauge_demo/README.md +41 -0
  91. data/examples/widget_layout_split/README.md +44 -0
  92. data/examples/widget_line_gauge_demo/README.md +41 -0
  93. data/examples/widget_list_demo/README.md +49 -0
  94. data/examples/widget_list_demo/app.rb +91 -107
  95. data/examples/widget_map_demo/README.md +39 -0
  96. data/examples/{app_map_demo → widget_map_demo}/app.rb +2 -2
  97. data/examples/widget_overlay_demo/app.rb +248 -0
  98. data/examples/widget_popup_demo/README.md +36 -0
  99. data/examples/widget_ratatui_logo_demo/README.md +34 -0
  100. data/examples/widget_ratatui_mascot_demo/README.md +34 -0
  101. data/examples/widget_rect/README.md +38 -0
  102. data/examples/widget_render/README.md +37 -0
  103. data/examples/widget_rich_text/README.md +35 -0
  104. data/examples/widget_rich_text/app.rb +62 -33
  105. data/examples/widget_scroll_text/README.md +37 -0
  106. data/examples/widget_scroll_text/app.rb +0 -1
  107. data/examples/widget_scrollbar_demo/README.md +37 -0
  108. data/examples/widget_sparkline_demo/README.md +42 -0
  109. data/examples/widget_sparkline_demo/app.rb +4 -3
  110. data/examples/widget_style_colors/README.md +34 -0
  111. data/examples/widget_table_demo/README.md +48 -0
  112. data/examples/{app_table_select → widget_table_demo}/app.rb +46 -8
  113. data/examples/widget_tabs_demo/README.md +41 -0
  114. data/examples/widget_tabs_demo/app.rb +15 -1
  115. data/examples/widget_text_width/README.md +35 -0
  116. data/examples/widget_text_width/app.rb +106 -0
  117. data/exe/.gitkeep +0 -0
  118. data/ext/ratatui_ruby/Cargo.lock +11 -4
  119. data/ext/ratatui_ruby/Cargo.toml +2 -1
  120. data/ext/ratatui_ruby/src/events.rs +238 -26
  121. data/ext/ratatui_ruby/src/frame.rs +113 -1
  122. data/ext/ratatui_ruby/src/lib.rs +34 -4
  123. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  124. data/ext/ratatui_ruby/src/terminal.rs +39 -15
  125. data/ext/ratatui_ruby/src/text.rs +1 -1
  126. data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
  127. data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
  128. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
  129. data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
  130. data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
  131. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  132. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
  133. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  134. data/ext/ratatui_ruby/src/widgets/table.rs +113 -1
  135. data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
  136. data/lib/ratatui_ruby/cell.rb +4 -4
  137. data/lib/ratatui_ruby/event/key/character.rb +35 -0
  138. data/lib/ratatui_ruby/event/key/media.rb +44 -0
  139. data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
  140. data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
  141. data/lib/ratatui_ruby/event/key/system.rb +45 -0
  142. data/lib/ratatui_ruby/event/key.rb +111 -51
  143. data/lib/ratatui_ruby/event/mouse.rb +3 -3
  144. data/lib/ratatui_ruby/event/paste.rb +1 -1
  145. data/lib/ratatui_ruby/frame.rb +96 -0
  146. data/lib/ratatui_ruby/list_state.rb +88 -0
  147. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
  148. data/lib/ratatui_ruby/schema/cursor.rb +5 -0
  149. data/lib/ratatui_ruby/schema/gauge.rb +3 -1
  150. data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
  151. data/lib/ratatui_ruby/schema/list.rb +25 -4
  152. data/lib/ratatui_ruby/schema/list_item.rb +41 -0
  153. data/lib/ratatui_ruby/schema/rect.rb +43 -0
  154. data/lib/ratatui_ruby/schema/style.rb +24 -4
  155. data/lib/ratatui_ruby/schema/table.rb +21 -3
  156. data/lib/ratatui_ruby/schema/text.rb +69 -1
  157. data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
  158. data/lib/ratatui_ruby/session/autodoc.rb +65 -0
  159. data/lib/ratatui_ruby/session.rb +22 -7
  160. data/lib/ratatui_ruby/table_state.rb +90 -0
  161. data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
  162. data/lib/ratatui_ruby/test_helper/snapshot.rb +390 -0
  163. data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
  164. data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
  165. data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
  166. data/lib/ratatui_ruby/test_helper.rb +65 -358
  167. data/lib/ratatui_ruby/version.rb +1 -1
  168. data/lib/ratatui_ruby.rb +42 -19
  169. data/sig/examples/app_stateful_interaction/app.rbs +33 -0
  170. data/sig/examples/widget_block_demo/app.rbs +32 -0
  171. data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
  172. data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
  173. data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
  174. data/sig/ratatui_ruby/event.rbs +11 -1
  175. data/sig/ratatui_ruby/frame.rbs +2 -0
  176. data/sig/ratatui_ruby/list_state.rbs +13 -0
  177. data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
  178. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
  179. data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
  180. data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
  181. data/sig/ratatui_ruby/schema/list.rbs +4 -2
  182. data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
  183. data/sig/ratatui_ruby/schema/rect.rbs +3 -0
  184. data/sig/ratatui_ruby/schema/style.rbs +3 -3
  185. data/sig/ratatui_ruby/schema/table.rbs +3 -1
  186. data/sig/ratatui_ruby/schema/text.rbs +8 -6
  187. data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
  188. data/sig/ratatui_ruby/session.rbs +13 -0
  189. data/sig/ratatui_ruby/table_state.rbs +15 -0
  190. data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
  191. data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
  192. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
  193. data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
  194. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
  195. data/sig/ratatui_ruby/test_helper.rbs +5 -4
  196. data/tasks/autodoc/examples.rb +79 -0
  197. data/tasks/autodoc/inventory.rb +9 -7
  198. data/tasks/autodoc.rake +11 -5
  199. data/tasks/bump/changelog.rb +3 -3
  200. data/tasks/bump/links.rb +67 -0
  201. data/tasks/sourcehut.rake +61 -21
  202. data/tasks/terminal_preview/app_screenshot.rb +13 -3
  203. data/tasks/terminal_preview/saved_screenshot.rb +4 -3
  204. metadata +111 -37
  205. data/doc/images/app_table_select.png +0 -0
  206. data/doc/images/widget_block_padding.png +0 -0
  207. data/doc/images/widget_block_titles.png +0 -0
  208. data/doc/images/widget_list_styles.png +0 -0
  209. data/examples/app_all_events/model/events.rb +0 -180
  210. data/examples/app_all_events/model/highlight.rb +0 -57
  211. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
  212. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
  213. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
  214. data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
  215. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
  216. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
  217. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
  218. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
  219. data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
  220. data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
  221. data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
  222. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
  223. data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
  224. data/examples/app_all_events/view_state.rb +0 -42
  225. data/examples/app_color_picker/scene.rb +0 -201
  226. data/examples/widget_block_padding/app.rb +0 -67
  227. data/examples/widget_block_titles/app.rb +0 -69
  228. data/examples/widget_list_styles/app.rb +0 -141
  229. data/examples/widget_table_flex/app.rb +0 -95
  230. data/sig/examples/widget_block_padding/app.rbs +0 -11
  231. data/sig/examples/widget_block_titles/app.rbs +0 -11
  232. data/sig/examples/widget_list_styles/app.rbs +0 -11
  233. data/tasks/bump/comparison_links.rb +0 -41
  234. /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
@@ -11,11 +11,14 @@ module RatatuiRuby
11
11
  def focus_lost?: () -> bool
12
12
  def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, untyped]
13
13
 
14
+ type key_kind = :standard | :function | :media | :modifier | :system
15
+
14
16
  class Key < Event
15
17
  attr_reader code: String
16
18
  attr_reader modifiers: Array[String]
19
+ attr_reader kind: key_kind
17
20
 
18
- def initialize: (code: String, modifiers: Array[String]) -> void
21
+ def initialize: (code: String, ?modifiers: Array[String], ?kind: key_kind) -> void
19
22
  def ==: (untyped other) -> bool
20
23
  def to_sym: () -> Symbol
21
24
  def to_s: () -> String
@@ -24,6 +27,13 @@ module RatatuiRuby
24
27
  def alt?: () -> bool
25
28
  def shift?: () -> bool
26
29
  def text?: () -> bool
30
+ def char: () -> String
31
+ def media?: () -> bool
32
+ def system?: () -> bool
33
+ def function?: () -> bool
34
+ def modifier?: () -> bool
35
+ def standard?: () -> bool
36
+ def unmodified?: () -> bool
27
37
  def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, untyped]
28
38
  end
29
39
 
@@ -5,5 +5,7 @@ module RatatuiRuby
5
5
  class Frame
6
6
  def area: () -> Rect
7
7
  def render_widget: (widget widget, Rect area) -> nil
8
+ def render_stateful_widget: (widget widget, Rect area, (ListState | TableState | ScrollbarState) state) -> nil
9
+ def set_cursor_position: (Integer x, Integer y) -> nil
8
10
  end
9
11
  end
@@ -0,0 +1,13 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ module RatatuiRuby
5
+ class ListState
6
+ def self.new: (?Integer? selected) -> ListState
7
+ def select: (Integer? index) -> nil
8
+ def selected: () -> Integer?
9
+ def offset: () -> Integer
10
+ def scroll_down_by: (Integer n) -> nil
11
+ def scroll_up_by: (Integer n) -> nil
12
+ end
13
+ end
@@ -12,7 +12,7 @@ module RatatuiRuby
12
12
  def self.restore_terminal: () -> void
13
13
  def self.draw: (widget tree) -> void
14
14
  | () { (Frame) -> void } -> void
15
- def self._poll_event: () -> Hash[Symbol, untyped]?
16
- def self.poll_event: () -> Event?
15
+ def self._poll_event: (Float?) -> Hash[Symbol, untyped]?
16
+ def self.poll_event: (?timeout: Float?) -> Event?
17
17
  def self.inject_test_event: (String, Hash[Symbol, untyped]) -> void
18
18
  end
@@ -5,12 +5,12 @@ module RatatuiRuby
5
5
  class BarChart
6
6
  class Bar < Data
7
7
  attr_reader value: Integer
8
- attr_reader label: String?
8
+ attr_reader label: (String | Text::Span | Text::Line)?
9
9
  attr_reader style: Style?
10
10
  attr_reader value_style: Style?
11
- attr_reader text_value: String?
11
+ attr_reader text_value: (String | Text::Span | Text::Line)?
12
12
 
13
- def self.new: (value: Numeric, ?label: String?, ?style: Style?, ?value_style: Style?, ?text_value: String?) -> Bar
13
+ def self.new: (value: Numeric, ?label: String | Text::Span | Text::Line | nil, ?style: Style?, ?value_style: Style?, ?text_value: String | Text::Span | Text::Line | nil) -> Bar
14
14
  end
15
15
  end
16
16
  end
@@ -4,12 +4,12 @@
4
4
  module RatatuiRuby
5
5
  class Gauge < Data
6
6
  attr_reader ratio: Float
7
- attr_reader label: String?
7
+ attr_reader label: (String | Text::Span)?
8
8
  attr_reader style: Style?
9
9
  attr_reader gauge_style: Style?
10
10
  attr_reader block: Block?
11
11
 
12
12
  attr_reader use_unicode: bool
13
- def self.new: (?ratio: Numeric?, ?percent: Numeric?, ?label: String?, ?style: Style?, ?gauge_style: Style?, ?block: Block?, ?use_unicode: bool) -> Gauge
13
+ def self.new: (?ratio: Numeric?, ?percent: Numeric?, ?label: String | Text::Span | nil, ?style: Style?, ?gauge_style: Style?, ?block: Block?, ?use_unicode: bool) -> Gauge
14
14
  end
15
15
  end
@@ -4,13 +4,13 @@
4
4
  module RatatuiRuby
5
5
  class LineGauge < Data
6
6
  attr_reader ratio: Float
7
- attr_reader label: String?
7
+ attr_reader label: (String | Text::Span)?
8
8
  attr_reader filled_style: Style?
9
9
  attr_reader unfilled_style: Style?
10
10
  attr_reader block: Block?
11
11
  attr_reader filled_symbol: String
12
12
  attr_reader unfilled_symbol: String
13
13
 
14
- def self.new: (?ratio: Numeric, ?label: String?, ?style: Style?, ?filled_style: Style?, ?unfilled_style: Style?, ?block: Block?, ?filled_symbol: String, ?unfilled_symbol: String) -> LineGauge
14
+ def self.new: (?ratio: Numeric, ?label: String | Text::Span | nil, ?style: Style?, ?filled_style: Style?, ?unfilled_style: Style?, ?block: Block?, ?filled_symbol: String, ?unfilled_symbol: String) -> LineGauge
15
15
  end
16
16
  end
@@ -3,8 +3,9 @@
3
3
 
4
4
  module RatatuiRuby
5
5
  class List < Data
6
- attr_reader items: Array[String]
6
+ attr_reader items: Array[String | Text::Span | Text::Line | ListItem]
7
7
  attr_reader selected_index: Integer?
8
+ attr_reader offset: Integer?
8
9
  attr_reader style: Style?
9
10
  attr_reader highlight_style: Style?
10
11
  attr_reader highlight_symbol: String?
@@ -13,6 +14,7 @@ module RatatuiRuby
13
14
  attr_reader direction: Symbol
14
15
  attr_reader scroll_padding: Integer?
15
16
  attr_reader block: Block?
16
- def self.new: (?items: Array[String], ?selected_index: Numeric?, ?style: Style?, ?highlight_style: Style?, ?highlight_symbol: String?, ?repeat_highlight_symbol: bool, ?highlight_spacing: Symbol, ?direction: Symbol, ?scroll_padding: Numeric?, ?block: Block?) -> List
17
+ def self.new: (?items: Array[String | Text::Span | Text::Line | ListItem], ?selected_index: Numeric?, ?offset: Numeric?, ?style: Style?, ?highlight_style: Style?, ?highlight_symbol: String?, ?repeat_highlight_symbol: bool, ?highlight_spacing: Symbol, ?direction: Symbol, ?scroll_padding: Numeric?, ?block: Block?) -> List
17
18
  end
18
19
  end
20
+
@@ -0,0 +1,10 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ module RatatuiRuby
5
+ class ListItem < Data
6
+ attr_reader content: String | Text::Span | Text::Line
7
+ attr_reader style: Style?
8
+ def self.new: (content: String | Text::Span | Text::Line, ?style: Style?) -> ListItem
9
+ end
10
+ end
@@ -11,5 +11,8 @@ module RatatuiRuby
11
11
  attr_reader height: Integer
12
12
  def self.new: (?x: Numeric, ?y: Numeric, ?width: Numeric, ?height: Numeric) -> instance
13
13
  def contains?: (Integer px, Integer py) -> bool
14
+ def intersects?: (Rect other) -> bool
15
+ def intersection: (Rect other) -> Rect?
14
16
  end
15
17
  end
18
+
@@ -3,11 +3,11 @@
3
3
 
4
4
  module RatatuiRuby
5
5
  class Style < Data
6
- attr_reader fg: (String | Symbol)?
7
- attr_reader bg: (String | Symbol)?
6
+ attr_reader fg: (String | Symbol | Integer)?
7
+ attr_reader bg: (String | Symbol | Integer)?
8
8
  attr_reader modifiers: Array[Symbol]
9
9
 
10
- def self.new: (?fg: (String | Symbol)?, ?bg: (String | Symbol)?, ?modifiers: Array[Symbol]) -> Style
10
+ def self.new: (?fg: (String | Symbol | Integer)?, ?bg: (String | Symbol | Integer)?, ?modifiers: Array[Symbol]) -> Style
11
11
  def self.default: () -> Style
12
12
  end
13
13
  end
@@ -14,7 +14,9 @@ module RatatuiRuby
14
14
  attr_reader highlight_spacing: Symbol
15
15
  attr_reader column_highlight_style: Style?
16
16
  attr_reader selected_column: Integer?
17
+ attr_reader offset: Integer?
17
18
 
18
- def self.new: (?header: Array[String | Paragraph]?, ?rows: Array[Array[String | Paragraph | Style | _ToS]], ?widths: Array[Constraint], ?highlight_style: Style?, ?highlight_symbol: String, ?highlight_spacing: Symbol, ?column_highlight_style: Style?, ?cell_highlight_style: Style?, ?selected_row: Numeric?, ?selected_column: Numeric?, ?block: Block?, ?footer: Array[String | Paragraph]?, ?flex: Symbol, ?style: (Style | Hash[Symbol, untyped])?, ?column_spacing: Numeric) -> Table
19
+ def self.new: (?header: Array[String | Paragraph]?, ?rows: Array[Array[String | Paragraph | Style | _ToS]], ?widths: Array[Constraint], ?highlight_style: Style?, ?highlight_symbol: String, ?highlight_spacing: Symbol, ?column_highlight_style: Style?, ?cell_highlight_style: Style?, ?selected_row: Numeric?, ?selected_column: Numeric?, ?offset: Numeric?, ?block: Block?, ?footer: Array[String | Paragraph]?, ?flex: Symbol, ?style: (Style | Hash[Symbol, untyped])?, ?column_spacing: Numeric) -> Table
19
20
  end
20
21
  end
22
+
@@ -5,18 +5,20 @@ module RatatuiRuby
5
5
  module Text
6
6
  class Span < Data
7
7
  attr_reader content: String
8
- attr_reader style: Style?
8
+ attr_reader style: Style | nil
9
9
 
10
- def self.new: (content: String, ?style: Style?) -> Span
11
- def self.styled: (String, ?Style?) -> Span
10
+ def initialize: (content: String, style: Style | nil) -> void
11
+ def self.styled: (String, Style | nil) -> Span
12
12
  end
13
13
 
14
14
  class Line < Data
15
15
  attr_reader spans: Array[Span]
16
- attr_reader alignment: (:left | :center | :right)?
16
+ attr_reader alignment: Symbol | nil
17
17
 
18
- def self.new: (?spans: Array[Span], ?alignment: (:left | :center | :right)?) -> Line
19
- def self.from_string: (String, ?alignment: (:left | :center | :right)?) -> Line
18
+ def initialize: (spans: Array[Span], alignment: Symbol | nil) -> void
19
+ def self.from_string: (String, alignment: Symbol | nil) -> Line
20
20
  end
21
+
22
+ def self.width: (String) -> Integer
21
23
  end
22
24
  end
@@ -0,0 +1,18 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ module RatatuiRuby
5
+ class ScrollbarState
6
+ def self.new: (Integer content_length) -> ScrollbarState
7
+ def position: () -> Integer
8
+ def position=: (Integer value) -> Integer
9
+ def content_length: () -> Integer
10
+ def content_length=: (Integer value) -> Integer
11
+ def viewport_content_length: () -> Integer
12
+ def viewport_content_length=: (Integer value) -> Integer
13
+ def first: () -> nil
14
+ def last: () -> nil
15
+ def next: () -> nil
16
+ def prev: () -> nil
17
+ end
18
+ end
@@ -13,6 +13,7 @@ module RatatuiRuby
13
13
  def _paragraph_line_count: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
14
14
  def _paragraph_line_width: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
15
15
  def _tabs_width: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
16
+ def _text_width: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
16
17
  def clear_events: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
17
18
  def draw: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
18
19
  def experimental_warnings: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
@@ -52,9 +53,13 @@ module RatatuiRuby
52
53
  def constraint_ratio: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
53
54
  def cursor: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
54
55
  def dataset: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
56
+ def draw_cell: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
57
+ def draw_string: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
55
58
  def draw_cell_cmd: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
56
59
  def draw_string_cmd: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
57
60
  def error: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
61
+ def error_safety: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
62
+ def error_terminal: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
58
63
  def event: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
59
64
  def event_focus_gained: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
60
65
  def event_focus_lost: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
@@ -70,6 +75,9 @@ module RatatuiRuby
70
75
  def line_chart: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
71
76
  def line_gauge: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
72
77
  def list: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
78
+ def list_item: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
79
+ def list_state: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
80
+ def list_state_new: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
73
81
  def overlay: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
74
82
  def paragraph: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
75
83
  def paragraph_new: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
@@ -77,6 +85,8 @@ module RatatuiRuby
77
85
  def ratatui_mascot: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
78
86
  def rect: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
79
87
  def scrollbar: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
88
+ def scrollbar_state: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
89
+ def scrollbar_state_new: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
80
90
  def shape_circle: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
81
91
  def shape_label: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
82
92
  def shape_line: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
@@ -87,7 +97,10 @@ module RatatuiRuby
87
97
  def style: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
88
98
  def style_default: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
89
99
  def table: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
100
+ def table_state: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
101
+ def table_state_new: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
90
102
  def tabs: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
103
+ def text_width: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
91
104
  def text_line: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
92
105
  def text_span: (*untyped args, **untyped kwargs) ?{ (*untyped) -> untyped } -> untyped
93
106
  end
@@ -0,0 +1,15 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ module RatatuiRuby
5
+ class TableState
6
+ def self.new: (?Integer? selected) -> TableState
7
+ def select: (Integer? index) -> nil
8
+ def selected: () -> Integer?
9
+ def select_column: (Integer? index) -> nil
10
+ def selected_column: () -> Integer?
11
+ def offset: () -> Integer
12
+ def scroll_down_by: (Integer n) -> nil
13
+ def scroll_up_by: (Integer n) -> nil
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ module RatatuiRuby
5
+ module TestHelper
6
+ module EventInjection
7
+ def inject_event: (Event::Base event) -> void
8
+ def inject_mouse: (x: Integer, y: Integer, ?kind: Symbol, ?modifiers: Array[String], ?button: Symbol) -> void
9
+ def inject_click: (x: Integer, y: Integer, ?modifiers: Array[String]) -> void
10
+ def inject_right_click: (x: Integer, y: Integer, ?modifiers: Array[String]) -> void
11
+ def inject_drag: (x: Integer, y: Integer, ?modifiers: Array[String], ?button: Symbol) -> void
12
+ def inject_keys: (*(String | Symbol | (Hash[Symbol, untyped]) | Event::Key) args) -> void
13
+ alias inject_key inject_keys
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ module RatatuiRuby
5
+ module TestHelper
6
+ module Snapshot
7
+ def assert_snapshot: (String name, ?String? msg) ?{ (Array[String]) -> Array[String] } -> void
8
+ def assert_screen_matches: (String | Array[String] expected, ?String? msg) ?{ (Array[String]) -> Array[String] } -> void
9
+ def assert_rich_snapshot: (String name, ?String? msg) ?{ (Array[String]) -> Array[String] } -> void
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,64 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ module RatatuiRuby
5
+ module TestHelper
6
+ module StyleAssertions
7
+ def assert_cell_style: (Integer x, Integer y, **untyped expected_attributes) -> void
8
+ def assert_color: (Symbol | Integer | String expected, x: Integer, y: Integer, ?layer: Symbol) -> void
9
+ def assert_area_style: (Rect | Hash[Symbol, Integer] area, **untyped attributes) -> void
10
+ def assert_fg_color: (Symbol | Integer | String expected, Integer x, Integer y) -> void
11
+ alias assert_fg assert_fg_color
12
+ def assert_bg_color: (Symbol | Integer | String expected, Integer x, Integer y) -> void
13
+ alias assert_bg assert_bg_color
14
+ def assert_bold: (Integer x, Integer y) -> void
15
+ def assert_italic: (Integer x, Integer y) -> void
16
+ def assert_underlined: (Integer x, Integer y) -> void
17
+ alias assert_underline assert_underlined
18
+ def assert_dim: (Integer x, Integer y) -> void
19
+ def assert_reversed: (Integer x, Integer y) -> void
20
+ alias assert_inverse assert_reversed
21
+ alias assert_inverse_video assert_reversed
22
+ def assert_crossed_out: (Integer x, Integer y) -> void
23
+ alias assert_strikethrough assert_crossed_out
24
+ alias assert_strike assert_crossed_out
25
+ def assert_hidden: (Integer x, Integer y) -> void
26
+ def assert_slow_blink: (Integer x, Integer y) -> void
27
+ alias assert_blink assert_slow_blink
28
+ def assert_rapid_blink: (Integer x, Integer y) -> void
29
+
30
+ def assert_black: (Integer x, Integer y) -> void
31
+ def assert_bg_black: (Integer x, Integer y) -> void
32
+ def assert_red: (Integer x, Integer y) -> void
33
+ def assert_bg_red: (Integer x, Integer y) -> void
34
+ def assert_green: (Integer x, Integer y) -> void
35
+ def assert_bg_green: (Integer x, Integer y) -> void
36
+ def assert_yellow: (Integer x, Integer y) -> void
37
+ def assert_bg_yellow: (Integer x, Integer y) -> void
38
+ def assert_blue: (Integer x, Integer y) -> void
39
+ def assert_bg_blue: (Integer x, Integer y) -> void
40
+ def assert_magenta: (Integer x, Integer y) -> void
41
+ def assert_bg_magenta: (Integer x, Integer y) -> void
42
+ def assert_cyan: (Integer x, Integer y) -> void
43
+ def assert_bg_cyan: (Integer x, Integer y) -> void
44
+ def assert_gray: (Integer x, Integer y) -> void
45
+ def assert_bg_gray: (Integer x, Integer y) -> void
46
+ def assert_dark_gray: (Integer x, Integer y) -> void
47
+ def assert_bg_dark_gray: (Integer x, Integer y) -> void
48
+ def assert_light_red: (Integer x, Integer y) -> void
49
+ def assert_bg_light_red: (Integer x, Integer y) -> void
50
+ def assert_light_green: (Integer x, Integer y) -> void
51
+ def assert_bg_light_green: (Integer x, Integer y) -> void
52
+ def assert_light_yellow: (Integer x, Integer y) -> void
53
+ def assert_bg_light_yellow: (Integer x, Integer y) -> void
54
+ def assert_light_blue: (Integer x, Integer y) -> void
55
+ def assert_bg_light_blue: (Integer x, Integer y) -> void
56
+ def assert_light_magenta: (Integer x, Integer y) -> void
57
+ def assert_bg_light_magenta: (Integer x, Integer y) -> void
58
+ def assert_light_cyan: (Integer x, Integer y) -> void
59
+ def assert_bg_light_cyan: (Integer x, Integer y) -> void
60
+ def assert_white: (Integer x, Integer y) -> void
61
+ def assert_bg_white: (Integer x, Integer y) -> void
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,14 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ module RatatuiRuby
5
+ module TestHelper
6
+ module Terminal
7
+ def with_test_terminal: (?Integer width, ?Integer height, ?timeout: Integer?) ?{ () -> void } -> void
8
+ def buffer_content: () -> Array[String]
9
+ def cursor_position: () -> { x: Integer, y: Integer }
10
+ def get_cell: (Integer x, Integer y) -> Cell
11
+ def print_buffer: () -> void
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ module RatatuiRuby
5
+ module TestHelper
6
+ module TestDoubles
7
+ class MockFrame < Data
8
+ attr_accessor rendered_widgets: Array[{ widget: untyped, area: Rect }]
9
+ def self.new: (?rendered_widgets: Array[{ widget: untyped, area: Rect }]) -> MockFrame
10
+ def render_widget: (untyped widget, Rect area) -> void
11
+ end
12
+
13
+ class StubRect < Data
14
+ attr_accessor x: Integer
15
+ attr_accessor y: Integer
16
+ attr_accessor width: Integer
17
+ attr_accessor height: Integer
18
+ def self.new: (?x: Integer, ?y: Integer, ?width: Integer, ?height: Integer) -> StubRect
19
+ end
20
+ end
21
+ end
22
+ end
@@ -3,9 +3,10 @@
3
3
 
4
4
  module RatatuiRuby
5
5
  module TestHelper
6
- def with_test_terminal: (?Integer width, ?Integer height) { () -> void } -> void
7
- def buffer_content: () -> Array[String]
8
- def cursor_position: () -> { x: Integer, y: Integer }
9
- def inject_event: (String event_type, Hash[Symbol, untyped] data) -> void
6
+ include Terminal
7
+ include Snapshot
8
+ include EventInjection
9
+ include StyleAssertions
10
+ include TestDoubles
10
11
  end
11
12
  end
@@ -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
@@ -32,14 +32,16 @@ module Autodoc
32
32
  if const.is_a?(Class)
33
33
  snake_name = Name.new(const_name).snake
34
34
  members << Member::Factory.new(name: snake_name, const_name:)
35
+ end
35
36
 
36
- const.singleton_methods(false).sort.each do |class_method|
37
- members << Member::Helper.new(
38
- name: "#{snake_name}_#{class_method}",
39
- class_method:,
40
- const_name:
41
- )
42
- end
37
+ # Singleton methods (for both Classes and Modules)
38
+ parent_prefix = Name.new(const_name).snake
39
+ const.singleton_methods(false).sort.each do |class_method|
40
+ members << Member::Helper.new(
41
+ name: "#{parent_prefix}_#{class_method}",
42
+ class_method:,
43
+ const_name:
44
+ )
43
45
  end
44
46
 
45
47
  const.constants.sort.each do |child_name|
data/tasks/autodoc.rake CHANGED
@@ -7,15 +7,16 @@ require_relative "autodoc/inventory"
7
7
  require_relative "autodoc/notice"
8
8
  require_relative "autodoc/rbs"
9
9
  require_relative "autodoc/rdoc"
10
+ require_relative "autodoc/examples"
10
11
 
11
12
  namespace :autodoc do
12
- desc "Generate all autodoc findings"
13
- task all: [:rbs, :rdoc]
13
+ desc "Update all automatically generated documentation"
14
+ task all: [:rbs, :rdoc, :examples]
14
15
 
15
- desc "Generate all RBS findings"
16
+ desc "Update all automatically generated RBS signatures"
16
17
  task rbs: ["rbs:session"]
17
18
 
18
- desc "Generate all RDoc findings"
19
+ desc "Update all automatically generated RDoc files"
19
20
  task rdoc: ["rdoc:session"]
20
21
 
21
22
  namespace :rbs do
@@ -41,7 +42,12 @@ namespace :autodoc do
41
42
  ).write(Autodoc::Inventory.new)
42
43
  end
43
44
  end
45
+
46
+ desc "Sync code snippets in example READMEs with source files"
47
+ task :examples do
48
+ Autodoc::Examples.sync
49
+ end
44
50
  end
45
51
 
46
- desc "Generate all autodoc findings"
52
+ desc "Update all automatically generated documentation"
47
53
  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}")