ratatui_ruby 0.10.3 → 1.0.0.pre.beta.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45627e7b77b76ba56a7cb5bb3b0cb578d282a26782c6702abdc87893d89e14a9
4
- data.tar.gz: b401cd48eb576970a74c8efc7bf22dba20a9bbd4d4378ff85b4a3f45797a3e31
3
+ metadata.gz: c251e31a8aea8d5e84bc0969f8f6c95f690a2ce94d0cbd3538856bfbdafd055a
4
+ data.tar.gz: ee38d86ebdc50612ba5a32990e7f1c3757e9f35ced6d621fc92203836c938793
5
5
  SHA512:
6
- metadata.gz: 8ef1ed4f7c6c35df19e7955fd9fc752e5b73da316380fe21c9927b3f5c5fbc3d959948821b0c795572edf58cd43627be65922452f93dfa51dd54f93e1a2b164b
7
- data.tar.gz: 13c4cb8c1de48f10920128527222f4ae94db9ac98f0ec1b3b975b3edd0a221859f352a2ddc570b0d955f566a393341413ea2e0ed9ea4e1e12f596973bf657723
6
+ metadata.gz: 4f555dd2eddeef420df2512c53e26a136a791cfabd72bfe74ac1c5bd3f652d317bc9c60d47a78611f3a20e76ae72925494d867fcd992a17f0dcd29b3bb18b33d
7
+ data.tar.gz: 47d21c6a4e5969a5457705eb088efaa45dd29ee5d6fac1f67166f3a4c2114aa65d54b926b05191fcf8f2a20b8a1c6481d19ffed195f0e2ee045bfaf561589dc5
data/.builds/ruby-3.2.yml CHANGED
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.10.3.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-1.0.0.pre.beta.1.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
data/.builds/ruby-3.3.yml CHANGED
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.10.3.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-1.0.0.pre.beta.1.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
data/.builds/ruby-3.4.yml CHANGED
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.10.3.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-1.0.0.pre.beta.1.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
@@ -16,7 +16,7 @@ packages:
16
16
  - clang
17
17
  - git
18
18
  artifacts:
19
- - ratatui_ruby/pkg/ratatui_ruby-0.10.3.gem
19
+ - ratatui_ruby/pkg/ratatui_ruby-1.0.0.pre.beta.1.gem
20
20
  sources:
21
21
  - https://git.sr.ht/~kerrick/ratatui_ruby
22
22
  tasks:
data/CHANGELOG.md CHANGED
@@ -18,6 +18,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
18
18
 
19
19
  ### Removed
20
20
 
21
+ ## [1.0.0-beta.1] - 2026-01-20
22
+ This release is functionally equivalent to v0.10.3. The version bump signals the beginning of the 1.0 release series.
23
+
21
24
  ## [0.10.3] - 2026-01-16
22
25
 
23
26
  ### Added
@@ -667,6 +670,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
667
670
  - **Testing Support**: Included `RatatuiRuby::TestHelper` and RSpec integration to make testing your TUI applications possible.
668
671
 
669
672
  [Unreleased]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/HEAD
673
+ [1.0.0-beta.1]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v1.0.0-beta.1
670
674
  [0.10.3]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.10.3
671
675
  [0.10.2]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.10.2
672
676
  [0.10.1]: https://git.sr.ht/~kerrick/ratatui_ruby/refs/v0.10.1
