Module: IO::Stream::Readable
- Included in:
- Generic
- Defined in:
- lib/io/stream/readable.rb
Overview
A module providing readable stream functionality.
You must implement the sysread method to read data from the underlying IO. You may implement sysread_nonblock to support non-blocking partial peeks.
Constant Summary collapse
- ASYNC_SAFE =
{ read: :readable, read_partial: :readable, read_exactly: :readable, read_until: :readable, peek: :readable, peek_partial: :readable, gets: :readable, getc: :readable, getbyte: :readable, readline: :readable, readlines: :readable, readable?: true, fill_read_buffer: :readable, eof?: :readable, finished?: :readable, }.freeze
Instance Attribute Summary collapse
-
#minimum_read_size ⇒ Object
Returns the value of attribute minimum_read_size.
Class Method Summary collapse
-
.async_safe?(method) ⇒ Boolean
Check if a method is async-safe.
Instance Method Summary collapse
-
#block_size ⇒ Object
Legacy accessor for backwards compatibility.
-
#block_size=(value) ⇒ Object
Legacy setter for backwards compatibility.
-
#close_read ⇒ Object
Close the read end of the stream.
-
#discard_until(pattern, offset = 0, limit: nil) ⇒ Object
Efficiently discard data from the stream until encountering pattern.
-
#finish! ⇒ Object
(also: #eof!)
Mark the stream as finished and raise
EOFError. -
#finished? ⇒ Boolean
(also: #eof?)
Determins if the stream has consumed all available data.
-
#gets(separator = $/, limit = nil, chomp: false) ⇒ Object
Read a line from the stream, similar to IO#gets.
-
#initialize(minimum_read_size: MINIMUM_READ_SIZE, maximum_read_size: MAXIMUM_READ_SIZE, block_size: nil, &block) ⇒ Object
Initialize readable stream functionality.
-
#peek(size = nil) ⇒ Object
Peek at data in the buffer without consuming it.
-
#peek_partial(size = @minimum_read_size) ⇒ Object
Peek at data without consuming it, making at most one non-blocking read attempt.
-
#read(size = nil, buffer = nil) ⇒ Object
Read data from the stream.
-
#read_exactly(size, buffer = nil, exception: EOFError) ⇒ Object
Read exactly the specified number of bytes.
-
#read_partial(size = nil, buffer = nil) ⇒ Object
Read at most
sizebytes from the stream. -
#read_until(pattern, offset = 0, limit: nil, chomp: true) ⇒ Object
Efficiently read data from the stream until encountering pattern.
-
#readable? ⇒ Boolean
Whether there is a chance that a read operation will succeed or not.
-
#readpartial(size = nil, buffer = nil) ⇒ Object
This is a compatibility shim for existing code that uses
readpartial.
Instance Attribute Details
#minimum_read_size ⇒ Object
Returns the value of attribute minimum_read_size.
71 72 73 |
# File 'lib/io/stream/readable.rb', line 71 def minimum_read_size @minimum_read_size end |
Class Method Details
.async_safe?(method) ⇒ Boolean
Check if a method is async-safe.
50 51 52 |
# File 'lib/io/stream/readable.rb', line 50 def self.async_safe?(method) ASYNC_SAFE.fetch(method, false) end |
Instance Method Details
#block_size ⇒ Object
Legacy accessor for backwards compatibility
75 76 77 |
# File 'lib/io/stream/readable.rb', line 75 def block_size @minimum_read_size end |
#block_size=(value) ⇒ Object
Legacy setter for backwards compatibility
81 82 83 |
# File 'lib/io/stream/readable.rb', line 81 def block_size=(value) @minimum_read_size = value end |
#close_read ⇒ Object
Close the read end of the stream.
398 399 |
# File 'lib/io/stream/readable.rb', line 398 def close_read end |
#discard_until(pattern, offset = 0, limit: nil) ⇒ Object
Efficiently discard data from the stream until encountering pattern.
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/io/stream/readable.rb', line 229 def discard_until(pattern, offset = 0, limit: nil) if index = index_of(pattern, offset, limit, true) @read_buffer.freeze if limit and index >= limit @read_buffer = @read_buffer.byteslice(limit, @read_buffer.bytesize) return nil end matched = @read_buffer.byteslice(0, index+pattern.bytesize) @read_buffer = @read_buffer.byteslice(index+pattern.bytesize, @read_buffer.bytesize) return matched end end |
#finish! ⇒ Object Also known as: eof!
Mark the stream as finished and raise EOFError.
371 372 373 374 375 376 |
# File 'lib/io/stream/readable.rb', line 371 def finish! @read_buffer.clear @finished = true raise EOFError end |
#finished? ⇒ Boolean Also known as: eof?
Determins if the stream has consumed all available data. May block if the stream is not readable. See #readable? for a non-blocking alternative.
358 359 360 361 362 363 364 365 366 |
# File 'lib/io/stream/readable.rb', line 358 def finished? if !@read_buffer.empty? return false elsif @finished return true else return !self.fill_read_buffer end end |
#gets(separator = $/, limit = nil, chomp: false) ⇒ Object
Read a line from the stream, similar to IO#gets.
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
# File 'lib/io/stream/readable.rb', line 313 def gets(separator = $/, limit = nil, chomp: false) # Compatibility with IO#gets: if separator.is_a?(Integer) limit = separator separator = $/ end # We don't want to split in the middle of the separator, so we subtract the size of the separator from the start of the search: split_offset = separator.bytesize - 1 offset = 0 until index = @read_buffer.index(separator, offset) offset = @read_buffer.bytesize - split_offset offset = 0 if offset < 0 # If a limit was given, and the offset is beyond the limit, we should return up to the limit: if limit and offset >= limit # As we didn't find the separator, there is nothing to chomp either. return consume_read_buffer(limit) end # If we can't read any more data, we should return what we have: return consume_read_buffer unless fill_read_buffer end # If the index of the separator was beyond the limit: if limit and index >= limit # Return up to the limit: return consume_read_buffer(limit) end # Freeze the read buffer, as this enables us to use byteslice without generating a hidden copy: @read_buffer.freeze line = @read_buffer.byteslice(0, index+(chomp ? 0 : separator.bytesize)) @read_buffer = @read_buffer.byteslice(index+separator.bytesize, @read_buffer.bytesize) return line end |
#initialize(minimum_read_size: MINIMUM_READ_SIZE, maximum_read_size: MAXIMUM_READ_SIZE, block_size: nil, &block) ⇒ Object
Initialize readable stream functionality.
58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/io/stream/readable.rb', line 58 def initialize(minimum_read_size: MINIMUM_READ_SIZE, maximum_read_size: MAXIMUM_READ_SIZE, block_size: nil, **, &block) @finished = false @read_buffer = StringBuffer.new # Used as destination buffer for underlying reads. @input_buffer = StringBuffer.new # Support legacy block_size parameter for backwards compatibility @minimum_read_size = block_size || minimum_read_size @maximum_read_size = maximum_read_size super(**, &block) if defined?(super) end |
#peek(size = nil) ⇒ Object
Peek at data in the buffer without consuming it.
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/io/stream/readable.rb', line 249 def peek(size = nil) if size == 0 return String.new(encoding: Encoding::BINARY) end if size until @finished 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 @minimum_read_size to avoid lots of small reads: fill_read_buffer(read_size > @minimum_read_size ? read_size : @minimum_read_size) end return @read_buffer[..([size, @read_buffer.size].min - 1)] end until (block_given? && yield(@read_buffer)) or @finished fill_read_buffer end return @read_buffer end |
#peek_partial(size = @minimum_read_size) ⇒ Object
Peek at data without consuming it, making at most one non-blocking read attempt.
Any data read from the underlying stream is preserved in the read buffer. If
the read would block or the stream is at EOF, this method returns nil.
After this method returns nil, #readable? indicates whether the read would
block or EOF was observed.
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/io/stream/readable.rb', line 283 def peek_partial(size = @minimum_read_size) if size == 0 return String.new(encoding: Encoding::BINARY) end if @read_buffer.empty? if @finished return nil end read_size = [size, @maximum_read_size].min result = sysread_nonblock(read_size, @read_buffer) case result when :wait_readable, :wait_writable return nil when nil @finished = true return nil end end return @read_buffer.byteslice(0, [size, @read_buffer.bytesize].min) end |
#read(size = nil, buffer = nil) ⇒ Object
Read data from the stream.
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 |
# File 'lib/io/stream/readable.rb', line 89 def read(size = nil, buffer = nil) if size == 0 if buffer buffer.clear buffer.force_encoding(Encoding::BINARY) return buffer else return String.new(encoding: Encoding::BINARY) end end if size until @finished 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 @minimum_read_size to avoid lots of small reads: fill_read_buffer(read_size > @minimum_read_size ? read_size : @minimum_read_size) end else until @finished fill_read_buffer end if buffer buffer.replace(@read_buffer) @read_buffer.clear else buffer = @read_buffer @read_buffer = StringBuffer.new end # Read without size always returns a non-nil value, even if it is an empty string. return buffer end return consume_read_buffer(size, buffer) end |
#read_exactly(size, buffer = nil, exception: EOFError) ⇒ Object
Read exactly the specified number of bytes.
154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/io/stream/readable.rb', line 154 def read_exactly(size, buffer = nil, exception: EOFError) if buffer = read(size, buffer) if buffer.bytesize != size raise exception, "Could not read enough data!" end return buffer end raise exception, "Stream finished before reading enough data!" end |
#read_partial(size = nil, buffer = nil) ⇒ Object
Read at most size bytes from the stream. Will avoid reading from the underlying stream if possible.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/io/stream/readable.rb', line 132 def read_partial(size = nil, buffer = nil) if size == 0 if buffer buffer.clear buffer.force_encoding(Encoding::BINARY) return buffer else return String.new(encoding: Encoding::BINARY) end end if !@finished and @read_buffer.empty? fill_read_buffer end return consume_read_buffer(size, buffer) end |
#read_until(pattern, offset = 0, limit: nil, chomp: true) ⇒ Object
Efficiently read data from the stream until encountering pattern.
212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/io/stream/readable.rb', line 212 def read_until(pattern, offset = 0, limit: nil, chomp: true) if index = index_of(pattern, offset, limit) 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 end |
#readable? ⇒ Boolean
Whether there is a chance that a read operation will succeed or not.
382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'lib/io/stream/readable.rb', line 382 def readable? # If we are at the end of the file, we can't read any more data: if @finished 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, buffer = nil) ⇒ Object
This is a compatibility shim for existing code that uses readpartial.
170 171 172 |
# File 'lib/io/stream/readable.rb', line 170 def readpartial(size = nil, buffer = nil) read_partial(size, buffer) or raise EOFError, "Stream finished before reading enough data!" end |