module Ionian::Extension::IO

A mixin for IO objects that allows regular expression matching and convenient notification of received data.

This module was designed to be extended by instantiated objects that implement the standard library IO class. my_socket.extend Ionian::IO

Attributes

ionian_timeout[RW]

Number of seconds to attempt an IO operation before timing out. See standard library IO::select.

Public Class Methods

extended(obj) click to toggle source

Called automaticallly when the object is extended with extend.

# File lib/ionian/extension/io.rb, line 15
def self.extended(obj)
  obj.initialize_ionian
end

Public Instance Methods

expression() click to toggle source

Returns the regular expression used for read_match.

# File lib/ionian/extension/io.rb, line 39
def expression
  @ionian_expression
end
expression=(exp) click to toggle source

Set the expression to match against the read buffer. Can be a regular expression specifying capture groups, or a string specifying the separator or line terminator sequence. It is possible to use named captures in a regex, which allows for convienient accessors like match.

# File lib/ionian/extension/io.rb, line 49
def expression=(exp)
  @ionian_expression = exp
  @ionian_expression = Regexp.new "(.*?)#{expression}" if exp.is_a? String
end
has_data?(timeout: 0) click to toggle source

Returns true if there is data in the receive buffer. Args:

Timeout: Number of seconds to wait for data until
  giving up. Set to nil for blocking.
# File lib/ionian/extension/io.rb, line 34
def has_data?(timeout: 0)
  ::IO.select([self], nil, nil, timeout) ? true : false
end
initialize_ionian() click to toggle source

Initialize the Ionian instance variables. This is called automatically if extend is called on an object.

# File lib/ionian/extension/io.rb, line 21
def initialize_ionian
  @ionian_listeners     = []
  @ionian_buf           = ''
  @ionian_expression    = /(.*?)[\r\n]+/
  @ionian_timeout       = nil
  @ionian_skip_select   = false
  @ionian_build_methods = true
end
on_match(&block)
Alias for: register_observer
purge() click to toggle source

Erase the data in the IO and Ionian buffers. This is typically handled automatically.

# File lib/ionian/extension/io.rb, line 151
def purge
  # Erase IO buffer.
  while ::IO.select [self], nil, nil, 0
    readpartial(0xFFFF)
  end
  
  @ionian_buf = ''
end
read_all() click to toggle source

Read all data in the buffer. An alternative to using readpartial with a large length.

# File lib/ionian/extension/io.rb, line 56
def read_all
  readpartial 0xFFFF
end
read_match(**kwargs) { |$~| ... } click to toggle source

Read matched data from the buffer. This method SHOULD NOT be used if run_match is used.

Passes matches to the block (do |match|). If there are multiple matches, the block is called multiple times.

Returns an array of matches. Returns nil if no data was received within the timeout period.

Junk data that could exist before a match in the buffer can be accessed with match.pre_match.

Data at the end of the buffer that is not matched can be accessed in the last match with match.post_match. This data remains in the buffer for the next read_match cycle. This is helpful for protocols like RS232 that do not have packet boundries.

kwargs:

timeout:        Timeout in seconds IO::select will block.
expression:     Override the expression match for this single
                method call.
notify:         Set to false to skip notifying match listener procs.
skip_select:    Skip over the IO::select statement. Use if you
                are calling IO::select ahead of this method.
build_methods:  Build accessor methods from named capture groups.
                Enabled by default.
# File lib/ionian/extension/io.rb, line 86
def read_match(**kwargs, &block)
  timeout       = kwargs.fetch :timeout,        @ionian_timeout
  notify        = kwargs.fetch :notify,         true
  skip_select   = kwargs.fetch :skip_select,    @ionian_skip_select
  build_methods = kwargs.fetch :build_methods,  @ionian_build_methods
  
  exp           = kwargs.fetch :expression,     @ionian_expression
  exp           = Regexp.new "(.*?)#{exp}" if exp.is_a? String
  
  unless skip_select
    return nil unless ::IO.select [self], nil, nil, timeout
  end
  
  # Read data from the IO buffer until it's empty.
  loop do
    @ionian_buf << readpartial(0xFFFF)
    break unless ::IO.select [self], nil, nil, 0
  end
  
  @matches = []
  
  while @ionian_buf =~ exp
    @matches << $~ # Match data.
    yield $~ if block_given?
    @ionian_buf = $' # Leave post match data in the buffer.
  end
  
  # Convert named captures to methods.
  if build_methods
    @matches.each do |match|
      match.names
        .map {|name| name.to_sym}
        .each {|symbol| match.singleton_class
          .send(:define_method, symbol) { match[symbol] }                    unless match.respond_to? symbol
        }
    end
  end
  
  # Notify on_match listeners unless the #run_match thread is active.
  @matches.each {|match| notify_listeners match} if notify and not @match_listener
  
  @matches
end
register_observer(&block) click to toggle source

Register a block to be called when run_match receives matched data. Method callbacks can be registered with &object.method(:method). Returns a reference to the given block. block = ionian_socket.register_observer {…}

# File lib/ionian/extension/io.rb, line 164
def register_observer(&block)
  @ionian_listeners << block unless @ionian_listeners.include? block
  block
end
Also aliased as: on_match
run_match(**kwargs) click to toggle source

Start a thread that checks for data and notifies listeners (do |match, socket|). Passes kwargs to read_match. This method SHOULD NOT be used if read_match is used.

# File lib/ionian/extension/io.rb, line 134
def run_match(**kwargs)
  @match_listener ||= Thread.new do
    begin
      while not closed? do
        matches = read_match **kwargs
        matches.each {|match| notify_listeners match } if matches
      end
    rescue EOFError
    rescue IOError
    ensure
      @match_listener = nil
    end
  end
end
unregister_observer(&block) click to toggle source

Unregister a block from being called when matched data is received.

# File lib/ionian/extension/io.rb, line 172
def unregister_observer(&block)
  @ionian_listeners.delete_if {|o| o == block}
  block
end