Class: Nodule::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/nodule/base.rb

Direct Known Subclasses

Alarm, Console, LineIO, Process, Tempfile

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Base

Create a new Nodule handler. This is meant to be a bass class for higher-level Nodule types.

Parameters:

  • opts (Hash{Symbol => String,Symbol,Proc}) (defaults to: {})

Options Hash (opts):

  • :prefix (String)

    text prefix for output/logs/etc.

  • :reader (Symbol, Proc)

    a symbol for a built-in reader, e.g. “:drain” or a proc

  • :readers (Enumerable)

    a list of readers instead of one :reader

  • :run (TrueClass)

    run immediately

  • :verbose (TrueClass)

    print verbose information to STDERR



25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/nodule/base.rb', line 25

def initialize(opts={})
  @read_count = 0
  @readers ||= []
  @output  ||= []
  @prefix  = opts[:prefix] || ''
  @verbose = opts[:verbose]
  @done    = false
  @topology = nil
  @capture_enabled = false

  add_readers(opts[:reader]) if opts[:reader]

  run if opts[:run]
end

Instance Attribute Details

#prefixObject

Returns the value of attribute prefix.



12
13
14
# File 'lib/nodule/base.rb', line 12

def prefix
  @prefix
end

#read_countObject (readonly)

Returns the value of attribute read_count.



11
12
13
# File 'lib/nodule/base.rb', line 11

def read_count
  @read_count
end

#readersObject (readonly)

Returns the value of attribute readers.



11
12
13
# File 'lib/nodule/base.rb', line 11

def readers
  @readers
end

#runningObject (readonly)

Returns the value of attribute running.



11
12
13
# File 'lib/nodule/base.rb', line 11

def running
  @running
end

#topologyObject (readonly)

Returns the value of attribute topology.



11
12
13
# File 'lib/nodule/base.rb', line 11

def topology
  @topology
end

Instance Method Details

#add_reader(action = nil) { ... } ⇒ Object

Add a reader action. Can be a block which will be executed for each unit of input, :capture to capture all items emitted by the target to a list (accessible with .output), :ignore, or nil (which will be ignored).

Parameters:

  • action (Symbol, Proc) (defaults to: nil)

    Action to take on each item read from the handler

Options Hash (action):

  • :capture (Symbol)

    capture the items into an array (access with .output)

  • :drain (Symbol)

    read items but throw them away

  • :stderr (Symbol)

    print the item to stderr (with prefix, in color)

  • :ignore (Symbol)

    don’t do anything

  • run (Proc)

    the block, passing it the item (e.g. a line of stdout)

Yields:

  • optionally pass a proc in with normal block syntax



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/nodule/base.rb', line 186

def add_reader(action=nil, &block)
  if block_given?
    @readers << block
    return unless action
  end

  if action.respond_to? :call
    @readers << action
  elsif action == :capture
    @capture_enabled = true
    @readers << proc { |item| @output.push(item) }
  elsif action == :drain
    @readers << proc { |_| }
  elsif action == :ignore
  # nothing to do here
  # if it's an unrecognized symbol, defer resolution against the containing topology
  elsif action.kind_of? Symbol
    @readers << proc do |item|
      raise "Topology is not set up!" unless @topology
      raise ":#{action} is not a valid topology symbol in #{@topology.to_hash.inspect}" unless @topology.has_key?(action)
      @topology[action].run_readers(item, self)
    end
  else
    raise ArgumentError.new "Invalid add_reader class: #{action.class}"
  end
end

#add_readers(*args) ⇒ Object

Add reader arguments with add_reader. Can be a single item or list.

Parameters:



217
218
219
220
221
# File 'lib/nodule/base.rb', line 217

def add_readers(*args)
  args.flatten.each do |reader|
    add_reader(reader)
  end
end

#clear!Object

Reset the read count to zero and clear any captured output.



108
109
110
111
# File 'lib/nodule/base.rb', line 108

def clear!
  @read_count = 0
  @output.clear
end

#done?Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/nodule/base.rb', line 66

def done?
  @done
end

#join_topology!(t, name = '') ⇒ Object



40
41
42
43
# File 'lib/nodule/base.rb', line 40

def join_topology!(t, name='')
  @topology = t
  @prefix = name if @prefix.nil? || @prefix.empty?
end

#outputArray<String>

Returns a copy of currently captured output. Line data is not chomped or anything of the sort. Will raise an exception if capture is not enabled.

Returns:



88
89
90
91
# File 'lib/nodule/base.rb', line 88

def output
  raise "output is not captured unless you enable :capture" unless @capture_enabled
  @output.clone
end

#output!Array<String>

Returns the captured output and clears the buffer (just like clear! but with a return value). Will raise an exception if capture is not enabled.

Returns:



98
99
100
101
102
103
# File 'lib/nodule/base.rb', line 98

def output!
  raise "output is not captured unless you enable :capture" unless @capture_enabled
  out = @output.clone
  clear!
  out
end

#output?TrueClass, FalseClass

