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
-
#flush_to(io = $stdout) ⇒ Object
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
A new instance of OutputCapture.
- #lines ⇒ Object
-
#redraw(io = $stderr) ⇒ Object
Redraw the reserved area using the current buffered lines.
-
#start ⇒ Object
Start capturing the child process.
- #stop ⇒ Object
- #wait ⇒ Object
Constructor Details
#initialize(command:, lines: 3, position: :above, log_path: nil, stream: false, debug: nil) ⇒ OutputCapture
Returns a new instance of OutputCapture.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/ruby-progress/output_capture.rb', line 21 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.
19 20 21 |
# File 'lib/ruby-progress/output_capture.rb', line 19 def exit_status @exit_status end |
Instance Method Details
#alive? ⇒ Boolean
85 86 87 |
# File 'lib/ruby-progress/output_capture.rb', line 85 def alive? @reader_thread&.alive? || false end |
#flush_to(io = $stdout) ⇒ Object
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.
157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/ruby-progress/output_capture.rb', line 157 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 ⇒ Object
81 82 83 |
# File 'lib/ruby-progress/output_capture.rb', line 81 def lines @buf_mutex.synchronize { @buffer.dup } end |
#redraw(io = $stderr) ⇒ Object
Redraw the reserved area using the current buffered lines.
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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/ruby-progress/output_capture.rb', line 90 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.
66 67 68 69 70 |
# File 'lib/ruby-progress/output_capture.rb', line 66 def start reserve_space($stderr) if @stream @reader_thread = Thread.new { spawn_and_read } self end |
#stop ⇒ Object
72 73 74 75 |
# File 'lib/ruby-progress/output_capture.rb', line 72 def stop @stop = true @reader_thread&.join end |
#wait ⇒ Object
77 78 79 |
# File 'lib/ruby-progress/output_capture.rb', line 77 def wait @reader_thread&.join end |