WidgetCommandSystem

Demonstrates the Command.execute command for running shell commands.

This example shows how to execute shell commands and handle both success and failure cases using pattern matching in update. The split layout shows command output in the main area with controls at the bottom.

Examples

Run the demo from the terminal:

ruby examples/widget_cmd_exec/app.rb

Source Code

# frozen_string_literal: true

#--
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
# SPDX-License-Identifier: MIT-0
#++

$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)

require "ratatui_ruby"
require "rooibos"

# Demonstrates the Command.execute command for running shell commands.
#
# This example shows how to execute shell commands and handle both success
# and failure cases using pattern matching in update. The split layout shows
# command output in the main area with controls at the bottom.
#
# === Examples
#
# Run the demo from the terminal:
#
#   ruby examples/widget_cmd_exec/app.rb
#
# rdoc-image:/doc/images/widget_cmd_exec.png
class WidgetCommandSystem
  CommandResult = Data.define(:result, :loading, :last_command)
  Init = -> {
    CommandResult.new(
      result: "Press a key to run a command...",
      loading: false,
      last_command: nil
    )
  }

  View = -> (model, tui) do
    hotkey_style = tui.style(modifiers: [:bold, :underlined])
    dim_style = tui.style(fg: :dark_gray)

    # Styles
    border_color = if model.loading
      "yellow"
    elsif model.result.start_with?("Error")
      "red"
    else
      "cyan"
    end

    title = model.last_command ? "Output: #{model.last_command}" : "Command.execute Demo"
    content_text = model.loading ? "Running command..." : model.result

    # 1. Main Output Widget
    output_widget = tui.paragraph(
      text: content_text,
      block: tui.block(
        title:,
        borders: [:all],
        border_style: { fg: border_color },
        padding: 1
      )
    )

    # 2. Control Panel Widget
    control_widget = tui.paragraph(
      text: [
        tui.text_line(spans: [
          tui.text_span(content: "d", style: hotkey_style),
          tui.text_span(content: ": Directory listing (ls -la)  "),
          tui.text_span(content: "u", style: hotkey_style),
          tui.text_span(content: ": System info (uname -a)"),
        ]),
        tui.text_line(spans: [
          tui.text_span(content: "f", style: hotkey_style),
          tui.text_span(content: ": Force failure  "),
          tui.text_span(content: "s", style: hotkey_style),
          tui.text_span(content: ": Sleep (3s)  "),
          tui.text_span(content: "q", style: hotkey_style),
          tui.text_span(content: ": Quit"),
        ]),
      ],
      block: tui.block(
        title: "Controls",
        borders: [:all],
        border_style: dim_style
      )
    )

    # Return the Root Layout Widget (Blueprint)
    tui.layout(
      direction: :vertical,
      constraints: [
        tui.constraint_fill(1),
        tui.constraint_length(6),
      ],
      children: [
        output_widget,
        control_widget,
      ]
    )
  end

  Update = -> (message, model) do
    case message
    # Handle command results (hash-based pattern matching)
    in { type: :system, envelope: :got_output, stdout:, status: 0 }
      [model.with(result: stdout.strip.freeze, loading: false), nil]
    in { type: :system, envelope: :got_output, stderr:, status: }
      [model.with(result: "Error (exit #{status}): #{stderr.strip}".freeze, loading: false), nil]

    # Handle key presses
    in _ if message.q? || message.ctrl_c?
      Rooibos::Command.exit
    in _ if message.d?
      [model.with(loading: true, last_command: "ls -la"), Rooibos::Command.system("ls -la", :got_output)]
    in _ if message.u?
      [model.with(loading: true, last_command: "uname -a"), Rooibos::Command.system("uname -a", :got_output)]
    in _ if message.s?
      cmd = "sleep 3 && echo 'Slept for 3s'"
      [model.with(loading: true, last_command: cmd.freeze), Rooibos::Command.system(cmd, :got_output)]
    in _ if message.f?
      # Intentional failure to demonstrate error handling
      cmd = "ls /nonexistent_path_12345"
      [model.with(loading: true, last_command: cmd.freeze), Rooibos::Command.system(cmd, :got_output)]
    else
      model
    end
  end

  def run
    Rooibos.run(WidgetCommandSystem)
  end
end

WidgetCommandSystem.new.run if __FILE__ == $PROGRAM_NAME