
2026/01/17 23:03
**Show HN:** *RatatuiRuby* は Rust の *Ratatui* をラップした RubyGem です ― Ruby の楽しさを感じる TUI アプリケーション。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
要約
RatatuiRuby は、Rust ベースの Ratatui ターミナル UI ライブラリをラップした RubyGem で、ネイティブパフォーマンスを Ruby アプリケーションに提供します。
インストール方法は次のとおりです。
gem install ratatui_ruby --preSemVer Tag: v1.0.0-beta.2
この gem は
RatatuiRuby.run を公開し、raw モードへ入り、代替画面に切り替え、終了時にターミナルを復元します。ブロック内では draw で描画し、poll_event で入力処理を行うことができます。
コア機能
| 機能 | 説明 |
|---|---|
| インラインビュー | スクロールバックを保持する固定高さ領域。スピナー、プログレスバー、メニューに最適です。 |
| ウィジェット | パラグラフ、ブロック(タイトル・境界線・スタイル付き)、カスタムウィジェット、および組み込みテストヘルパー () があります。 |
| 例示ウィジェット | Spinner – は接続スピナーを表示し、Ctrl‑C を処理します。RadioMenu – ["Production", "Staging", "Development"] の中から矢印キーで選択でき、同期的に選択値を返します。 |
| テスト | イベント注入、スタイルアサーション、およびスナップショット比較(例: ウィジェットの単体テスト、 の統合テスト)を伴うヘッドレスターミナルテスト。 |
プログラミングパラダイム
RatatuiRuby はオブジェクト指向と関数型の両方のスタイルに対応しており、開発者はコードベースに最適なアプローチを選択できます。
他の Ruby TUI ライブラリとの比較
| ライブラリ | 統合 | 実行時 | メモリ | GC |
|---|---|---|---|---|
| CharmRuby (Go + Ruby) | 2つの GC、統合が遅い | 遅い | 高め | 2 |
| RatatuiRuby (Rust ネイティブ拡張) | 1つの GC、統合が速い | 速い | 低め | 1 |
この表は、実行速度、メモリフットプリント、および統合容易性における RatatuiRuby の優位性を示しています。
コミュニティと今後の展開
Mike Perham の引用では「RatatuiRuby は Ruby 開発者にとって世界クラス」と称され、Rust の低レベルパフォーマンスと Ruby ドメインロジックの融合が際立っています。 Rooibos などの新しいコンポーネントライブラリや拡張 UI キットは、機能拡充を約束しています。
影響
RatatuiRuby を使うことで、Ruby 開発者は高速でメモリ効率の良いターミナルインターフェース(CLI ツール、ダッシュボード、対話型スクリプト)を構築でき、Ruby の表現力を犠牲にすることなく実装できます。
本文
Terminal UIs ― Ruby で書く方法
==============================
RatatuiRuby は、Rust で実装された先進的な TUI ライブラリ「Ratatui」をベースにした RubyGem です。Ruby の開発の楽しさを保ちながらも、ネイティブレベルの高速処理を体験できます。
$ gem install ratatui_ruby --pre # SemVer Tag: v1.0.0-beta.2
インライン・ビューポート
標準的な TUI は終了時に自動で消去され、整形された CLI 出力が失われてしまいます。インラインビューポートは固定行数を占有し、リッチな UI を描画したあと出力をそのまま残します。スピナー、メニュー、進捗バーなど、短時間だけ見せる場面に最適です。
class Spinner def main RatatuiRuby.run(viewport: :inline, height: 1) do |tui| until connected? status = tui.paragraph(text: "#{spin} Connecting...") tui.draw { |frame| frame.render_widget(status, frame.area) } return ending(tui, "Canceled!", :red) if tui.poll_event.ctrl_c? end ending(tui, "Connected!", :green) end end def ending(tui, message, color) = tui.draw do |frame| frame.render_widget( tui.paragraph(text: message, fg: color), frame.area ) end def initialize = (@frame, @finish = 0, Time.now + 2) def connected? = Time.now >= @finish # Simulate work def spin = SPINNER[(@frame += 1) % SPINNER.length] SPINNER = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏] end Spinner.new.main; puts
インラインメニューの例
require "ratatui_ruby" class RadioMenu CHOICES = ["Production", "Staging", "Development"] PREFIXES = { active: "●", inactive: "○" } CONTROLS = "↑/↓: Select | Enter: Choose | Ctrl+C: Cancel" TITLES = [ "Select Environment", { content: CONTROLS, position: :bottom, alignment: :right } ] def call RatatuiRuby.run(viewport: :inline, height: 5) do |tui| @tui = tui show_menu until chosen? end RadioMenu::CHOICES[@choice] end private def show_menu = @tui.draw do |frame| widget = @tui.paragraph( text: menu_items, block: @tui.block(borders: :all, titles: TITLES) ) frame.render_widget(widget, frame.area) end def chosen? interaction = @tui.poll_event return choose if interaction.enter? move_by(-1) if interaction.up? move_by(1) if interaction.down? quit! if interaction.ctrl_c? false end def choose prepare_next_line @choice end def prepare_next_line area = @tui.viewport_area RatatuiRuby.cursor_position = [0, area.y + area.height] puts end def quit! prepare_next_line exit 0 end def move_by(line_count) @choice = (@choice + line_count) % CHOICES.size end def menu_items = CHOICES.map.with_index do |choice, i| "#{prefix_for(i)} #{choice}" end def prefix_for(choice_index) choice_index == @choice ? PREFIXES[:active] : PREFIXES[:inactive] end def initialize = @choice = 0 end choice = RadioMenu.new.call puts "You chose #{choice}!"
シンプルな「Hello」例
RatatuiRuby.run do |tui| loop do tui.draw do |frame| frame.render_widget( tui.paragraph( text: "Hello, RatatuiRuby!", alignment: :center, block: tui.block( title: "My App", titles: [{ content: "q: Quit", position: :bottom, alignment: :right }], borders: [:all], border_style:{ fg: "cyan" } ) ), frame.area ) end case tui.poll_event in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] } break else nil end end end
管理されたループ
RatatuiRuby.run は raw モードに入り、代替画面へ切り替え、終了時にターミナルを復元します。ブロック内では
– ウィジェットを描画draw
– 入力を読み取るpoll_event
ウィジェットには段落(paragraph)、ブロック(block)、枠線・タイトルなどが含まれます。
テストサポート
require "ratatui_ruby/test_helper" class TestColorPicker < Minitest::Test include RatatuiRuby::TestHelper # Built-in mixin def test_swatch_widget with_test_terminal(10, 3) do RatatuiRuby.draw do |frame| frame.render_widget(Swatch.new(:red), frame.area) end assert_cell_style 2, 1, char: "█", bg: :red end end def test_input_hex with_test_terminal do inject_keys "#", "f", "f", "0", "0", "0", "0" inject_keys :enter, :q ColorPicker.new.run assert_snapshots "after_hex_entry" end end end
include RatatuiRuby::TestHelper を一度だけ書くだけで、ヘッドレスターミナルのセットアップ、イベント注入、描画結果のアサーションがすべて可能です。外部依存は不要です。
RatatuiRuby vs CharmRuby
| Feature | CharmRuby | RatatuiRuby |
|---|---|---|
| Integration | 2つのランタイム (Go + Ruby) | Rust のネイティブ拡張 |
| Runtime | Go + Ruby(GC が競合) | Ruby 一択 (Rust はランタイムなし) |
| Memory | 2つの非協調 GC | 1つのガベージコレクタ |
| Style | Elm Architecture (TEA) | TEA、OOP、または命令型 |
なぜ Rust? なぜ Ruby?
Rust は低レベルの描画に優れ、Ruby はドメインロジックと UI を簡潔に表現できます。RatatuiRuby はそれぞれの言語が最も得意な分野で活躍できるよう設計されています。
「テキスト UI は新たな TUI ライブラリが次々登場し、レジェンド化しています。Ratatui のバインディングは機能豊富かつ安定していることが証明されました。」 – Mike Perham(Sidekiq および Faktory の創始者)
RatatuiRuby エコシステムを探索する
→ さらに詳しい情報やサンプルコードは公式リポジトリでご確認ください。