1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 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 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
|
require 'enumerator'
Enumerator = Enumerable::Enumerator unless defined?(Enumerator)
module Packable
module Extensions #:nodoc:
module IO
def self.included(base) #:nodoc:
base.alias_method_chain :read, :packing
base.alias_method_chain :write, :packing
base.alias_method_chain :each, :packing
end
# Methods supported by seekable streams.
SEEKABLE_API = %i[pos pos= seek rewind].freeze
# Check whether can seek without errors.
def seekable?
if !defined?(@seekable)
@seekable =
# The IO class throws an exception at runtime if we try to change
# position on a non-regular file.
if respond_to?(:stat)
stat.file?
else
# Duck-type the rest of this.
SEEKABLE_API.all? { |m| respond_to?(m) }
end
end
@seekable
end
# Returns the change in io.pos caused by the block.
# Has nothing to do with packing, but quite helpful and so simple...
def pos_change(&block)
delta =- pos
yield
delta += pos
end
# Usage:
# io >> Class
# io >> [Class, options]
# io >> :shortcut
def >> (options)
r = []
class << r
attr_accessor :stream
def >> (options)
self << stream.read(options)
end
end
r.stream = self
r >> options
end
# Returns (or yields) a modified IO object that will always pack/unpack when writing/reading.
def packed
packedio = clone
packedio.set_encoding("ascii-8bit") if packedio.respond_to? :set_encoding
class << packedio
def << (arg)
arg = [arg, :default] unless arg.instance_of?(::Array)
pack_and_write(*arg)
self
end
def packed
block_given? ? yield(self) : self
end
alias_method :write, :pack_and_write #bypass test for argument length
end
block_given? ? yield(packedio) : packedio
end
def each_with_packing(*options, &block)
return each_without_packing(*options, &block) if options.empty? || (Integer === options.first) || (String === options.first) || !seekable?
return self.to_enum(__method__, *options) unless block_given?
yield read(*options) until eof?
end
def write_with_packing(*arg)
(arg.length <= 1 || !seekable?) ? write_without_packing(*arg) : pack_and_write(*arg)
end
def read_with_packing(*arg)
return read_without_packing(*arg) if arg.empty? || arg.first.nil? || arg.first.is_a?(Numeric) || !seekable?
values = Packable::Packers.to_class_option_list(*arg).map do |klass, options, original|
if options[:read_packed]
options[:read_packed].call(self)
else
klass.read_packed(self, options)
end
end
return values.size > 1 ? values : values.first
end
# returns a string of exactly n bytes, or else raises an EOFError
def read_exactly(n)
return "" if n.zero?
s = read_without_packing(n)
raise EOFError if s.nil? || s.length < n
s
end
def pack_and_write(*arg)
original_pos = pos
Packable::Packers.to_object_option_list(*arg).each do |obj, options|
if options[:write_packed]
options[:write_packed].bind(obj).call(self)
else
obj.write_packed(self, options)
end
end
pos - original_pos
end
end
end
end
|