data/README.rdoc ADDED
@@ -0,0 +1,302 @@
1
+
2
+ == Terminal UIs, the Ruby Way
3
+
4
+ RatatuiRuby[https://rubygems.org/gems/ratatui_ruby] is a RubyGem built on
5
+ Ratatui[https://ratatui.rs], a leading TUI library written in
6
+ Rust[https://rust-lang.org]. You get native performance with the joy of Ruby.
7
+
8
+ gem install ratatui_ruby
9
+
10
+ {rdoc-image:https://ratatui-ruby.dev/hero.gif}[https://www.ratatui-ruby.dev/docs/v0.10/examples/app_cli_rich_moments/README_md.html]
11
+
12
+ === Rich Moments
13
+
14
+ Add a spinner, a progress bar, or an inline menu to your CLI script. No
15
+ full-screen takeover. Your terminal history stays intact.
16
+
17
+ ==== Inline Viewports
18
+
19
+ Standard TUIs erase themselves on exit. Your carefully formatted CLI output
20
+ disappears. Users lose their scrollback.
21
+
22
+ <b>Inline viewports</b> solve this. They occupy a fixed number of lines, render
23
+ rich UI, then leave the output in place when done.
24
+
25
+ Perfect for spinners, menus, progress indicators—any brief moment of richness.
26
+
27
+ require "ratatui_ruby"
28
+
29
+ RatatuiRuby.run(viewport: :inline, height: 1) do |tui|
30
+ until connected?
31
+ status = tui.paragraph(text: "\#{spin} Connecting...")
32
+ tui.draw { |frame| frame.render_widget(status, frame.area) }
33
+ end
34
+ end
35
+
36
+ === Build Something Real
37
+
38
+ Full-screen applications with {keyboard and mouse input}[https://www.ratatui-ruby.dev/docs/v0.10/examples/app_all_events/README_md.html]. The managed loop
39
+ sets up the terminal and restores it on exit, even after crashes.
40
+
41
+ RatatuiRuby.run do |tui|
42
+ loop do
43
+ tui.draw do |frame|
44
+ frame.render_widget(
45
+ tui.paragraph(text: "Hello, RatatuiRuby!", alignment: :center),
46
+ frame.area
47
+ )
48
+ end
49
+
50
+ case tui.poll_event
51
+ in { type: :key, code: "q" } then break
52
+ else nil
53
+ end
54
+ end
55
+ end
56
+
57
+ ==== Widgets included:
58
+
59
+ [Layout]
60
+ {Block}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_block/README_md.html],
61
+ {Center}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_center/README_md.html],
62
+ {Clear (Popup, Modal)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_popup/README_md.html],
63
+ {Layout (Split, Grid)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_layout_split/README_md.html],
64
+ {Overlay}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_overlay/README_md.html]
65
+ [Data]
66
+ {Bar Chart}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_barchart/README_md.html],
67
+ {Chart}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_chart/README_md.html],
68
+ {Gauge}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_gauge/README_md.html],
69
+ {Line Gauge}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_line_gauge/README_md.html],
70
+ {Sparkline}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_sparkline/README_md.html],
71
+ {Table}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_table/README_md.html]
72
+ [Text]
73
+ {Cell}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_cell/README_md.html],
74
+ {List}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_list/README_md.html],
75
+ {Rich Text (Line, Span)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_rich_text/README_md.html],
76
+ {Scrollbar (Scroll)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_scrollbar/README_md.html],
77
+ {Tabs}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_tabs/README_md.html]
78
+ [Graphics]
79
+ {Calendar}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_calendar/README_md.html],
80
+ {Canvas}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_canvas/README_md.html],
81
+ {Map (World Map)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_map/README_md.html]
82
+
83
+ Need something else? {Build custom widgets}[https://www.ratatui-ruby.dev/docs/v0.10/doc/concepts/custom_widgets_md.html] in Ruby!
84
+
85
+
86
+ ---
87
+
88
+ === Testing Built In
89
+
90
+ TUI testing is tedious. You need a headless terminal, event injection,
91
+ snapshot comparisons, and style assertions. RatatuiRuby bundles all of it.
92
+
93
+ require "ratatui_ruby/test_helper"
94
+
95
+ class TestColorPicker < Minitest::Test
96
+ include RatatuiRuby::TestHelper
97
+
98
+ def test_swatch_widget
99
+ with_test_terminal(10, 3) do
100
+ RatatuiRuby.draw do |frame|
101
+ frame.render_widget(Swatch.new(:red), frame.area)
102
+ end
103
+ assert_cell_style 2, 1, char: "█", bg: :red
104
+ end
105
+ end
106
+ end
107
+
108
+ ==== What's inside:
109
+
110
+ - <b>Headless terminal</b> — No real TTY needed
111
+ - <b>Snapshots</b> — Plain text and rich (ANSI colors)
112
+ - <b>Event injection</b> — Keys, mouse, paste, resize
113
+ - <b>Style assertions</b> — Color, bold, underline at any cell
114
+ - <b>Test doubles</b> — Mock frames and stub rects
115
+ - <b>UPDATE_SNAPSHOTS=1</b> — Regenerate baselines in one command
116
+
117
+
118
+ ---
119
+
120
+ ==== Inline Menu Example
121
+
122
+ require "ratatui_ruby"
123
+
124
+ # This example renders an inline menu. Arrow keys select, enter confirms.
125
+ # The menu appears in-place, preserving scrollback. When the user chooses,
126
+ # the TUI closes and the script continues with the selected value.
127
+ class RadioMenu
128
+ CHOICES = ["Production", "Staging", "Development"] # ASCII strings are universally supported.
129
+ PREFIXES = { active: "●", inactive: "○" } # Some terminals may not support Unicode.
130
+ CONTROLS = "↑/↓: Select | Enter: Choose | Ctrl+C: Cancel" # Let users know what keys you handle.
131
+ TITLES = ["Select Environment", # The default title position is top left.
132
+ { content: CONTROLS, # Multiple titles can save space.
133
+ position: :bottom, # Titles go on the top or bottom,
134
+ alignment: :right }] # aligned left, right, or center
135
+
136
+ def call # This method blocks until a choice is made.
137
+ RatatuiRuby.run(viewport: :inline, height: 5) do |tui| # RatauiRuby.run manages the terminal.
138
+ @tui = tui # The TUI instance is safe to store.
139
+ show_menu until chosen? # You can use any loop keyword you like.
140
+ end # `run` won't return until your block does,
141
+ RadioMenu::CHOICES[@choice] # so you can use it synchronously.
142
+ end
143
+ # Classes like RadioMenu are convenient for
144
+ private # CLI authors to offer "rich moments."
145
+
146
+ def show_menu = @tui.draw do |frame| # RatatuiRuby gives you low-level access.
147
+ widget = @tui.paragraph( # But the TUI facade makes it easy to use.
148
+ text: menu_items, # Text can be spans, lines, or paragraphs.
149
+ block: @tui.block(borders: :all, titles: TITLES) # Blocks give you boxes and titles, and hold
150
+ ) # one or more widgets. We only use one here,
151
+ frame.render_widget(widget, frame.area) # but "area" lets you compose sub-views.
152
+ end
153
+
154
+ def chosen? # You are responsible for handling input.
155
+ interaction = @tui.poll_event # Every frame, you receive an event object:
156
+ return choose if interaction.enter? # Key, Mouse, Resize, Paste, FocusGained,
157
+ # FocusLost, or None objects. They come with
158
+ move_by(-1) if interaction.up? # predicates, support pattern matching, and
159
+ move_by(1) if interaction.down? # can be inspected for properties directly.
160
+ quit! if interaction.ctrl_c? # Your application must handle every input,
161
+ false # even interrupts and other exit patterns.
162
+ end
163
+
164
+ def choose # Here, the loop is about to exit, and the
165
+ prepare_next_line # block will return. The inline viewport
166
+ @choice # will be torn down and the terminal will
167
+ end # be restored, but you are responsible for
168
+ # positioning the cursor.
169
+ def prepare_next_line # To ensure the next output is on a new
170
+ area = @tui.viewport_area # line, query the viewport area and move
171
+ RatatuiRuby.cursor_position = [0, area.y + area.height] # the cursor to the start of the last line.
172
+ puts # Then print a newline.
173
+ end
174
+
175
+ def quit! # All of your familiar Ruby control flow
176
+ prepare_next_line # keywords work as expected, so we can
177
+ exit 0 # use them to leave the TUI.
178
+ end
179
+
180
+ def move_by(line_count) # You are in full control of your UX, so
181
+ @choice = (@choice + line_count) % CHOICES.size # you can implement any logic you need:
182
+ end # Would you "wrap around" here, or not?
183
+ #
184
+ def menu_items = CHOICES.map.with_index do |choice, i| # Notably, RatatuiRuby has no concept of
185
+ "\#{prefix_for(i)} \#{choice}" # "menus" or "radio buttons". You are in
186
+ end # full control, but it also means you must
187
+ def prefix_for(choice_index) # implement the logic yourself. For larger
188
+ return PREFIXES[:active] if choice_index == @choice # applications, consider using Rooibos,
189
+ PREFIXES[:inactive] # an MVU framework built with RatatuiRuby.
190
+ end # Or, use the upcoming ratatui-ruby-kit,
191
+ # our object-oriented component library.
192
+ def initialize = @choice = 0 # However, those are both optional, and
193
+ end # designed for full-screen Terminal UIs.
194
+ # RatatuiRuby will always give you the most
195
+ choice = RadioMenu.new.call # control, and is enough for "rich CLI
196
+ puts "You chose \#{choice}!" # moments" like this one.
197
+
198
+ ---
199
+
200
+ === Full App Solutions
201
+
202
+ RatatuiRuby renders. For complex applications, add a framework that manages
203
+ state and composition.
204
+
205
+ ==== Rooibos[https://git.sr.ht/~kerrick/rooibos] (Framework)
206
+
207
+ Model-View-Update architecture. Inspired by Elm, Bubble Tea, and React +
208
+ Redux. Your UI is a pure function of state.
209
+
210
+ - Functional programming with MVU
211
+ - Commands work off the main thread
212
+ - Messages, not callbacks, drive updates
213
+
214
+ ==== {Kit}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-3-the-object-path--kit] (Coming Soon)
215
+
216
+ Component-based architecture. Encapsulate state, input handling, and
217
+ rendering in reusable pieces.
218
+
219
+ - OOP with stateful components
220
+ - Separate UI state from domain logic
221
+ - Built-in focus management & click handling
222
+
223
+ Both use the same widget library and rendering engine. Pick the paradigm
224
+ that fits your brain.
225
+
226
+
227
+ ---
228
+
229
+ === Why RatatuiRuby?
230
+
231
+ Ruby deserves world-class terminal user interfaces. TUI developers deserve
232
+ a world-class language.
233
+
234
+ RatatuiRuby wraps Rust's Ratatui via native extension. The Rust library
235
+ handles rendering. Your Ruby code handles design.
236
+
237
+ >>>
238
+ "Text UIs are seeing a renaissance with many new TUI libraries popping up.
239
+ The Ratatui bindings have proven to be full featured and stable."
240
+
241
+ — {Mike Perham}[https://www.mikeperham.com/], creator of
242
+ Sidekiq[https://sidekiq.org/] and Faktory[https://contribsys.com/faktory/]
243
+
244
+ ==== Why Rust? Why Ruby?
245
+
246
+ Rust excels at low-level rendering. Ruby excels at expressing domain logic
247
+ and UI. RatatuiRuby puts each language where it performs best.
248
+
249
+ ==== Versus CharmRuby
250
+
251
+ CharmRuby[https://charm-ruby.dev/] wraps Charm's Go libraries. Both projects
252
+ give Ruby developers TUI options.
253
+
254
+ [Integration]
255
+ CharmRuby: Two runtimes, one process.
256
+ RatatuiRuby: Native extension in Rust.
257
+ [Runtime]
258
+ CharmRuby: Go + Ruby (competing).
259
+ RatatuiRuby: Ruby (Rust has no runtime).
260
+ [Memory]
261
+ CharmRuby: Two uncoordinated GCs.
262
+ RatatuiRuby: One Garbage Collector.
263
+ [Style]
264
+ CharmRuby: The Elm Architecture (TEA).
265
+ RatatuiRuby: TEA, OOP, or Imperative.
266
+
267
+
268
+ ---
269
+
270
+ === Links
271
+
272
+ [Get Started]
273
+ {Quickstart}[https://www.ratatui-ruby.dev/docs/v0.10/doc/getting_started/quickstart_md.html],
274
+ {Examples}[https://www.ratatui-ruby.dev/docs/v0.10/examples/app_cli_rich_moments/README_md.html],
275
+ {API Reference}[https://www.ratatui-ruby.dev/docs/v0.10/],
276
+ {Guides}[https://www.ratatui-ruby.dev/docs/v0.10/doc/index_md.html]
277
+ [Ecosystem]
278
+ Rooibos[https://git.sr.ht/~kerrick/rooibos],
279
+ {Kit}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-3-the-object-path--kit] (Planned),
280
+ {Framework}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-5-the-framework] (Planned),
281
+ {UI Widgets}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-6-licensing] (Planned)
282
+ [Community]
283
+ {Discuss and Chat}[https://lists.sr.ht/~kerrick/ratatui_ruby-discuss],
284
+ {Announcements}[https://lists.sr.ht/~kerrick/ratatui_ruby-announce],
285
+ {Development}[https://lists.sr.ht/~kerrick/ratatui_ruby-devel],
286
+ {Bug Tracker}[https://todo.sr.ht/~kerrick/ratatui_ruby]
287
+ [Contribute]
288
+ {Contributing Guide}[https://man.sr.ht/~kerrick/ratatui_ruby/contributing.md],
289
+ {Code of Conduct}[https://man.sr.ht/~kerrick/ratatui_ruby/code_of_conduct.md],
290
+ {Project History}[https://man.sr.ht/~kerrick/ratatui_ruby/history/index.md],
291
+ {Pull Requests}[https://lists.sr.ht/~kerrick/ratatui_ruby-devel/patches]
292
+
293
+
294
+ ---
295
+
296
+ [Website] https://www.ratatui-ruby.dev
297
+ [Source] https://git.sr.ht/~kerrick/ratatui_ruby
298
+ [RubyGems] https://rubygems.org/gems/ratatui_ruby
299
+ [Upstream] https://ratatui.rs
300
+ [Build Status] https://builds.sr.ht/~kerrick/ratatui_ruby
301
+
302
+ © 2026 Kerrick Long · Library: LGPL-3.0-or-later · Website: CC-BY-NC-ND-4.0 · Snippets: MIT-0
data/REUSE.toml CHANGED
@@ -5,6 +5,11 @@ path = 'Gemfile.lock'
5
5
  SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
6
6
  SPDX-License-Identifier = "CC0-1.0"
7
7
 
8
+ [[annotations]]
9
+ path = 'README.rdoc'
10
+ SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
11
+ SPDX-License-Identifier = "CC-BY-SA-4.0"
12
+
8
13
 
9
14
  [[annotations]]
10
15
  path = 'ext/ratatui_ruby/Cargo.lock'
@@ -6,52 +6,84 @@
6
6
  # SPDX-License-Identifier: AGPL-3.0-or-later
7
7
  #++
8
8
 
9
- # Test 2: Inline menu example (fixed)
10
9
  require "ratatui_ruby"
11
10
 
12
- choices = ["Production", "Staging", "Development"]
13
- index = 0
14
-
15
- RatatuiRuby.run(viewport: :inline, height: 5) do |tui|
16
- loop do
17
- tui.draw do |frame|
18
- items = choices.map.with_index do |c, i|
19
- prefix = (i == index) ? "● " : "○ "
20
- "#{prefix}#{c}"
21
- end
22
- widget = tui.paragraph(
23
- text: items.join("\n"),
24
- block: tui.block(
25
- borders: :all,
26
- title: "Select Environment",
27
- titles: [{ content: "↑/↓ Enter | Ctrl+C: Cancel", position: :bottom, alignment: :right }]
28
- )
29
- )
30
- frame.render_widget(widget, frame.area)
31
- end
32
-
33
- case tui.poll_event
34
- in { type: :key, code: "up" }
35
- index = (index - 1) % choices.size
36
- in { type: :key, code: "down" }
37
- index = (index + 1) % choices.size
38
- in { type: :key, code: "enter" }
39
- area = tui.viewport_area
40
- RatatuiRuby.cursor_position = [0, area.y + area.height]
41
- break
42
- in { type: :key, code: "c", modifiers: ["ctrl"] }
43
- area = tui.viewport_area
44
- RatatuiRuby.cursor_position = [0, area.y + area.height]
45
- index = nil
46
- break
47
- else nil
48
- end
11
+ # This example renders an inline menu. Arrow keys select, enter confirms.
12
+ # The menu appears in-place, preserving scrollback. When the user chooses,
13
+ # the TUI closes and the script continues with the selected value.
14
+ class RadioMenu
15
+ CHOICES = ["Production", "Staging", "Development"] # ASCII strings are universally supported.
16
+ PREFIXES = { active: "●", inactive: "○" } # Some terminals may not support Unicode.
17
+ CONTROLS = "↑/↓: Select | Enter: Choose | Ctrl+C: Cancel" # Let users know what keys you handle.
18
+ TITLES = [
19
+ "Select Environment", # The default title position is top left.
20
+ {
21
+ content: CONTROLS, # Multiple titles can save space.
22
+ position: :bottom, # Titles go on the top or bottom,
23
+ alignment: :right,
24
+ },
25
+ ] # aligned left, right, or center
26
+
27
+ def call # This method blocks until a choice is made.
28
+ RatatuiRuby.run(viewport: :inline, height: 5) do |tui| # RatauiRuby.run manages the terminal.
29
+ @tui = tui # The TUI instance is safe to store.
30
+ show_menu until chosen? # You can use any loop keyword you like.
31
+ end # `run` won't return until your block does,
32
+ RadioMenu::CHOICES[@choice] # so you can use it synchronously.
33
+ end
34
+ # Classes like RadioMenu are convenient for
35
+
36
+ private def show_menu = @tui.draw do |frame| # RatatuiRuby gives you low-level access.
37
+ widget = @tui.paragraph( # But the TUI facade makes it easy to use.
38
+ text: menu_items, # Text can be spans, lines, or paragraphs.
39
+ block: @tui.block(borders: :all, titles: TITLES) # Blocks give you boxes and titles, and hold
40
+ ) # one or more widgets. We only use one here,
41
+ frame.render_widget(widget, frame.area) # but "area" lets you compose sub-views.
42
+ end
43
+
44
+ private def chosen? # You are responsible for handling input.
45
+ interaction = @tui.poll_event # Every frame, you receive an event object:
46
+ return choose if interaction.enter? # Key, Mouse, Resize, Paste, FocusGained,
47
+ # FocusLost, or None objects. They come with
48
+ move_by(-1) if interaction.up? # predicates, support pattern matching, and
49
+ move_by(1) if interaction.down? # can be inspected for properties directly.
50
+ quit! if interaction.ctrl_c? # Your application must handle every input,
51
+ false # even interrupts and other exit patterns.
52
+ end
53
+
54
+ private def choose # Here, the loop is about to exit, and the
55
+ prepare_next_line # block will return. The inline viewport
56
+ @choice # will be torn down and the terminal will
57
+ end # be restored, but you are responsible for
58
+
59
+ # positioning the cursor.
60
+ private def prepare_next_line # To ensure the next output is on a new
61
+ area = @tui.viewport_area # line, query the viewport area and move
62
+ RatatuiRuby.cursor_position = [0, area.y + area.height] # the cursor to the start of the last line.
63
+ puts # Then print a newline.
64
+ end
65
+
66
+ private def quit! # All of your familiar Ruby control flow
67
+ prepare_next_line # keywords work as expected, so we can
68
+ exit 0 # use them to leave the TUI.
49
69
  end
50
- end
51
-
52
- puts
53
- if index
54
- puts "Deploying to #{choices[index]}..."
55
- else
56
- puts "Cancelled."
57
- end
70
+
71
+ private def move_by(line_count) # You are in full control of your UX, so
72
+ @choice = (@choice + line_count) % CHOICES.size # you can implement any logic you need:
73
+ end # Would you "wrap around" here, or not?
74
+
75
+ private def menu_items = CHOICES.map.with_index do |choice, i| # Notably, RatatuiRuby has no concept of
76
+ "#{prefix_for(i)} #{choice}" # "menus" or "radio buttons". You are in
77
+ end # full control, but it also means you must
78
+
79
+ private def prefix_for(choice_index) # implement the logic yourself. For larger
80
+ return PREFIXES[:active] if choice_index == @choice # applications, consider using Rooibos,
81
+ PREFIXES[:inactive] # an MVU framework built with RatatuiRuby.
82
+ end # Or, use the upcoming ratatui-ruby-kit,
83
+
84
+ # our object-oriented component library.
85
+ private def initialize = @choice = 0 # However, those are both optional, and
86
+ end # designed for full-screen Terminal UIs.
87
+ # RatatuiRuby will always give you the most
88
+ choice = RadioMenu.new.call # control, and is enough for "rich CLI
89
+ puts "You chose #{choice}!" # moments" like this one.
@@ -21,13 +21,14 @@ class Spinner
21
21
  end
22
22
  end
23
23
 
24
- def ending(tui, message, color) = tui.draw do |frame|
25
- frame.render_widget(tui.paragraph(text: message, fg: color), frame.area)
24
+ def ending(tui, text, fg) = tui.draw do |frame|
25
+ frame.render_widget(tui.paragraph(text:, fg:), frame.area)
26
+ puts # Prepare a new line for the shell prompt
26
27
  end
27
28
 
28
- def initialize = (@frame, @finish = 0, Time.now + 1)
29
+ def initialize = (@frame, @finish = 0, Time.now + 2)
29
30
  def connected? = Time.now >= @finish # Simulate work
30
31
  def spin = SPINNER[(@frame += 1) % SPINNER.length]
31
32
  SPINNER = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏]
32
33
  end
33
- Spinner.new.main; puts
34
+ Spinner.new.main
@@ -1059,7 +1059,7 @@ dependencies = [
1059
1059
 
1060
1060
  [[package]]
1061
1061
  name = "ratatui_ruby"
1062
- version = "0.10.3"
1062
+ version = "1.0.0-beta.1"
1063
1063
  dependencies = [
1064
1064
  "bumpalo",
1065
1065
  "lazy_static",
@@ -3,7 +3,7 @@
3
3
 
4
4
  [package]
5
5
  name = "ratatui_ruby"
6
- version = "0.10.3"
6
+ version = "1.0.0-beta.1"
7
7
  edition = "2021"
8
8
 
9
9
  [lib]
@@ -8,5 +8,5 @@
8
8
  module RatatuiRuby
9
9
  # The version of the ratatui_ruby gem.
10
10
  # See https://semver.org/spec/v2.0.0.html
11
- VERSION = "0.10.3"
11
+ VERSION = "1.0.0-beta.1"
12
12
  end
@@ -11,12 +11,15 @@ class SemVer
11
11
 
12
12
  def self.parse(string)
13
13
  require "rubygems"
14
- segments = Gem::Version.new(string).segments.fill(0, 3).first(3)
15
- new(segments)
14
+ # Extract prerelease suffix (e.g., "-beta.1", "-alpha.2", "-rc.1")
15
+ base, prerelease = string.split("-", 2)
16
+ segments = Gem::Version.new(base).segments.fill(0, 3).first(3)
17
+ new(segments, prerelease:)
16
18
  end
17
19
 
18
- def initialize(segments)
20
+ def initialize(segments, prerelease: nil)
19
21
  @segments = segments
22
+ @prerelease = prerelease
20
23
  end
21
24
 
22
25
  def next(segment)
@@ -31,6 +34,7 @@ class SemVer
31
34
  end
32
35
 
33
36
  def to_s
34
- @segments.join(".")
37
+ base = @segments.join(".")
38
+ @prerelease ? "#{base}-#{@prerelease}" : base
35
39
  end
36
40
  end
data/tasks/sourcehut.rake CHANGED
@@ -25,7 +25,11 @@ namespace :sourcehut do
25
25
  version_content = File.read("lib/ratatui_ruby/version.rb")
26
26
  version = version_content.match(/VERSION = "(.+?)"/)[1]
27
27
 
28
- gem_filename = "#{spec.name}-#{version}.gem"
28
+ # Normalize version using Gem::Version - RubyGems converts hyphens
29
+ # (e.g., "1.0.0-beta.1" -> "1.0.0.pre.beta.1")
30
+ normalized_version = Gem::Version.new(version).to_s
31
+
32
+ gem_filename = "#{spec.name}-#{normalized_version}.gem"
29
33
 
30
34
  rubies = YAML.load_file("tasks/resources/rubies.yml")
31
35
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ratatui_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.3
4
+ version: 1.0.0.pre.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kerrick Long
@@ -93,8 +93,309 @@ dependencies:
93
93
  - - ">="
94
94
  - !ruby/object:Gem::Version
95
95
  version: '1.0'
96
- description: ratatui_ruby is a wrapper for the Ratatui Rust crate <https://ratatui.rs>.
97
- It allows you to cook up Terminal User Interfaces in Ruby.
96
+ description: |2-
97
+
98
+ == Terminal UIs, the Ruby Way
99
+
100
+ RatatuiRuby[https://rubygems.org/gems/ratatui_ruby] is a RubyGem built on
101
+ Ratatui[https://ratatui.rs], a leading TUI library written in
102
+ Rust[https://rust-lang.org]. You get native performance with the joy of Ruby.
103
+
104
+ gem install ratatui_ruby
105
+
106
+ {rdoc-image:https://ratatui-ruby.dev/hero.gif}[https://www.ratatui-ruby.dev/docs/v0.10/examples/app_cli_rich_moments/README_md.html]
107
+
108
+ === Rich Moments
109
+
110
+ Add a spinner, a progress bar, or an inline menu to your CLI script. No
111
+ full-screen takeover. Your terminal history stays intact.
112
+
113
+ ==== Inline Viewports
114
+
115
+ Standard TUIs erase themselves on exit. Your carefully formatted CLI output
116
+ disappears. Users lose their scrollback.
117
+
118
+ <b>Inline viewports</b> solve this. They occupy a fixed number of lines, render
119
+ rich UI, then leave the output in place when done.
120
+
121
+ Perfect for spinners, menus, progress indicators—any brief moment of richness.
122
+
123
+ require "ratatui_ruby"
124
+
125
+ RatatuiRuby.run(viewport: :inline, height: 1) do |tui|
126
+ until connected?
127
+ status = tui.paragraph(text: "\#{spin} Connecting...")
128
+ tui.draw { |frame| frame.render_widget(status, frame.area) }
129
+ end
130
+ end
131
+
132
+ === Build Something Real
133
+
134
+ Full-screen applications with {keyboard and mouse input}[https://www.ratatui-ruby.dev/docs/v0.10/examples/app_all_events/README_md.html]. The managed loop
135
+ sets up the terminal and restores it on exit, even after crashes.
136
+
137
+ RatatuiRuby.run do |tui|
138
+ loop do
139
+ tui.draw do |frame|
140
+ frame.render_widget(
141
+ tui.paragraph(text: "Hello, RatatuiRuby!", alignment: :center),
142
+ frame.area
143
+ )
144
+ end
145
+
146
+ case tui.poll_event
147
+ in { type: :key, code: "q" } then break
148
+ else nil
149
+ end
150
+ end
151
+ end
152
+
153
+ ==== Widgets included:
154
+
155
+ [Layout]
156
+ {Block}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_block/README_md.html],
157
+ {Center}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_center/README_md.html],
158
+ {Clear (Popup, Modal)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_popup/README_md.html],
159
+ {Layout (Split, Grid)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_layout_split/README_md.html],
160
+ {Overlay}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_overlay/README_md.html]
161
+ [Data]
162
+ {Bar Chart}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_barchart/README_md.html],
163
+ {Chart}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_chart/README_md.html],
164
+ {Gauge}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_gauge/README_md.html],
165
+ {Line Gauge}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_line_gauge/README_md.html],
166
+ {Sparkline}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_sparkline/README_md.html],
167
+ {Table}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_table/README_md.html]
168
+ [Text]
169
+ {Cell}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_cell/README_md.html],
170
+ {List}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_list/README_md.html],
171
+ {Rich Text (Line, Span)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_rich_text/README_md.html],
172
+ {Scrollbar (Scroll)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_scrollbar/README_md.html],
173
+ {Tabs}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_tabs/README_md.html]
174
+ [Graphics]
175
+ {Calendar}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_calendar/README_md.html],
176
+ {Canvas}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_canvas/README_md.html],
177
+ {Map (World Map)}[https://www.ratatui-ruby.dev/docs/v0.10/examples/widget_map/README_md.html]
178
+
179
+ Need something else? {Build custom widgets}[https://www.ratatui-ruby.dev/docs/v0.10/doc/concepts/custom_widgets_md.html] in Ruby!
180
+
181
+
182
+ ---
183
+
184
+ === Testing Built In
185
+
186
+ TUI testing is tedious. You need a headless terminal, event injection,
187
+ snapshot comparisons, and style assertions. RatatuiRuby bundles all of it.
188
+
189
+ require "ratatui_ruby/test_helper"
190
+
191
+ class TestColorPicker < Minitest::Test
192
+ include RatatuiRuby::TestHelper
193
+
194
+ def test_swatch_widget
195
+ with_test_terminal(10, 3) do
196
+ RatatuiRuby.draw do |frame|
197
+ frame.render_widget(Swatch.new(:red), frame.area)
198
+ end
199
+ assert_cell_style 2, 1, char: "█", bg: :red
200
+ end
201
+ end
202
+ end
203
+
204
+ ==== What's inside:
205
+
206
+ - <b>Headless terminal</b> — No real TTY needed
207
+ - <b>Snapshots</b> — Plain text and rich (ANSI colors)
208
+ - <b>Event injection</b> — Keys, mouse, paste, resize
209
+ - <b>Style assertions</b> — Color, bold, underline at any cell
210
+ - <b>Test doubles</b> — Mock frames and stub rects
211
+ - <b>UPDATE_SNAPSHOTS=1</b> — Regenerate baselines in one command
212
+
213
+
214
+ ---
215
+
216
+ ==== Inline Menu Example
217
+
218
+ require "ratatui_ruby"
219
+
220
+ # This example renders an inline menu. Arrow keys select, enter confirms.
221
+ # The menu appears in-place, preserving scrollback. When the user chooses,
222
+ # the TUI closes and the script continues with the selected value.
223
+ class RadioMenu
224
+ CHOICES = ["Production", "Staging", "Development"] # ASCII strings are universally supported.
225
+ PREFIXES = { active: "●", inactive: "○" } # Some terminals may not support Unicode.
226
+ CONTROLS = "↑/↓: Select | Enter: Choose | Ctrl+C: Cancel" # Let users know what keys you handle.
227
+ TITLES = ["Select Environment", # The default title position is top left.
228
+ { content: CONTROLS, # Multiple titles can save space.
229
+ position: :bottom, # Titles go on the top or bottom,
230
+ alignment: :right }] # aligned left, right, or center
231
+
232
+ def call # This method blocks until a choice is made.
233
+ RatatuiRuby.run(viewport: :inline, height: 5) do |tui| # RatauiRuby.run manages the terminal.
234
+ @tui = tui # The TUI instance is safe to store.
235
+ show_menu until chosen? # You can use any loop keyword you like.
236
+ end # `run` won't return until your block does,
237
+ RadioMenu::CHOICES[@choice] # so you can use it synchronously.
238
+ end
239
+ # Classes like RadioMenu are convenient for
240
+ private # CLI authors to offer "rich moments."
241
+
242
+ def show_menu = @tui.draw do |frame| # RatatuiRuby gives you low-level access.
243
+ widget = @tui.paragraph( # But the TUI facade makes it easy to use.
244
+ text: menu_items, # Text can be spans, lines, or paragraphs.
245
+ block: @tui.block(borders: :all, titles: TITLES) # Blocks give you boxes and titles, and hold
246
+ ) # one or more widgets. We only use one here,
247
+ frame.render_widget(widget, frame.area) # but "area" lets you compose sub-views.
248
+ end
249
+
250
+ def chosen? # You are responsible for handling input.
251
+ interaction = @tui.poll_event # Every frame, you receive an event object:
252
+ return choose if interaction.enter? # Key, Mouse, Resize, Paste, FocusGained,
253
+ # FocusLost, or None objects. They come with
254
+ move_by(-1) if interaction.up? # predicates, support pattern matching, and
255
+ move_by(1) if interaction.down? # can be inspected for properties directly.
256
+ quit! if interaction.ctrl_c? # Your application must handle every input,
257
+ false # even interrupts and other exit patterns.
258
+ end
259
+
260
+ def choose # Here, the loop is about to exit, and the
261
+ prepare_next_line # block will return. The inline viewport
262
+ @choice # will be torn down and the terminal will
263
+ end # be restored, but you are responsible for
264
+ # positioning the cursor.
265
+ def prepare_next_line # To ensure the next output is on a new
266
+ area = @tui.viewport_area # line, query the viewport area and move
267
+ RatatuiRuby.cursor_position = [0, area.y + area.height] # the cursor to the start of the last line.
268
+ puts # Then print a newline.
269
+ end
270
+
271
+ def quit! # All of your familiar Ruby control flow
272
+ prepare_next_line # keywords work as expected, so we can
273
+ exit 0 # use them to leave the TUI.
274
+ end
275
+
276
+ def move_by(line_count) # You are in full control of your UX, so
277
+ @choice = (@choice + line_count) % CHOICES.size # you can implement any logic you need:
278
+ end # Would you "wrap around" here, or not?
279
+ #
280
+ def menu_items = CHOICES.map.with_index do |choice, i| # Notably, RatatuiRuby has no concept of
281
+ "\#{prefix_for(i)} \#{choice}" # "menus" or "radio buttons". You are in
282
+ end # full control, but it also means you must
283
+ def prefix_for(choice_index) # implement the logic yourself. For larger
284
+ return PREFIXES[:active] if choice_index == @choice # applications, consider using Rooibos,
285
+ PREFIXES[:inactive] # an MVU framework built with RatatuiRuby.
286
+ end # Or, use the upcoming ratatui-ruby-kit,
287
+ # our object-oriented component library.
288
+ def initialize = @choice = 0 # However, those are both optional, and
289
+ end # designed for full-screen Terminal UIs.
290
+ # RatatuiRuby will always give you the most
291
+ choice = RadioMenu.new.call # control, and is enough for "rich CLI
292
+ puts "You chose \#{choice}!" # moments" like this one.
293
+
294
+ ---
295
+
296
+ === Full App Solutions
297
+
298
+ RatatuiRuby renders. For complex applications, add a framework that manages
299
+ state and composition.
300
+
301
+ ==== Rooibos[https://git.sr.ht/~kerrick/rooibos] (Framework)
302
+
303
+ Model-View-Update architecture. Inspired by Elm, Bubble Tea, and React +
304
+ Redux. Your UI is a pure function of state.
305
+
306
+ - Functional programming with MVU
307
+ - Commands work off the main thread
308
+ - Messages, not callbacks, drive updates
309
+
310
+ ==== {Kit}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-3-the-object-path--kit] (Coming Soon)
311
+
312
+ Component-based architecture. Encapsulate state, input handling, and
313
+ rendering in reusable pieces.
314
+
315
+ - OOP with stateful components
316
+ - Separate UI state from domain logic
317
+ - Built-in focus management & click handling
318
+
319
+ Both use the same widget library and rendering engine. Pick the paradigm
320
+ that fits your brain.
321
+
322
+
323
+ ---
324
+
325
+ === Why RatatuiRuby?
326
+
327
+ Ruby deserves world-class terminal user interfaces. TUI developers deserve
328
+ a world-class language.
329
+
330
+ RatatuiRuby wraps Rust's Ratatui via native extension. The Rust library
331
+ handles rendering. Your Ruby code handles design.
332
+
333
+ >>>
334
+ "Text UIs are seeing a renaissance with many new TUI libraries popping up.
335
+ The Ratatui bindings have proven to be full featured and stable."
336
+
337
+ — {Mike Perham}[https://www.mikeperham.com/], creator of
338
+ Sidekiq[https://sidekiq.org/] and Faktory[https://contribsys.com/faktory/]
339
+
340
+ ==== Why Rust? Why Ruby?
341
+
342
+ Rust excels at low-level rendering. Ruby excels at expressing domain logic
343
+ and UI. RatatuiRuby puts each language where it performs best.
344
+
345
+ ==== Versus CharmRuby
346
+
347
+ CharmRuby[https://charm-ruby.dev/] wraps Charm's Go libraries. Both projects
348
+ give Ruby developers TUI options.
349
+
350
+ [Integration]
351
+ CharmRuby: Two runtimes, one process.
352
+ RatatuiRuby: Native extension in Rust.
353
+ [Runtime]
354
+ CharmRuby: Go + Ruby (competing).
355
+ RatatuiRuby: Ruby (Rust has no runtime).
356
+ [Memory]
357
+ CharmRuby: Two uncoordinated GCs.
358
+ RatatuiRuby: One Garbage Collector.
359
+ [Style]
360
+ CharmRuby: The Elm Architecture (TEA).
361
+ RatatuiRuby: TEA, OOP, or Imperative.
362
+
363
+
364
+ ---
365
+
366
+ === Links
367
+
368
+ [Get Started]
369
+ {Quickstart}[https://www.ratatui-ruby.dev/docs/v0.10/doc/getting_started/quickstart_md.html],
370
+ {Examples}[https://www.ratatui-ruby.dev/docs/v0.10/examples/app_cli_rich_moments/README_md.html],
371
+ {API Reference}[https://www.ratatui-ruby.dev/docs/v0.10/],
372
+ {Guides}[https://www.ratatui-ruby.dev/docs/v0.10/doc/index_md.html]
373
+ [Ecosystem]
374
+ Rooibos[https://git.sr.ht/~kerrick/rooibos],
375
+ {Kit}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-3-the-object-path--kit] (Planned),
376
+ {Framework}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-5-the-framework] (Planned),
377
+ {UI Widgets}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-6-licensing] (Planned)
378
+ [Community]
379
+ {Discuss and Chat}[https://lists.sr.ht/~kerrick/ratatui_ruby-discuss],
380
+ {Announcements}[https://lists.sr.ht/~kerrick/ratatui_ruby-announce],
381
+ {Development}[https://lists.sr.ht/~kerrick/ratatui_ruby-devel],
382
+ {Bug Tracker}[https://todo.sr.ht/~kerrick/ratatui_ruby]
383
+ [Contribute]
384
+ {Contributing Guide}[https://man.sr.ht/~kerrick/ratatui_ruby/contributing.md],
385
+ {Code of Conduct}[https://man.sr.ht/~kerrick/ratatui_ruby/code_of_conduct.md],
386
+ {Project History}[https://man.sr.ht/~kerrick/ratatui_ruby/history/index.md],
387
+ {Pull Requests}[https://lists.sr.ht/~kerrick/ratatui_ruby-devel/patches]
388
+
389
+
390
+ ---
391
+
392
+ [Website] https://www.ratatui-ruby.dev
393
+ [Source] https://git.sr.ht/~kerrick/ratatui_ruby
394
+ [RubyGems] https://rubygems.org/gems/ratatui_ruby
395
+ [Upstream] https://ratatui.rs
396
+ [Build Status] https://builds.sr.ht/~kerrick/ratatui_ruby
397
+
398
+ © 2026 Kerrick Long · Library: LGPL-3.0-or-later · Website: CC-BY-NC-ND-4.0 · Snippets: MIT-0
98
399
  email:
99
400
  - me@kerricklong.com
100
401
  executables:
@@ -119,6 +420,7 @@ files:
119
420
  - LICENSES/MIT-0.txt
120
421
  - LICENSES/MIT.txt
121
422
  - README.md
423
+ - README.rdoc
122
424
  - REUSE.toml
123
425
  - Rakefile
124
426
  - Steepfile