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 34 35 36 |
# 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 = +"" @output_mutex = Mutex.new @reader_thread = nil @reader_running = false 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
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/tui_td/driver.rb', line 145 def close _stop_reader_thread # 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)
121 122 123 124 |
# File 'lib/tui_td/driver.rb', line 121 def raw_output read_available! @output_mutex.synchronize { @output_buffer.dup } end |
#screenshot(output_path) ⇒ Object
Capture a PNG screenshot of the current terminal state
139 140 141 142 |
# File 'lib/tui_td/driver.rb', line 139 def screenshot(output_path) state_data Screenshot.new(@state).render(output_path) end |
#send(text) ⇒ Object
Send text to the TUI
58 59 60 61 62 63 |
# File 'lib/tui_td/driver.rb', line 58 def send(text) ensure_running! @stdin&.print(text) @stdin&.flush true end |
#send_keys(keys) ⇒ Object
Send keys (escape sequences, control characters)
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/tui_td/driver.rb', line 66 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
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/tui_td/driver.rb', line 39 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! _start_reader_thread true end |
#state_data ⇒ Object
Get structured terminal state as a Hash
127 128 129 130 |
# File 'lib/tui_td/driver.rb', line 127 def state_data refresh_state! if @state.nil? @state end |
#state_json(pretty: false) ⇒ Object
Get structured terminal state as JSON string
133 134 135 136 |
# File 'lib/tui_td/driver.rb', line 133 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
116 117 118 |
# File 'lib/tui_td/driver.rb', line 116 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)
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/tui_td/driver.rb', line 97 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
84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/tui_td/driver.rb', line 84 def wait_for_text(text) deadline = monotonic + @timeout loop do raise TimeoutError, "Timeout waiting for: #{text.inspect}" if monotonic > deadline read_available! found = @output_mutex.synchronize { @output_buffer.include?(text) } break if found sleep 0.05 end refresh_state! end |