class LCR::LineReader
This class aims to read over a groupe of stream and triger a block when new lines are read on one of them.
Constants
- LINE_END
What is considered as a return line
- MAX_READ_CHAR
How must characters to read at a time.
Attributes
access to the array of streams. Be carefull accessing these, because you may prevent line_reader to run correctly. @return [Array<IO>]
Public Class Methods
Initializer takes an array of IO streams. You can optionaly pass a block that will be called at each new line.
The block will be called with as must as IO stream there is to treat.
@attr_reader [Array<IO>] streams The list of streams that have been given @yield [io0_new_line, io1_new_line, …] Will be called each time a new line is read. @yieldparam [String | nil] io0_new_line The string read as a new line without the endline
characters. It will be nil if the new line detected is not on the first IO given in the initializer.
@yieldparam [String | nil] io1io1_new_line Same as previous param except it is for IO stream at index
1 given to the initializer.
@yieldreturn [void] It is ignored.
# File lib/long-command-runner/line_reader.rb, line 25 def initialize(streams, &on_input) @streams = streams @streams_lines = Array.new(streams.length) { [] } @on_input = on_input end
Public Instance Methods
Access to the lines of a stream
@param [Integer] index The index of the stream in the order given at the
initialization of this object.
@return [Array<String>]
# File lib/long-command-runner/line_reader.rb, line 75 def [](index) @streams_lines[index].dup end
Are all the streams reached End-Of-File ?
@return [Boolean]
# File lib/long-command-runner/line_reader.rb, line 59 def eof? @streams.all? do |stream| begin Timeout.timeout(0.001) { stream.eof? } rescue Timeout::Error false end end end
Blocking method to read on the streams (you may call it in a dedicated thread).
It will stop when eof is reach. Never the less it may not be the end.
Block is optional: @yield [] Will be called just before waiting on the select(streams) @yieldreturn [Float | Integer | nil] a value for the timeout on the next select call.
A nil value for no timeout (which is default when no block is provided).
@return [Array<Integer>] the number of lines read on each streams
# File lib/long-command-runner/line_reader.rb, line 45 def read buffers = Array.new(@streams.length) { nil } loop do # puts "read - loop" timeout = block_given? ? yield : nil break if internal_read(buffers, timeout) end # puts "read - quitting: #{@streams_lines.map(&:length)}" @streams_lines.map(&:length) end
Private Instance Methods
# File lib/long-command-runner/line_reader.rb, line 113 def extract_lines(buffer) lines = buffer.split(LINE_END) return lines, nil if buffer =~ /#{LINE_END}\z/ buffer = lines.pop [lines, buffer] end
This internal reader function is made to run inside a loop. @return [Boolean] false if any of the stream can be read (not all at eof).
then return true when all streams are closed.
# File lib/long-command-runner/line_reader.rb, line 84 def internal_read(buffers, timeout) # puts "internal_read(buffers:#{buffers}, timeout:#{timeout})" closed = 0 ready = IO.select(@streams, [], [], timeout) stream_newlines = Array.new(@streams.length) { [] } ready[0].each do |io| io_index = @streams.find_index(io) closed += 1 unless read_on(buffers, stream_newlines, io, io_index) end # puts "internal_read - calling treat_lines(#{stream_newlines})" treat_lines(stream_newlines) closed == @streams.length end
read on a given stream and treat the flow @return [Boolean] false if stream has reach eof
# File lib/long-command-runner/line_reader.rb, line 100 def read_on(buffers, stream_newlines, io, line) # puts "read_on(buffers:#{buffers}, streams_lines:#{stream_newlines}, io:#{io}, line:#{line})" if io.eof? stream_newlines[line] = [buffers[line]] unless buffers[line].nil? buffers[line] = nil return false end r = io.read_nonblock(MAX_READ_CHAR) buffers[line] = buffers[line].nil? ? r : (buffers[line] + r) stream_newlines[line], buffers[line] = extract_lines(buffers[line]) true end
# File lib/long-command-runner/line_reader.rb, line 121 def treat_lines(stream_newlines) # puts "treat_lines(stream_newlines:#{stream_newlines})" stream_newlines.each_with_index do |new_lines, i| @streams_lines[i].concat(new_lines) end until stream_newlines.all?(&:empty?) # puts "treat_lines - stream_newlines not all empty: #{stream_newlines}" on_input_params = [] stream_newlines.each do |newlines| on_input_params << (newlines.empty? ? nil : newlines.shift) end # puts "treat_lines - may call:#{@on_input} with:#{on_input_params}" @on_input&.call(*on_input_params) end end