module Packable # Packers for any packable class. class Packers < Hash SPECIAL = [:default, :merge_all].freeze # Usage: # PackableClass.packers.set :shortcut, :option => value, ... # PackableClass.packers { |p| p.set...; p.set... } # PackableClass.packers.set :shortcut, :another_shortcut # PackableClass.packers.set :shortcut do |packer| # packer.write{|io| io << self.something... } # packer.read{|io| Whatever.new(io.read(...)) } # end def set(key, options_or_shortcut={}) if block_given? packer = FilterCapture.new options_or_shortcut yield packer end self[key] = options_or_shortcut self end def initialize(klass) #:nodoc: @klass = klass end def lookup(key) #:nodoc: k = @klass begin if found = Packers.for(k)[key] return found end k = k.superclass end while k SPECIAL.include?(key) ? {} : raise("Unknown option #{key} for #{@klass}") end def finalize(options) #:nodoc: options = lookup(options) while options.is_a? Symbol lookup(:merge_all).merge(options) end @@packers_for_class = Hash.new{|h, klass| h[klass] = Packers.new(klass)} # Returns the configuration for the given +klass+. def self.for(klass) @@packers_for_class[klass] end def self.to_class_option_list(*arg) #:nodoc: r = [] until arg.empty? do k, options = original = arg.shift k, options = global_lookup(k) if k.is_a? Symbol raise TypeError, "Expected a class or symbol: #{k.inspect}" unless k.instance_of? Class options ||= arg.first.is_a?(Hash) ? arg.shift.tap{|o| original = [original, o]} : :default r << [k, k.packers.finalize(options), original] end r end def self.to_object_option_list(*arg) #:nodoc: r=[] until arg.empty? do obj = arg.shift options = case arg.first when Hash, Symbol arg.shift else :default end r << [obj, obj.class.packers.finalize(options)] end r end private def self.global_lookup(key) #:nodoc: @@packers_for_class.each do |klass, packers| if options = packers[key] return [klass, options] end end raise "Couldn't find packing option #{key}" end end # Use to capture the blocks given to read/write class FilterCapture #:nodoc: attr_accessor :options def initialize(options) self.options = options end def read(&block) options[:read_packed] = block end def write(&block) options[:write_packed] = block.unbind end end end