Module: Ionian::Extension::IO

Defined in:
lib/ionian/extension/io.rb

Overview

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

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Instance Attribute Details

- (Object) ionian_timeout

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



12
13
14
# File 'lib/ionian/extension/io.rb', line 12

def ionian_timeout
  @ionian_timeout
end

Class Method Details

+ (Object) extended(obj)

Called automaticallly when the object is extended with #extend.



15
16
17
# File 'lib/ionian/extension/io.rb', line 15

def self.extended obj
  obj.initialize_ionian
end

Instance Method Details

- (Object) expression

Returns the regular expression used for #read_match.



39
40
41
# File 'lib/ionian/extension/io.rb', line 39

def expression
  @ionian_expression
end

- (Object) expression=(exp)

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.



49
50
51
52
# 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

- (Boolean) has_data?(timeout: 0)

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.

Returns:

  • (Boolean)


34
35
36
# File 'lib/ionian/extension/io.rb', line 34

def has_data?(timeout: 0)
  ::IO.select([self], nil, nil, timeout) ? true : false
end

- (Object) initialize_ionian

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



21
22
23
24
25
26
27
28
# 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

- (Object) purge

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



154
155
156
157
158
# File 'lib/ionian/extension/io.rb', line 154

def purge
  # Erase IO buffer.
  read_all
  @ionian_buf = ''
end

- (Object) read_all

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



56
57
58
59
60
# File 'lib/ionian/extension/io.rb', line 56

def read_all
  data = ''
  data += readpartial 0xFFFF while has_data?
  data
end

- (Object) read_match(**kwargs, &block)

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.


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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/ionian/extension/io.rb', line 88

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.
  @ionian_buf << read_all
  
  @matches = []
  
  while @ionian_buf =~ exp
    @matches << $~ # Match data.
    @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
  
  # Pass each match to block.
  @matches.each { |match| yield match } if block_given?
  
  # 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

- (Object) register_observer(&block) Also known as: on_match

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 { … }



164
165
166
167
# File 'lib/ionian/extension/io.rb', line 164

def register_observer &block
  @ionian_listeners << block unless @ionian_listeners.include? block
  block
end

- (Object) run_match(**kwargs)

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.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/ionian/extension/io.rb', line 137

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

- (Object) unregister_observer(&block)

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



172
173
174
175
# File 'lib/ionian/extension/io.rb', line 172

def unregister_observer &block
  @ionian_listeners.delete_if { |o| o == block }
  block
end