Returns whether or not any output has been captured. Will raise an exception if capture is not enabled.

Returns:

  • (TrueClass, FalseClass)


78
79
80
81
# File 'lib/nodule/base.rb', line 78

def output?
  raise "output is not captured unless you enable :capture" unless @capture_enabled
  @output.any?
end

#read_until(opts = {}) { ... } ⇒ Object

Wait in a sleep loop until a condition is true or the timeout is reached. On timeout an exception is raised. Has no impact on normal readers.

Examples:

act.read_until(:max_sleep => 1.0) { File.exist?(“/tmp/file”) }

Parameters:

  • opts (Hash) (defaults to: {})

    Options for how to read and wait

Options Hash (opts):

  • :max_sleep (Float)

    maximum number of seconds to wait

  • :sleep_by (Float)

    how long to sleep by each time, default 0.1

Yields:

  • block to call to check condition, returns true or false



147
148
149
150
151
152
153
154
155
156
# File 'lib/nodule/base.rb', line 147

def read_until(opts = {}, &block)
  raise "No block given to read_until!" unless block_given?
  started = Time.now
  until block.call
    sleep (opts[:sleep_by] || 0.1)
    if Time.now - started >= (opts[:max_sleep] || 10.0)
      raise "Timeout!"
    end
  end
end

#require_read_count(count, max_sleep = 10) { ... } ⇒ Object

Wait in a sleep(0.1) loop for the number of reads on the handler to reach <count>. Returns when the number of reads is given. On timeout, if a block was provided, it’s called before return. Otherwise, an exception is raised. Has no impact on normal readers.

Examples:

act.require_read_timeout 1, 10 { fail }

act.require_read_timeout 1, 10 rescue nil

Parameters:

  • count (Fixnum)

    how many reads to wait for

  • max_sleep (Float) (defaults to: 10)

    maximum number of seconds to wait for the count

Yields:

  • optional block to run on timeout



170
171
172
# File 'lib/nodule/base.rb', line 170

def require_read_count(count, max_sleep=10)
  read_until(:max_sleep => max_sleep) { @read_count == count }
end

#runObject



45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/nodule/base.rb', line 45

def run
  @done = false

  unless @topology
    @toplogy = Nodule::Topology.new(:auto => self)
  end

  # automatically determine a prefix for console output based on the key name known to the topology
  if name = @topology.key(self)
    @prefix = "[#{name}]: "
  end
end

#run_readers(item, src = nil) ⇒ Object

Run all of the registered reader blocks. The block should expect a single argument that is an item of input. If the block has an arity of two, it will also be handed the nodule object provided to run_readers (if it was provided; no guarantee is made that it will be available). The arity-2 version is provided mostly as a clean way for Nodule::Console to add prefixes to output, but could be useful elsewhere.

Parameters:

  • item (Object)

    the item to pass to the readers, often a String (but could be anything)

  • handler (Nodule::Base)

    that generated the item, optional, untyped



232
233
234
235
236
237
238
239
240
241
242
# File 'lib/nodule/base.rb', line 232

def run_readers(item, src=nil)
  @read_count += 1
  verbose "READ(#{@read_count}): #{item}"
  @readers.each do |reader|
    if reader.arity == 2
      reader.call(item, src)
    else
      reader.call(item)
    end
  end
end

#stopObject



58
59
60
# File 'lib/nodule/base.rb', line 58

def stop
  @done = true
end

#stop!Object



62
63
64
# File 'lib/nodule/base.rb', line 62

def stop!
  @done = true
end

#verbose(*out) ⇒ Object

Verbose Nodule output.

Parameters:

  • out (Array<String>)

    strings to output, will be joined with ‘ ’



248
249
250
251
252
253
254
255
256
# File 'lib/nodule/base.rb', line 248

def verbose(*out)
  if @verbose
    if @topology.respond_to? :[] and @topology[@verbose]
      @topology[@verbose].run_readers out.join(' ')
    else
      STDERR.print "#{out.join(' ')}\n"
    end
  end
end

#wait(timeout = nil) ⇒ Object



70
71
# File 'lib/nodule/base.rb', line 70

def wait(timeout=nil)
end

#wait_with_backoff(timeout, sleeptime = 0.01) { ... } ⇒ Object

A dead-simple backoff loop. Calls your block every @sleeptime seconds, increasing the sleep time by sleeptime every iteration, up to timeout, at which point false will be returned. If the block returns an non-false/nil value, it is returned.

Parameters:

  • timeout (Float, Fixnum)
  • sleeptime (Float, Fixnum) (defaults to: 0.01)

Yields:

  • block that returns false to continue waiting, non-false to return that value

Returns:

  • block return value or false if timeout



122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/nodule/base.rb', line 122

def wait_with_backoff(timeout, sleeptime=0.01)
  raise "a block to execute on each iteration is required" unless block_given?
  started = Time.now
  loop do
    val = yield
    return val if val
    return false if Time.now - started > timeout
    sleep sleeptime
    if sleeptime < timeout / 4
      sleeptime += sleeptime
    end
  end
end