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

streams[R]

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

new(streams, &on_input) click to toggle source

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

[](index) click to toggle source

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
eof?() click to toggle source

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
read() { |: nil| ... } click to toggle source

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

extract_lines(buffer) click to toggle source
# 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
internal_read(buffers, timeout) click to toggle source

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(buffers, stream_newlines, io, line) click to toggle source

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
treat_lines(stream_newlines) click to toggle source
# 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