Class: IO::Stream::Generic

Inherits:
Object
  • Object
show all
Defined in:
lib/io/stream/generic.rb

Direct Known Subclasses

Buffered

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(block_size: BLOCK_SIZE, maximum_read_size: MAXIMUM_READ_SIZE) ⇒ Generic

Returns a new instance of Generic.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/io/stream/generic.rb', line 22

def initialize(block_size: BLOCK_SIZE, maximum_read_size: MAXIMUM_READ_SIZE)
  @eof = false
  
  @writing = ::Thread::Mutex.new
  
  @block_size = block_size
  @maximum_read_size = maximum_read_size
  
  @read_buffer = StringBuffer.new
  @write_buffer = StringBuffer.new
  
  # Used as destination buffer for underlying reads.
  @input_buffer = StringBuffer.new
end

Instance Attribute Details

#block_sizeObject

Returns the value of attribute block_size.



37
38
39
# File 'lib/io/stream/generic.rb', line 37

def block_size
  @block_size
end

Instance Method Details

#<<(string) ⇒ Object

Writes ‘string` to the stream and returns self.



173
174
175
176
177
# File 'lib/io/stream/generic.rb', line 173

def <<(string)
  write(string)
  
  return self
end

#closeObject

Best effort to flush any unwritten data, and then close the underling IO.



203
204
205
206
207
208
209
210
211
212
213
# File 'lib/io/stream/generic.rb', line 203

def close
  return if closed?
  
  begin
    flush
  rescue
    # We really can't do anything here unless we want #close to raise exceptions.
  ensure
    self.sysclose
  end
end

#close_readObject



195
196
# File 'lib/io/stream/generic.rb', line 195

def close_read
end

#close_writeObject



198
199
200
# File 'lib/io/stream/generic.rb', line 198

def close_write
  flush
end

#closed?Boolean

Returns:

  • (Boolean)


191
192
193
# File 'lib/io/stream/generic.rb', line 191

def closed?
  false
end

#eof!Object

Raises:

  • (EOFError)


229
230
231
232
233
234
# File 'lib/io/stream/generic.rb', line 229

def eof!
  @read_buffer.clear
  @eof = true
  
  raise EOFError
end

#eof?Boolean

Determins if the stream has consumed all available data. May block if the stream is not readable. See #readable? for a non-blocking alternative.

Returns:

  • (Boolean)


219
220
221
222
223
224
225
226
227
# File 'lib/io/stream/generic.rb', line 219

def eof?
  if !@read_buffer.empty?
    return false
  elsif @eof
    return true
  else
    return !self.fill_read_buffer
  end
end

#flushObject

Flushes buffered data to the stream.



146
147
148
149
150
151
152
# File 'lib/io/stream/generic.rb', line 146

def flush
  return if @write_buffer.empty?
  
  @writing.synchronize do
    self.drain(@write_buffer)
  end
end

#gets(separator = $/, **options) ⇒ Object



132
133
134
# File 'lib/io/stream/generic.rb', line 132

def gets(separator = $/, **options)
  read_until(separator, **options)
end

#peek(size = nil) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/io/stream/generic.rb', line 115

def peek(size = nil)
  if size
    until @eof or @read_buffer.bytesize >= size
      # Compute the amount of data we need to read from the underlying stream:
      read_size = size - @read_buffer.bytesize
      
      # Don't read less than @block_size to avoid lots of small reads:
      fill_read_buffer(read_size > @block_size ? read_size : @block_size)
    end
    return @read_buffer[..([size, @read_buffer.size].min - 1)]
  end
  until (block_given? && yield(@read_buffer)) or @eof
    fill_read_buffer
  end
  return @read_buffer
end

#puts(*arguments, separator: $/) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/io/stream/generic.rb', line 179

def puts(*arguments, separator: $/)
  return if arguments.empty?
  
  @writing.synchronize do
    arguments.each do |argument|
      @write_buffer << argument << separator
    end
    
    self.drain(@write_buffer)
  end
end

#read(size = nil) ⇒ Object

Reads ‘size` bytes from the stream. If size is not specified, read until end of file.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/io/stream/generic.rb', line 40

def read(size = nil)
  return String.new(encoding: Encoding::BINARY) if size == 0
  
  if size
    until @eof or @read_buffer.bytesize >= size
      # Compute the amount of data we need to read from the underlying stream:
      read_size = size - @read_buffer.bytesize
      
      # Don't read less than @block_size to avoid lots of small reads:
      fill_read_buffer(read_size > @block_size ? read_size : @block_size)
    end
  else
    until @eof
      fill_read_buffer
    end
  end
  
  return consume_read_buffer(size)
end

#read_exactly(size, exception: EOFError) ⇒ Object

Raises:

  • (exception)


71
72
73
74
75
76
77
78
79
80
81
# File 'lib/io/stream/generic.rb', line 71

def read_exactly(size, exception: EOFError)
  if buffer = read(size)
    if buffer.bytesize != size
      raise exception, "could not read enough data"
    end
    
    return buffer
  end
  
  raise exception, "encountered eof while reading data"
end

#read_partial(size = nil) ⇒ Object

Read at most ‘size` bytes from the stream. Will avoid reading from the underlying stream if possible.



61
62
63
64
65
66
67
68
69
# File 'lib/io/stream/generic.rb', line 61

def read_partial(size = nil)
  return String.new(encoding: Encoding::BINARY) if size == 0

  if !@eof and @read_buffer.empty?
    fill_read_buffer
  end
  
  return consume_read_buffer(size)
end

#read_until(pattern, offset = 0, limit: nil, chomp: true) ⇒ Object

Efficiently read data from the stream until encountering pattern.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/io/stream/generic.rb', line 93

def read_until(pattern, offset = 0, limit: nil, chomp: true)
  # We don't want to split on the pattern, so we subtract the size of the pattern.
  split_offset = pattern.bytesize - 1
  
  until index = @read_buffer.index(pattern, offset)
    offset = @read_buffer.bytesize - split_offset
    
    offset = 0 if offset < 0
    
    return nil if limit and offset >= limit
    return nil unless fill_read_buffer
  end
  
  return nil if limit and index >= limit
  
  @read_buffer.freeze
  matched = @read_buffer.byteslice(0, index+(chomp ? 0 : pattern.bytesize))
  @read_buffer = @read_buffer.byteslice(index+pattern.bytesize, @read_buffer.bytesize)
  
  return matched
end

#readable?Boolean

Whether there is a chance that a read operation will succeed or not.

Returns:

  • (Boolean)


238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/io/stream/generic.rb', line 238

def readable?
  # If we are at the end of the file, we can't read any more data:
  if @eof
    return false
  end
  
  # If the read buffer is not empty, we can read more data:
  if !@read_buffer.empty?
    return true
  end
  
  # If the underlying stream is readable, we can read more data:
  return !closed?
end

#readpartial(size = nil) ⇒ Object

This is a compatibility shim for existing code that uses ‘readpartial`.



84
85
86
# File 'lib/io/stream/generic.rb', line 84

def readpartial(size = nil)
  read_partial(size) or raise EOFError, "Encountered eof while reading data!"
end

#write(string, flush: false) ⇒ Object

Writes ‘string` to the buffer. When the buffer is full or #sync is true the buffer is flushed to the underlying `io`.



158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/io/stream/generic.rb', line 158

def write(string, flush: false)
  @writing.synchronize do
    @write_buffer << string
    
    flush |= (@write_buffer.bytesize >= @block_size)
    
    if flush
      self.drain(@write_buffer)
    end
  end
  
  return string.bytesize
end