Class: TUITD::Driver
- Inherits:
-
Object
- Object
- TUITD::Driver
- Defined in:
- lib/tui_td/driver.rb
Overview
Drives a TUI application in a pseudo-terminal (PTY).
Usage:
driver = Driver.new("my_tui_app")
driver.start
driver.wait_for_text("> ")
driver.send("Write hello\n")
state = driver.state_json # => structured JSON for AI
driver.screenshot("out.png")
driver.close
Instance Attribute Summary collapse
-
#command ⇒ Object
readonly
Returns the value of attribute command.
-
#state ⇒ Object
readonly
Returns the value of attribute state.
Instance Method Summary collapse
-
#close ⇒ Object
Close the driver and clean up.
-
#initialize(command, rows: 40, cols: 120, timeout: 30, chdir: nil) ⇒ Driver
constructor
A new instance of Driver.
-
#raw_output ⇒ Object
Get the terminal output (raw ANSI + text).
-
#screenshot(output_path) ⇒ Object
Capture a PNG screenshot of the current terminal state.
-
#send(text) ⇒ Object
Send text to the TUI.
-
#send_keys(keys) ⇒ Object
Send keys (escape sequences, control characters).
-
#start ⇒ Object
Start the TUI application in a PTY.
-
#state_data ⇒ Object
Get structured terminal state as a Hash.
-
#state_json(pretty: false) ⇒ Object
Get structured terminal state as JSON string.
-
#wait_for_exit ⇒ Object
Wait until the process finishes.
-
#wait_for_stable(stable_ms: 300) ⇒ Object
Wait for output to stabilize (no new data for N milliseconds).
-
#wait_for_text(text) ⇒ Object
Wait until output contains the given text.
Constructor Details
#initialize(command, rows: 40, cols: 120, timeout: 30, chdir: nil) ⇒ Driver
Returns a new instance of Driver.
22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/tui_td/driver.rb', line 22 def initialize(command, rows: 40, cols: 120, timeout: 30, chdir: nil) @command = command @rows = rows @cols = cols @timeout = timeout @chdir = chdir @state = nil @stdin = nil @stdout = nil @wait_thr = nil @output_buffer = +"" end |
Instance Attribute Details
#command ⇒ Object (readonly)
Returns the value of attribute command.
20 21 22 |
# File 'lib/tui_td/driver.rb', line 20 def command @command end |
#state ⇒ Object (readonly)
Returns the value of attribute state.
20 21 22 |
# File 'lib/tui_td/driver.rb', line 20 def state @state end |
Instance Method Details
#close ⇒ Object
Close the driver and clean up
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/tui_td/driver.rb', line 139 def close # Kill the process if still running if @pid begin if Process.waitpid(@pid, Process::WNOHANG).nil? Process.kill("TERM", @pid) rescue nil sleep 0.05 Process.kill("KILL", @pid) rescue nil end rescue Errno::ECHILD # Already reaped by Process.detach end end @stdin&.close rescue nil @stdout&.close rescue nil @stdin = @stdout = @pid = nil end |
#raw_output ⇒ Object
Get the terminal output (raw ANSI + text)
115 116 117 118 |
# File 'lib/tui_td/driver.rb', line 115 def raw_output read_available! @output_buffer end |
#screenshot(output_path) ⇒ Object
Capture a PNG screenshot of the current terminal state
133 134 135 136 |
# File 'lib/tui_td/driver.rb', line 133 def screenshot(output_path) state_data Screenshot.new(@state).render(output_path) end |
#send(text) ⇒ Object
Send text to the TUI
53 54 55 56 57 58 |
# File 'lib/tui_td/driver.rb', line 53 def send(text) ensure_running! @stdin&.print(text) @stdin&.flush true end |
#send_keys(keys) ⇒ Object
Send keys (escape sequences, control characters)
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/tui_td/driver.rb', line 61 def send_keys(keys) ensure_running! case keys when :enter then send("\r") when :tab then send("\t") when :escape then send("\e") when :up then send("\e[A") when :down then send("\e[B") when :left then send("\e[D") when :right then send("\e[C") when :backspace then send("\u007f") when :ctrl_c then send("\u0003") when :ctrl_d then send("\u0004") else send(keys.to_s) end end |
#start ⇒ Object
Start the TUI application in a PTY
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/tui_td/driver.rb', line 36 def start env = { "TERM" => "xterm-256color", "COLUMNS" => @cols.to_s, "LINES" => @rows.to_s } spawn_opts = {} spawn_opts[:chdir] = @chdir if @chdir @stdout, @stdin, @pid = PTY.spawn(env, @command, spawn_opts) @stdout.winsize = [@rows, @cols] # Set PTY window size for TUIs that check winsize @wait_thr = Process.detach(@pid) # Read until initial output stabilizes wait_for_stable refresh_state! true end |
#state_data ⇒ Object
Get structured terminal state as a Hash
121 122 123 124 |
# File 'lib/tui_td/driver.rb', line 121 def state_data refresh_state! if @state.nil? @state end |
#state_json(pretty: false) ⇒ Object
Get structured terminal state as JSON string
127 128 129 130 |
# File 'lib/tui_td/driver.rb', line 127 def state_json(pretty: false) state_data pretty ? JSON.pretty_generate(@state) : JSON.generate(@state) end |
#wait_for_exit ⇒ Object
Wait until the process finishes
110 111 112 |
# File 'lib/tui_td/driver.rb', line 110 def wait_for_exit @wait_thr&.value end |
#wait_for_stable(stable_ms: 300) ⇒ Object
Wait for output to stabilize (no new data for N milliseconds)
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/tui_td/driver.rb', line 91 def wait_for_stable(stable_ms: 300) deadline = monotonic + @timeout last_change = monotonic loop do raise TimeoutError, "Timeout waiting for stable output" if monotonic > deadline if read_available! last_change = monotonic elsif (monotonic - last_change) * 1000 >= stable_ms break end sleep 0.05 end refresh_state! end |
#wait_for_text(text) ⇒ Object
Wait until output contains the given text
79 80 81 82 83 84 85 86 87 88 |
# File 'lib/tui_td/driver.rb', line 79 def wait_for_text(text) deadline = monotonic + @timeout loop do raise TimeoutError, "Timeout waiting for: #{text.inspect}" if monotonic > deadline read_available! break if @output_buffer.include?(text) sleep 0.05 end refresh_state! end |