Class: RubyProgress::OutputCapture
- Inherits:
-
Object
- Object
- RubyProgress::OutputCapture
- Defined in:
- lib/ruby-progress/output_capture.rb
Overview
PTY-based live output capture that reserves a small terminal area for printing captured output while the animation draws elsewhere.
Instance Attribute Summary collapse
-
#exit_status ⇒ Object
readonly
Returns the value of attribute exit_status.
Instance Method Summary collapse
-
#alive? ⇒ Boolean
Return true when the background reader thread is alive.
-
#flush_to(io = $stdout) ⇒ void
Flush the buffered lines to the given IO (defaults to STDOUT).
-
#initialize(command:, lines: 3, position: :above, log_path: nil, stream: false, debug: nil) ⇒ OutputCapture
constructor
Create a new OutputCapture instance.
-
#lines ⇒ Array<String>
Return a snapshot of the currently buffered lines.
-
#redraw(io = $stderr) ⇒ void
Redraw the reserved area using the current buffered lines.
-
#start ⇒ Object
Start capturing the child process.
-
#stop ⇒ void
Signal the reader thread to stop and wait for it to finish.
-
#wait ⇒ Thread?
Wait for the background reader thread to finish and return control to the caller.
Constructor Details
#initialize(command:, lines: 3, position: :above, log_path: nil, stream: false, debug: nil) ⇒ OutputCapture
Create a new OutputCapture instance.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/ruby-progress/output_capture.rb', line 74 def initialize(command:, lines: 3, position: :above, log_path: nil, stream: false, debug: nil) @command = command # Coerce lines into a positive Integer @lines = (lines || 3).to_i @lines = 1 if @lines < 1 # Normalize position (accept :top/:bottom or :above/:below or strings) pos = position.respond_to?(:to_sym) ? position.to_sym : position @position = case pos when :top, 'top' then :above when :bottom, 'bottom' then :below when :above, 'above' then :above when :below, 'below' then :below else :above end @buffer = [] @buf_mutex = Mutex.new @stop = false @log_path = log_path @log_file = nil @stream = stream @debug = if debug.nil? ENV.fetch('RUBY_PROGRESS_DEBUG', nil) && ENV['RUBY_PROGRESS_DEBUG'] != '0' else debug end @debug_path = '/tmp/ruby-progress-debug.log' if @debug begin FileUtils.mkdir_p(File.dirname(@debug_path)) File.open(@debug_path, 'w') { |f| f.puts("debug start: #{Time.now}") } rescue StandardError @debug = false end end # Debug: log init if requested via ENV or explicit debug flag debug_log("init: position=#{@position.inspect}; lines=#{@lines}") end |
Instance Attribute Details
#exit_status ⇒ Object (readonly)
Returns the value of attribute exit_status.
58 59 60 |
# File 'lib/ruby-progress/output_capture.rb', line 58 def exit_status @exit_status end |
Instance Method Details
#alive? ⇒ Boolean
Return true when the background reader thread is alive.
154 155 156 |
# File 'lib/ruby-progress/output_capture.rb', line 154 def alive? @reader_thread&.alive? || false end |
#flush_to(io = $stdout) ⇒ void
This method returns an undefined value.
Flush the buffered lines to the given IO (defaults to STDOUT). This is used when capturing non-live output: capture silently during the run and emit all captured output at the end.
232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/ruby-progress/output_capture.rb', line 232 def flush_to(io = $stdout) buf = lines return if buf.empty? begin buf.each do |line| io.puts(line) end io.flush rescue StandardError => e debug_log("flush_to error: #{e.class}: #{e.}") end end |
#lines ⇒ Array<String>
Return a snapshot of the currently buffered lines.
148 149 150 |
# File 'lib/ruby-progress/output_capture.rb', line 148 def lines @buf_mutex.synchronize { @buffer.dup } end |
#redraw(io = $stderr) ⇒ void
This method returns an undefined value.
Redraw the reserved area using the current buffered lines.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/ruby-progress/output_capture.rb', line 162 def redraw(io = $stderr) buf = lines debug_log("redraw called; buffer=#{buf.size}; lines=#{@lines}; position=#{@position}") # If not streaming live to the terminal, don't redraw during capture. return unless @stream cols = if defined?(TTY::Screen) TTY::Screen.columns else IO.console.winsize[1] end display_lines = Array.new(@lines, '') if buf.empty? # leave display_lines as blanks elsif buf.size <= @lines buf.each_with_index { |l, i| display_lines[i] = l.to_s } else buf.last(@lines).each_with_index { |l, i| display_lines[i] = l.to_s } end if defined?(TTY::Cursor) cursor = TTY::Cursor io.print cursor.save if @position == :above io.print cursor.up(@lines) else io.print cursor.down(1) end display_lines.each_with_index do |line, idx| io.print cursor.clear_line io.print line[0, cols] io.print cursor.down(1) unless idx == display_lines.length - 1 end io.print cursor.restore debug_log('redraw finished (TTY)') else io.print "\e7" if @position == :above io.print "\e[#{@lines}A" else io.print "\e[1B" end display_lines.each_with_index do |line, idx| io.print "\e[2K\r" io.print line[0, cols] io.print "\e[1B" unless idx == display_lines.length - 1 end io.print "\e8" debug_log('redraw finished (ANSI)') end io.flush rescue StandardError => e debug_log("redraw error: #{e.class}: #{e.}") end |
#start ⇒ Object
Start capturing the child process. Returns self.
This spawns the configured command in a PTY and begins a background reader thread which buffers the most recent lines. When stream is true the captured lines are redrawn into the terminal area reserved by #reserve_space.
124 125 126 127 128 |
# File 'lib/ruby-progress/output_capture.rb', line 124 def start OutputUI.reserve_space($stderr, @position, @lines) if @stream @reader_thread = Thread.new { spawn_and_read } self end |
#stop ⇒ void
This method returns an undefined value.
Signal the reader thread to stop and wait for it to finish.
132 133 134 135 |
# File 'lib/ruby-progress/output_capture.rb', line 132 def stop @stop = true @reader_thread&.join end |
#wait ⇒ Thread?
Wait for the background reader thread to finish and return control to the caller. This is a simple join wrapper used by callers that need to block until the captured command completes.
142 143 144 |
# File 'lib/ruby-progress/output_capture.rb', line 142 def wait @reader_thread&.join end |