require '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 attr_accessor :throw_on_error 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 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 (Integer === options.first) || (String === options.first) return Enumerable::Enumerator.new(self, :each_with_packing, *options) unless block_given? yield read(*options) until eof? end def write_with_packing(*arg) (arg.length == 1) ? write_without_packing(*arg) : pack_and_write(*arg) end def read_with_packing(*arg) return read_without_packing(*arg) if (arg.length == 0) || arg.first.is_a?(Numeric) return *Packable::Packers.to_class_option_list(*arg).map do |klass, options, original| if eof? raise EOFError, "End of IO when attempting to read #{klass} with options #{original.inspect}" if @throw_on_eof nil elsif options[:read_packed] options[:read_packed].call(self) else klass.read_packed(self, options) end end 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