lib/bindata/io.rb in bindata-2.2.0 vs lib/bindata/io.rb in bindata-2.3.0
- old
+ new
@@ -2,10 +2,151 @@
module BinData
# A wrapper around an IO object. The wrapper provides a consistent
# interface for BinData objects to use when accessing the IO.
module IO
+
+ # Common operations for both Read and Write.
+ module Common
+ def initialize(io)
+ if self.class === io
+ raise ArgumentError, "io must not be a #{self.class}"
+ end
+
+ # wrap strings in a StringIO
+ if io.respond_to?(:to_str)
+ io = BinData::IO.create_string_io(io.to_str)
+ end
+
+ @raw_io = io
+ @buffer_end_pos = nil
+
+ extend seekable? ? SeekableStream : UnSeekableStream
+ stream_init
+ end
+
+ #-------------
+ private
+
+ def seekable?
+ @raw_io.pos
+ rescue NoMethodError, Errno::ESPIPE, Errno::EPIPE
+ nil
+ end
+
+ def seek(n)
+ seek_raw(buffer_limited_n(n))
+ end
+
+ def buffer_limited_n(n)
+ if @buffer_end_pos
+ if n.nil? or n > 0
+ max = @buffer_end_pos[1] - offset
+ n = max if n.nil? or n > max
+ else
+ min = @buffer_end_pos[0] - offset
+ n = min if n < min
+ end
+ end
+
+ n
+ end
+
+ def with_buffer_common(n, &block)
+ prev = @buffer_end_pos
+ if prev
+ avail = prev[1] - offset
+ n = avail if n > avail
+ end
+ @buffer_end_pos = [offset, offset + n]
+ begin
+ block.call(*@buffer_end_pos)
+ ensure
+ @buffer_end_pos = prev
+ end
+ end
+
+ # Use #seek and #pos on seekable streams
+ module SeekableStream
+ # The number of bytes remaining in the input stream.
+ def num_bytes_remaining
+ mark = @raw_io.pos
+ @raw_io.seek(0, ::IO::SEEK_END)
+ bytes_remaining = @raw_io.pos - mark
+ @raw_io.seek(mark, ::IO::SEEK_SET)
+
+ bytes_remaining
+ end
+
+ #-----------
+ private
+
+ def stream_init
+ @initial_pos = @raw_io.pos
+ end
+
+ def offset_raw
+ @raw_io.pos - @initial_pos
+ end
+
+ def seek_raw(n)
+ @raw_io.seek(n, ::IO::SEEK_CUR)
+ end
+
+ def read_raw(n)
+ @raw_io.read(n)
+ end
+
+ def write_raw(data)
+ @raw_io.write(data)
+ end
+ end
+
+ # Manually keep track of offset for unseekable streams.
+ module UnSeekableStream
+ def offset_raw
+ @offset
+ end
+
+ # The number of bytes remaining in the input stream.
+ def num_bytes_remaining
+ raise IOError, "stream is unseekable"
+ end
+
+ #-----------
+ private
+
+ def stream_init
+ @offset = 0
+ end
+
+ def read_raw(n)
+ data = @raw_io.read(n)
+ @offset += data.size if data
+ data
+ end
+
+ def write_raw(data)
+ @offset += data.size
+ @raw_io.write(data)
+ end
+
+ def seek_raw(n)
+ raise IOError, "stream is unseekable" if n < 0
+
+ # NOTE: how do we seek on a writable stream?
+
+ # skip over data in 8k blocks
+ while n > 0
+ bytes_to_read = [n, 8192].min
+ read_raw(bytes_to_read)
+ n -= bytes_to_read
+ end
+ end
+ end
+ end
+
# Creates a StringIO around +str+.
def self.create_string_io(str = "")
StringIO.new(str.dup.force_encoding(Encoding::BINARY))
end
@@ -25,47 +166,36 @@
#
# In little endian format:
# readbits(6), readbits(5) #=> [543210, a9876]
#
class Read
+ include Common
+
def initialize(io)
- raise ArgumentError, "io must not be a BinData::IO::Read" if BinData::IO::Read === io
+ super(io)
- # wrap strings in a StringIO
- if io.respond_to?(:to_str)
- io = BinData::IO.create_string_io(io.to_str)
- end
-
- @raw_io = io
-
# bits when reading
@rnbits = 0
@rval = 0
@rendian = nil
-
- @buffer_end_pos = nil
-
- extend seekable? ? SeekableStream : UnSeekableStream
end
# Sets a buffer of +n+ bytes on the io stream. Any reading or seeking
# calls inside the +block+ will be contained within this buffer.
def with_buffer(n, &block)
- prev = @buffer_end_pos
- if prev
- avail = prev - offset
- n = avail if n > avail
- end
- @buffer_end_pos = offset + n
- begin
+ with_buffer_common(n) do
block.call
read
- ensure
- @buffer_end_pos = prev
end
end
+ # Returns the current offset of the io stream. Offset will be rounded
+ # up when reading bitfields.
+ def offset
+ offset_raw
+ end
+
# Seek +n+ bytes from the current position in the io stream.
def seekbytes(n)
reset_read_bits
seek(n)
end
@@ -114,33 +244,14 @@
end
#---------------
private
- def seekable?
- @raw_io.pos
- rescue NoMethodError, Errno::ESPIPE, Errno::EPIPE
- nil
- end
-
- def seek(n)
- seek_raw(buffer_limited_n(n))
- end
-
def read(n = nil)
read_raw(buffer_limited_n(n))
end
- def buffer_limited_n(n)
- if @buffer_end_pos
- max = @buffer_end_pos - offset
- n = max if n.nil? or n > max
- end
-
- n
- end
-
def read_big_endian_bits(nbits)
while @rnbits < nbits
accumulate_big_endian_bits
end
@@ -182,142 +293,56 @@
end
def mask(nbits)
(1 << nbits) - 1
end
-
- # Use #seek and #pos on seekable streams
- module SeekableStream
- # Returns the current offset of the io stream. Offset will be rounded
- # up when reading bitfields.
- def offset
- raw_io.pos - @initial_pos
- end
-
- # The number of bytes remaining in the input stream.
- def num_bytes_remaining
- mark = raw_io.pos
- raw_io.seek(0, ::IO::SEEK_END)
- bytes_remaining = raw_io.pos - mark
- raw_io.seek(mark, ::IO::SEEK_SET)
-
- bytes_remaining
- end
-
- #-----------
- private
-
- def read_raw(n)
- raw_io.read(n)
- end
-
- def seek_raw(n)
- raw_io.seek(n, ::IO::SEEK_CUR)
- end
-
- def raw_io
- @initial_pos ||= @raw_io.pos
- @raw_io
- end
- end
-
- # Manually keep track of offset for unseekable streams.
- module UnSeekableStream
- # Returns the current offset of the io stream. Offset will be rounded
- # up when reading bitfields.
- def offset
- @read_count ||= 0
- end
-
- # The number of bytes remaining in the input stream.
- def num_bytes_remaining
- raise IOError, "stream is unseekable"
- end
-
- #-----------
- private
-
- def read_raw(n)
- @read_count ||= 0
-
- data = @raw_io.read(n)
- @read_count += data.size if data
- data
- end
-
- def seek_raw(n)
- raise IOError, "stream is unseekable" if n < 0
-
- # skip over data in 8k blocks
- while n > 0
- bytes_to_read = [n, 8192].min
- read_raw(bytes_to_read)
- n -= bytes_to_read
- end
- end
- end
end
# Create a new IO Write wrapper around +io+. +io+ must provide #write.
# If +io+ is a string it will be automatically wrapped in an StringIO
# object.
#
# The IO can handle bitstreams in either big or little endian format.
#
# See IO::Read for more information.
class Write
+ include Common
def initialize(io)
- if BinData::IO::Write === io
- raise ArgumentError, "io must not be a BinData::IO::Write"
- end
+ super(io)
- # wrap strings in a StringIO
- if io.respond_to?(:to_str)
- io = BinData::IO.create_string_io(io.to_str)
- end
-
- @raw_io = io
-
@wnbits = 0
@wval = 0
@wendian = nil
-
- @write_count = 0
-
- @bytes_remaining = nil
end
# Sets a buffer of +n+ bytes on the io stream. Any writes inside the
# +block+ will be contained within this buffer. If less than +n+ bytes
# are written inside the block, the remainder will be padded with '\0'
# bytes.
def with_buffer(n, &block)
- prev = @bytes_remaining
- if prev
- n = prev if n > prev
- prev -= n
- end
-
- @bytes_remaining = n
- begin
+ with_buffer_common(n) do |buf_start, buf_end|
block.call
- write_raw("\0" * @bytes_remaining)
- ensure
- @bytes_remaining = prev
+ write("\0" * (buf_end - offset))
end
end
# Returns the current offset of the io stream. Offset will be rounded
# up when writing bitfields.
def offset
- @write_count + (@wnbits > 0 ? 1 : 0)
+ offset_raw + (@wnbits > 0 ? 1 : 0)
end
+ # Seek +n+ bytes from the current position in the io stream.
+ def seekbytes(n)
+ flushbits
+ seek(n)
+ end
+
# Writes the given string of bytes to the io stream.
def writebytes(str)
flushbits
- write_raw(str)
+ write(str)
end
# Writes +nbits+ bits from +val+ to the stream. +endian+ specifies whether
# the bits are to be stored in +:big+ or +:little+ endian format.
def writebits(val, nbits, endian)
@@ -347,20 +372,29 @@
alias_method :flush, :flushbits
#---------------
private
+ def write(data)
+ n = buffer_limited_n(data.size)
+ if n < data.size
+ data = data[0, n]
+ end
+
+ write_raw(data)
+ end
+
def write_big_endian_bits(val, nbits)
while nbits > 0
bits_req = 8 - @wnbits
if nbits >= bits_req
msb_bits = (val >> (nbits - bits_req)) & mask(bits_req)
nbits -= bits_req
val &= mask(nbits)
@wval = (@wval << bits_req) | msb_bits
- write_raw(@wval.chr)
+ write(@wval.chr)
@wval = 0
@wnbits = 0
else
@wval = (@wval << nbits) | val
@@ -377,31 +411,19 @@
lsb_bits = val & mask(bits_req)
nbits -= bits_req
val >>= bits_req
@wval = @wval | (lsb_bits << @wnbits)
- write_raw(@wval.chr)
+ write(@wval.chr)
@wval = 0
@wnbits = 0
else
@wval = @wval | (val << @wnbits)
@wnbits += nbits
nbits = 0
end
end
- end
-
- def write_raw(data)
- if @bytes_remaining
- if data.size > @bytes_remaining
- data = data[0, @bytes_remaining]
- end
- @bytes_remaining -= data.size
- end
-
- @write_count += data.size
- @raw_io.write(data)
end
def mask(nbits)
(1 << nbits) - 1
end