Class: Store::Digest::ReadWrapper

Inherits:
Object
  • Object
show all
Defined in:
lib/store/digest/readwrapper.rb

Overview

This class is an attempt to normalize input so that it can be #read like an IO handle. Use the class method ReadWrapper.coerce to determine if it's even necessary.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(obj) ⇒ ReadWrapper

Initialize a wrapper.

Parameters:

  • obj (#call, #each)

    a suitable object

Raises:

  • (ArgumentError)

    said object is unsuitable



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
133
134
# File 'lib/store/digest/readwrapper.rb', line 101

def initialize obj
  test = obj.respond_to?(:arity) ? obj :
    obj.respond_to?(:call) ? obj.method(:call) : nil

  if test
    raise ArgumentError,
      'Callable object is expected to take a write handle as an argument' if
      test.arity == 0
  elsif obj.respond_to?(:each)
    nil
  elsif obj.respond_to? :to_s
    obj = [obj.to_s]
  else
    raise ArgumentError,
      'Argument must respond to #call(write_fh) or #each or #to_s'
  end

  @done  = false
  @mutex = Mutex.new

  @read, @write = IO.pipe

  @thread = Thread.new do
    if obj.respond_to? :call
      obj.call @write
    else
      obj.each { |x| @write << x.to_s.b }
    end
  rescue Errno::EPIPE # => e
    nil # not sure if we do anything here
  ensure
    @write.close unless @write.closed?
  end
end

Class Method Details

.coerce(obj, thunk: false) ⇒ ReadWrapper, Object Also known as: []

Attempt to coerce a suitable object or no-op.

Parameters:

  • obj (Object)

    an object to be coerced

  • thunk (false, true) (defaults to: false)

    let a thunk (a zero-arity callable that in this case returns a read handle) pass through; if falsy, it will execute the thunk and expect it to return something that quacks like a read handle, and throw an error if it isn't.

Returns:

  • (ReadWrapper, Object)

    a new proxy object around whatever the input is, or the original input if file-handle-ey enough

Raises:

  • (ArgumentError)

    if the input is not sufficiently coercible



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/store/digest/readwrapper.rb', line 65

def self.coerce obj, thunk: false
  return obj if [self, Store::Digest::Entry].any? { |c| obj.is_a? c }

  return obj.open('rb') if obj.is_a? Pathname

  return obj if quacks? obj # no need for this if it can read

  return StringIO.new(obj) if obj.is_a? String

  # response bodies /don't do this but other stuff does
  if obj.respond_to?(:arity) && obj.arity == 0 ||
      obj.respond_to?(:call) && obj.method(:call).arity == 0
    # let the thunk through
    return obj if thunk

    out = obj.call
    raise ArgumentError,
      'a `call` with no arguments must return an IO-like object' unless
      quacks? out

    return out
  end

  new obj
end

.quacks?(obj) ⇒ false, true

Test if the object quacks like an IO.

Parameters:

  • obj (Object)

    said object

Returns:

  • (false, true)


20
21
22
23
24
# File 'lib/store/digest/readwrapper.rb', line 20

def self.quacks? obj
  obj.is_a?(IO) or %i[gets read close].all? do |m|
    obj.respond_to? m
  end
end

Instance Method Details

#closeObject

close for parity with IO



167
168
169
170
# File 'lib/store/digest/readwrapper.rb', line 167

def close
  cleanup
  nil # close returns nil
end

#gets(sep = $/, chomp = false) ⇒ String?

gets for parity with IO

Returns:

  • (String, nil)


140
141
142
143
144
145
146
147
# File 'lib/store/digest/readwrapper.rb', line 140

def gets sep = $/, chomp = false
  unless @read.closed?
    out = @read.gets sep, chomp
    cleanup if out.nil?

    out
  end
end

#read(maxlen = nil, string = nil) ⇒ String?

read for parity with IO

Parameters:

  • maxlen (Integer) (defaults to: nil)

    the length to read

  • string (String) (defaults to: nil)

    an optional string

Returns:

  • (String, nil)


156
157
158
159
160
161
162
163
# File 'lib/store/digest/readwrapper.rb', line 156

def read maxlen = nil, string = nil
  unless @read.closed?
    out = @read.read maxlen, string
    cleanup if out.nil?

    out
  end
end