# File lib/map.rb, line 88 def add_conversion_method!(method) if define_conversion_method!(method) method = method.to_s.strip raise ArguementError if method.empty? module_eval(" unless conversion_methods.include?(#{ method.inspect }) conversion_methods.unshift(#{ method.inspect }) end ", __FILE__, __LINE__) true else false end end
# File lib/map.rb, line 29 def allocate super.instance_eval do @keys = [] self end end
# File lib/map.rb, line 827 def Map.alphanumeric_key_for(key) return key if Numeric===key key.to_s =~ %r^\d+$/ ? Integer(key) : key end
# File lib/map.rb, line 696 def Map.blank?(value) return value.blank? if value.respond_to?(:blank?) case value when String value.strip.empty? when Numeric value == 0 when false true else value.respond_to?(:empty?) ? value.empty? : !value end end
# File lib/map.rb, line 934 def Map.breadth_first_each(enumerable, accum = [], &block) levels = [] keys = Map.depth_first_keys(enumerable) keys.each do |key| key.size.times do |i| k = key.slice(0, i + 1) level = k.size - 1 levels[level] ||= Array.new last = levels[level].last levels[level].push(k) unless last == k end end levels.each do |level| level.each do |key| val = enumerable.get(key) block ? block.call(key, val) : accum.push([key, val]) end end block ? enumerable : accum end
# File lib/map.rb, line 50 def coerce(other) case other when Map other else allocate.update(other.to_hash) end end
# File lib/map.rb, line 59 def conversion_methods @conversion_methods ||= ( map_like = ancestors.select{|ancestor| ancestor <= Map} type_names = map_like.map do |ancestor| name = ancestor.name.to_s.strip next if name.empty? name.downcase.gsub(%r::/, '_') end.compact list = type_names.map{|type_name| "to_#{ type_name }"} list.each{|method| define_conversion_method!(method)} list ) end
def self.convert_key(key)
key.kind_of?(Symbol) ? key.to_s : key
end
# File lib/map.rb, line 239 def self.convert_key(key) key = key.kind_of?(Symbol) ? key.to_s : key end
# File lib/map.rb, line 251 def self.convert_value(value) conversion_methods.each do |method| #return convert_value(value.send(method)) if value.respond_to?(method) hashlike = value.is_a?(Hash) if hashlike and value.respond_to?(method) value = value.send(method) break end end case value when Hash coerce(value) when Array value.map{|v| convert_value(v)} else value end end
# File lib/map.rb, line 73 def define_conversion_method!(method) method = method.to_s.strip raise ArguementError if method.empty? module_eval(" unless public_method_defined?(#{ method.inspect }) def #{ method } self end true else false end ", __FILE__, __LINE__) end
TODO - technically this returns only leaves so the name isn't quite right. re-factor for 3.0
# File lib/map.rb, line 883 def Map.depth_first_each(enumerable, path = [], accum = [], &block) Map.pairs_for(enumerable) do |key, val| path.push(key) if((val.is_a?(Hash) or val.is_a?(Array)) and not val.empty?) Map.depth_first_each(val, path, accum) else accum << [path.dup, val] end path.pop() end if block accum.each{|keys, val| block.call(keys, val)} else accum end end
# File lib/map.rb, line 900 def Map.depth_first_keys(enumerable, path = [], accum = [], &block) accum = Map.depth_first_each(enumerable, path = [], accum = [], &block) accum.map!{|kv| kv.first} accum end
# File lib/map.rb, line 906 def Map.depth_first_values(enumerable, path = [], accum = [], &block) accum = Map.depth_first_each(enumerable, path = [], accum = [], &block) accum.map!{|kv| kv.last} accum end
key path support
# File lib/map.rb, line 838 def self.dot_key_for(*keys) dot = keys.compact.flatten.join('.') dot.split(%r\s*[,.]\s*/).map{|part| part =~ %r^\d+$/ ? Integer(part) : part} end
# File lib/map.rb, line 843 def self.dot_keys @@dot_keys = {} unless defined?(@@dot_keys) @@dot_keys end
# File lib/map.rb, line 860 def self.dot_keys!(boolean = true) dot_keys[self] = !!boolean end
# File lib/map.rb, line 848 def self.dot_keys? ancestors.each do |ancestor| return dot_keys[ancestor] if dot_keys.has_key?(ancestor) end false end
iterate over arguments in pairs smartly.
# File lib/map.rb, line 105 def each_pair(*args, &block) size = args.size parity = size % 2 == 0 ? :even : :odd first = args.first if block.nil? result = [] block = lambda{|*kv| result.push(kv)} else result = args end return args if size == 0 if size == 1 conversion_methods.each do |method| if first.respond_to?(method) first = first.send(method) break end end if first.respond_to?(:each_pair) first.each_pair do |key, val| block.call(key, val) end return args end if first.respond_to?(:each_slice) first.each_slice(2) do |key, val| block.call(key, val) end return args end raise(ArgumentError, 'odd number of arguments for Map') end array_of_pairs = args.all?{|a| a.is_a?(Array) and a.size == 2} if array_of_pairs args.each do |pair| key, val, *ignored = pair block.call(key, val) end else 0.step(args.size - 1, 2) do |a| key = args[a] val = args[a + 1] block.call(key, val) end end args end
# File lib/map.rb, line 43 def for(*args, &block) if(args.size == 1 and block.nil?) return args.first if args.first.class == self end new(*args, &block) end
support for building ordered hasshes from a map's own image
# File lib/map.rb, line 512 def Map.from_hash(hash, order = nil) map = Map.for(hash) map.reorder!(order) if order map end
# File lib/map.rb, line 163 def intersection(a, b) a, b, i = Map.for(a), Map.for(b), Map.new a.depth_first_each{|key, val| i.set(key, val) if b.has?(key)} i end
# File lib/map.rb, line 868 def self.key_for(*keys) return keys.flatten unless dot_keys? self.dot_key_for(*keys) end
# File lib/map.rb, line 959 def Map.keys_for(enumerable) keys = enumerable.respond_to?(:keys) ? enumerable.keys : Array.new(enumerable.size){|i| i} end
# File lib/map.rb, line 11 def libdir(*args, &block) @libdir ||= File.expand_path(__FILE__).sub(%r\.rb$/,'') libdir = args.empty? ? @libdir : File.join(@libdir, *args.map{|arg| arg.to_s}) ensure if block begin $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.first==libdir module_eval(&block) ensure $LOAD_PATH.shift() if $LOAD_PATH.first==libdir end end end
# File lib/map.rb, line 25 def load(*args, &block) libdir{ Load.call(*args, &block) } end
# File lib/map.rb, line 224 def Map.map_for(hash) map = klass.coerce(hash) map.default = hash.default map end
# File lib/map.rb, line 169 def match(haystack, needle) intersection(haystack, needle) == needle end
# File lib/map.rb, line 36 def new(*args, &block) allocate.instance_eval do initialize(*args, &block) self end end
# File lib/map.rb, line 180 def initialize(*args, &block) case args.size when 0 super(&block) when 1 first = args.first case first when nil, false nil when Hash initialize_from_hash(first) when Array initialize_from_array(first) else if first.respond_to?(:to_hash) initialize_from_hash(first.to_hash) else initialize_from_hash(first) end end else initialize_from_array(args) end end
# File lib/map/options.rb, line 145 def Map.options_for(*args, &block) Map::Options.for(*args, &block) end
# File lib/map/options.rb, line 149 def Map.options_for!(*args, &block) Map::Options.for(*args, &block).pop end
# File lib/map.rb, line 912 def Map.pairs_for(enumerable, *args, &block) if block.nil? pairs, block = [], lambda{|*pair| pairs.push(pair)} else pairs = false end result = case enumerable when Hash enumerable.each_pair(*args, &block) when Array enumerable.each_with_index(*args) do |val, key| block.call(key, val) end else enumerable.each_pair(*args, &block) end pairs ? pairs : result end
# File lib/map/struct.rb, line 49 def Map.struct(*args, &block) new(*args, &block).struct end
# File lib/map/options.rb, line 153 def Map.update_options_for!(args, &block) options = Map.options_for(args) block.call(options) end
# File lib/map.rb, line 7 def version Map::Version end
# File lib/map.rb, line 486 def <=>(other) cmp = keys <=> klass.coerce(other).keys return cmp unless cmp.zero? values <=> klass.coerce(other).values end
equality / sorting / matching support
# File lib/map.rb, line 472 def ==(other) case other when Map return false if keys != other.keys super(other) when Hash self == Map.from_hash(other, self) else false end end
# File lib/map.rb, line 492 def =~(hash) to_hash == klass.coerce(hash).to_hash end
# File lib/map.rb, line 324 def [](key) key = convert_key(key) __get__(key) end
# File lib/map.rb, line 317 def []=(key, val) key, val = convert(key, val) keys.push(key) unless has_key?(key) __set__(key, val) end
# File lib/map.rb, line 832 def alphanumeric_key_for(key) Map.alphanumeric_key_for(key) end
# File lib/map.rb, line 820 def apply(other) Map.for(other).depth_first_each do |keys, value| set(keys => value) unless !get(keys).nil? end self end
# File lib/map.rb, line 691 def blank?(*keys) return empty? if keys.empty? !has?(*keys) or Map.blank?(get(*keys)) end
# File lib/map.rb, line 975 def breadth_first_each(*args, &block) Map.breadth_first_each(enumerable=self, *args, &block) end
# File lib/map.rb, line 411 def clear keys.clear super end
# File lib/map.rb, line 299 def clone copy end
# File lib/map.rb, line 711 def collection_has_key?(collection, key) case collection when Hash collection.has_key?(key) when Array return false unless key (0...collection.size).include?(Integer(key)) end end
# File lib/map.rb, line 979 def contains(other) other = other.is_a?(Hash) ? Map.coerce(other) : other breadth_first_each{|key, value| return true if value == other} return false end
conversions
# File lib/map.rb, line 547 def conversion_methods self.class.conversion_methods end
# File lib/map.rb, line 279 def convert(key, val) [convert_key(key), convert_value(val)] end
# File lib/map.rb, line 243 def convert_key(key) if klass.respond_to?(:convert_key) klass.convert_key(key) else Map.convert_key(key) end end
# File lib/map.rb, line 270 def convert_value(value) if klass.respond_to?(:convert_value) klass.convert_value(value) else Map.convert_value(value) end end
# File lib/map.rb, line 283 def copy default = self.default self.default = nil copy = Marshal.load(Marshal.dump(self)) rescue Dup.bind(self).call() copy.default = default copy ensure self.default = default end
# File lib/map.rb, line 303 def default(key = nil) key.is_a?(Symbol) && include?(key = key.to_s) ? self[key] : super end
# File lib/map.rb, line 307 def default=(value) raise ArgumentError.new("Map doesn't work so well with a non-nil default value!") unless value.nil? end
mutators
# File lib/map.rb, line 405 def delete(key) key = convert_key(key) keys.delete(key) super(key) end
# File lib/map.rb, line 416 def delete_if to_delete = [] keys.each{|key| to_delete.push(key) if yield(key)} to_delete.each{|key| delete(key)} self end
# File lib/map.rb, line 963 def depth_first_each(*args, &block) Map.depth_first_each(enumerable=self, *args, &block) end
# File lib/map.rb, line 967 def depth_first_keys(*args, &block) Map.depth_first_keys(enumerable=self, *args, &block) end
# File lib/map.rb, line 971 def depth_first_values(*args, &block) Map.depth_first_values(enumerable=self, *args, &block) end
# File lib/map.rb, line 998 def deserialize(object) ::Map.for(object) end
# File lib/map.rb, line 864 def dot_keys!(boolean = true) @dot_keys = !!boolean end
# File lib/map.rb, line 855 def dot_keys? @dot_keys = false unless defined?(@dot_keys) @dot_keys end
# File lib/map.rb, line 295 def dup copy end
# File lib/map.rb, line 397 def each keys.each{|key| yield(key, self[key])} self end
# File lib/map.rb, line 387 def each_key keys.each{|key| yield(key)} self end
# File lib/map.rb, line 392 def each_value keys.each{|key| yield self[key]} self end
iterator methods
# File lib/map.rb, line 382 def each_with_index keys.each_with_index{|key, index| yield([key, self[key]], index)} self end
for rails' extract_options! compat
# File lib/map.rb, line 988 def extractable_options? true end
# File lib/map.rb, line 329 def fetch(key, *args, &block) key = convert_key(key) super(key, *args, &block) end
# File lib/map.rb, line 372 def first [keys.first, self[keys.first]] end
# File lib/map.rb, line 799 def forcing(forcing=nil, &block) @forcing ||= nil if block begin previous = @forcing @forcing = forcing block.call() ensure @forcing = previous end else @forcing end end
# File lib/map.rb, line 815 def forcing?(forcing=nil) @forcing ||= nil @forcing == forcing end
support for compound key indexing and depth first iteration
# File lib/map.rb, line 641 def get(*keys) keys = key_for(keys) if keys.size <= 1 if !self.has_key?(keys.first) && block_given? return yield else return self[keys.first] end end keys, key = keys[0..-2], keys[-1] collection = self keys.each do |k| k = alphanumeric_key_for(k) if collection_has_key?(collection, k) collection = collection[k] else collection = nil end unless collection.respond_to?('[]') leaf = collection return leaf end end alphanumeric_key = alphanumeric_key_for(key) if !collection_has_key?(collection, alphanumeric_key) && block_given? yield else collection[alphanumeric_key] end end
# File lib/map.rb, line 673 def has?(*keys) keys = key_for(keys) collection = self return collection_has_key?(collection, keys.first) if keys.size <= 1 keys, key = keys[0..-2], keys[-1] keys.each do |k| k = alphanumeric_key_for(k) if collection_has_key?(collection, k) collection = collection[k] else collection = nil end return collection unless collection.respond_to?('[]') end return false unless(collection.is_a?(Hash) or collection.is_a?(Array)) collection_has_key?(collection, alphanumeric_key_for(key)) end
# File lib/map.rb, line 633 def id return self[:id] if has_key?(:id) return self[:_id] if has_key?(:_id) raise NoMethodError end
# File lib/map.rb, line 213 def initialize_from_array(array) map = self Map.each_pair(array){|key, val| map[key] = val} end
# File lib/map.rb, line 207 def initialize_from_hash(hash) map = self map.update(hash) map.default = hash.default end
# File lib/map.rb, line 540 def inspect(*args, &block) require 'pp' unless defined?(PP) PP.pp(self, '') end
# File lib/map.rb, line 518 def invert inverted = klass.allocate inverted.default = self.default keys.each{|key| inverted[self[key]] = key } inverted end
# File lib/map.rb, line 334 def key?(key) super(convert_key(key)) end
# File lib/map.rb, line 873 def key_for(*keys) if dot_keys? self.class.dot_key_for(*keys) else self.class.key_for(*keys) end end
instance constructor
# File lib/map.rb, line 176 def keys @keys ||= [] end
support methods
# File lib/map.rb, line 220 def klass self.class end
# File lib/map.rb, line 376 def last [keys.last, self[keys.last]] end
# File lib/map.rb, line 229 def map_for(hash) klass.map_for(hash) end
# File lib/map.rb, line 347 def merge(*args) copy.update(*args) end
a sane method missing that only supports writing values or reading *previously set* values
# File lib/map.rb, line 608 def method_missing(*args, &block) method = args.first.to_s case method when %r=$/ key = args.shift.to_s.chomp('=') value = args.shift self[key] = value when %r\?$/ key = args.shift.to_s.chomp('?') self.has?( key ) else key = method unless has_key?(key) return(block ? fetch(key, &block) : super(*args)) end self[key] end end
# File lib/map.rb, line 462 def pop unless empty? key = keys.last val = delete(key) [key, val] end end
# File lib/map.rb, line 450 def push(*args) Map.each_pair(*args) do |key, val| if key?(key) delete(key) else keys.push(key) end __set__(key, val) end self end
# File lib/map.rb, line 525 def reject(&block) dup.delete_if(&block) end
# File lib/map.rb, line 529 def reject!(&block) hash = reject(&block) self == hash ? nil : hash end
reordering support
# File lib/map.rb, line 498 def reorder(order = {}) order = Map.for(order) map = Map.new keys = order.depth_first_keys | depth_first_keys keys.each{|key| map.set(key, get(key))} map end
# File lib/map.rb, line 506 def reorder!(order = {}) replace(reorder(order)) end
# File lib/map.rb, line 423 def replace(*args) clear update(*args) end
# File lib/map.rb, line 627 def respond_to?(method, *args, &block) has_key = has_key?(method) setter = method.to_s =~ %r=\Z/ !!((!has_key and setter) or has_key or super) end
# File lib/map.rb, line 351 def reverse_merge(hash) map = copy hash.each{|key, val| map[key] = val unless map.key?(key)} map end
# File lib/map.rb, line 357 def reverse_merge!(hash) replace(reverse_merge(hash)) end
# File lib/map.rb, line 772 def rm(*args) paths, path = args.partition{|arg| arg.is_a?(Array)} paths.push(path) paths.each do |path| if path.size == 1 delete(*path) next end branch, leaf = path[0..-2], path[-1] collection = get(branch) case collection when Hash key = leaf collection.delete(key) when Array index = leaf collection.delete_at(index) else raise(IndexError, "(#{ collection.inspect }).rm(#{ path.inspect })") end end paths end
# File lib/map.rb, line 534 def select array = [] each{|key, val| array << [key,val] if yield(key, val)} array end
for mongoid type system support
# File lib/map.rb, line 994 def serialize(object) ::Map.for(object) end
# File lib/map.rb, line 721 def set(*args) if args.size == 1 and args.first.is_a?(Hash) spec = args.shift else spec = {} value = args.pop keys = args spec[keys] = value end spec.each do |keys, value| keys = Array(keys).flatten collection = self keys = key_for(keys) if keys.size <= 1 key = keys.first collection[key] = value next end key = nil keys.each_cons(2) do |a, b| a, b = alphanumeric_key_for(a), alphanumeric_key_for(b) exists = collection_has_key?(collection, a) case b when Numeric #collection[a] ||= [] collection[a] = [] unless exists raise(IndexError, "(#{ collection.inspect })[#{ a.inspect }]=#{ value.inspect }") unless collection[a].is_a?(Array) when String, Symbol #collection[a] ||= {} collection[a] = {} unless exists raise(IndexError, "(#{ collection.inspect })[#{ a.inspect }]=#{ value.inspect }") unless collection[a].is_a?(Hash) end collection = collection[a] key = b end collection[key] = value end return spec.values end
ordered container specific methods
# File lib/map.rb, line 430 def shift unless empty? key = keys.first val = delete(key) [key, val] end end
# File lib/map.rb, line 597 def stringify_keys; dup end
oh rails - would that map.rb existed before all this non-sense...
# File lib/map.rb, line 596 def stringify_keys!; self end
# File lib/map/struct.rb, line 45 def struct @struct ||= Struct.new(map=self) end
# File lib/map.rb, line 599 def symbolize_keys; dup end
# File lib/map.rb, line 598 def symbolize_keys!; self end
# File lib/map.rb, line 575 def to_array array = [] each{|*pair| array.push(pair)} array end
# File lib/map.rb, line 559 def to_hash hash = Hash.new(default) each do |key, val| val = val.to_hash if val.respond_to?(:to_hash) hash[key] = val end hash end
# File lib/map.rb, line 582 def to_list list = [] each_pair do |key, val| list[key.to_i] = val if(key.is_a?(Numeric) or key.to_s =~ %r^\d+$/) end list end
# File lib/map.rb, line 601 def to_options; dup end
# File lib/map.rb, line 600 def to_options!; self end
# File lib/map.rb, line 590 def to_s to_array.to_s end
# File lib/map.rb, line 568 def to_yaml( opts = {} ) map = self YAML.quick_emit(self.object_id, opts){|out| out.map('!omap'){|m| map.each{|k,v| m.add(k, v)}} } end
# File lib/map.rb, line 438 def unshift(*args) Map.each_pair(*args) do |key, val| if key?(key) delete(key) else keys.unshift(key) end __set__(key, val) end self end
# File lib/map.rb, line 341 def update(*args) Map.each_pair(*args){|key, val| store(key, val)} self end
# File lib/map.rb, line 361 def values array = [] keys.each{|key| array.push(self[key])} array end
# File lib/map.rb, line 368 def values_at(*keys) keys.map{|key| self[key]} end
# File lib/map.rb, line 603 def with_indifferent_access; dup end
# File lib/map.rb, line 602 def with_indifferent_access!; self end