Class: Hash

Inherits:
Object show all
Defined in:
lib/y_support/name_magic/hash.rb,
lib/y_support/core_ext/hash/misc.rb,
lib/y_support/typing/hash/typing.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.method_added(sym) ⇒ Object

This kludge method guards against overwriting of the #slice method by ActiveSupport.



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
# File 'lib/y_support/core_ext/hash/misc.rb', line 8

def method_added( sym )
  if sym == :slice then
    # Unless it is our method, overwrite it.
    unless instance_method( sym ).source_location.include? 'y_support'
      # Let's now make a cache of this very method being called
      ma = singleton_class.instance_method :method_added
      # Let's remove the :method_added hook, or otherwise infinite recursion
      # would ensue.
      singleton_class.class_exec { remove_method :method_added }
      # And let's redefine the +:slice+ method now:
      warn "Warning: Attempt to redefine Hash##{sym} occured, reverting." if YSupport::DEBUG

      class_exec do
        # A bit like Array#slice, but only takes 1 argument, which is either
        # a Range, or an Array, and returns the selection of the hash for
        # the keys that match the range or are present in the array.
        # 
        define_method sym do |matcher|
          self.class[ case matcher
                      when Array then select { |k, _| matcher.include? k }
                      else select { |k, _| matcher === k } end ]
        end
      end

      # Finally, let's bind the +:method_added+ method to self again.
      singleton_class.class_exec do
        define_method :method_added do |sym| ma.bind( self ).call( sym ) end
      end
    end
  end
end

Instance Method Details

#aA_empty(what_is_receiver = "hash") ⇒ Object

Fails with ArgumentError unless the receiver’s ‘#empty?` returns true.



85
86
87
# File 'lib/y_support/typing/hash/typing.rb', line 85

def aA_empty what_is_receiver="hash"
  tap { empty? or fail ArgumentError, "%s not empty".X!( what_is_receiver ) }
end

#aA_has(key, options = {}, &b) ⇒ Object

This method behaves exactly like #aT_has, but it raises ArgumentError instead of TypeError.



63
64
65
66
67
68
69
# File 'lib/y_support/typing/hash/typing.rb', line 63

def aA_has key, options={}, &b
  fail ArgumentError, "Key '#{key}' absent!" unless has? key, options
  self[key].tap do |val|
    fail ArgumentError, "Value for #{key} of wrong type!" unless
      ( b.arity == 0 ? val.instance_exec( &b ) : b.( val ) ) if b
  end
end

#aA_not_empty(what_is_receiver = "hash") ⇒ Object

Fails with ArgumentError unless the receiver’s ‘#empty?` returns false.



91
92
93
# File 'lib/y_support/typing/hash/typing.rb', line 91

def aA_not_empty what_is_receiver="hash"
  tap { empty? and fail ArgumentError, "%s empty".X!( what_is_receiver ) }
end

#aT_empty(what_is_receiver = "hash") ⇒ Object

Fails with TypeError unless the receiver’s #empty? returns true.



73
74
75
# File 'lib/y_support/typing/hash/typing.rb', line 73

def aT_empty what_is_receiver="hash"
  tap { empty? or fail TypeError, "%s not empty".X!( what_is_receiver ) }
end

#aT_has(key, options = {}, &b) ⇒ Object Also known as: must_have

This runtime assertion raises TypeError when:

  1. Neither the required key nor any of its synonyms are present.

  2. The supplied criterion block, if any, returns false when applied

to the value of the key in question. If the block takes an argument (or more arguments), the value is passed in. If the block takes no arguments (arity 0), it is executed inside the singleton class of the receiver (using #instance_exec method).



51
52
53
54
55
56
57
# File 'lib/y_support/typing/hash/typing.rb', line 51

def aT_has key, options={}, &b
  fail TypeError, "Key '#{key}' absent!" unless has? key, options
  self[key].tap do |val|
    fail TypeError, "Value for #{key} of wrong type!" unless
      ( b.arity == 0 ? val.instance_exec( &b ) : b.( val ) ) if b
  end
end

#aT_not_empty(what_is_receiver = "hash") ⇒ Object

Fails with TypeError unless the receiver’s ‘#empty?` returns false.



79
80
81
# File 'lib/y_support/typing/hash/typing.rb', line 79

def aT_not_empty what_is_receiver="hash"
  tap { empty? and fail TypeError, "%s empty".X!( what_is_receiver ) }
end

#dot!(overwrite_methods: false) ⇒ Object

Makes hash keys accessible as methods. If the hash keys collide with its methods, ArgumentError is raised, unless :overwrite_methods option == true.



119
120
121
122
123
124
125
126
127
128
# File 'lib/y_support/core_ext/hash/misc.rb', line 119

def dot! overwrite_methods: false
  each_pair do |key, _|
    fail ArgumentError, "key #{key} of #dot!-ted hash is not convertible " +
      "to a symbol" unless key.respond_to? :to_sym
    fail ArgumentError, "#dot!-ted hash must not have keys colliding with " +
      "its methods" if methods.include? key.to_sym unless overwrite_methods
    define_singleton_method key.to_sym do self[key] end
    define_singleton_method "#{key}=".to_sym do |val| self[key] = val end
  end
end

#has?(key, options = {}) ⇒ Boolean

This method behaves like #may_have, but it returns true/false value instead of the value under the specified key.

Returns:

  • (Boolean)


39
40
41
# File 'lib/y_support/typing/hash/typing.rb', line 39

def has? key, options={}
  ! merge_synonym_keys!( key, *options[:syn!] ).nil?
end

#keys_to_namesObject

Maps the hash into one whose keys have been replaced with full names of the keys (which are assumed to be objects responding to #full_name method).



7
8
9
# File 'lib/y_support/name_magic/hash.rb', line 7

def keys_to_names                        # FIXME: Change to keys_to_full_names
  with_keys do |key| key.name || key end # FIXME: Change name to full_name
end

#keys_to_names!Object

Modifies a hash in place so that the keys are replaced with key names (key objects are assumed to respond to #name method).



14
15
16
# File 'lib/y_support/name_magic/hash.rb', line 14

def keys_to_names!                        # FIXME: Change to keys_to_full_names!
  with_keys! do |key| key.name || key end # FIXME: Change name to full_name
end

#keys_to_ɴsObject

Maps a hash into a hash, whose keys have been replaced with names of the key objects (which are assumed to respond to #name method).



21
22
23
# File 'lib/y_support/name_magic/hash.rb', line 21

def keys_to_ɴs
  with_keys do |key| key._name_ || key end
end

#keys_to_ɴs!Object

Modifies a hash in place so that the keys are replaced with key names (key objects are assumed to respond to #name method).



28
29
30
# File 'lib/y_support/name_magic/hash.rb', line 28

def keys_to_ɴs!
  with_keys! do |key| key._name_ || key end
end

#may_have(key, options = {}) ⇒ Object

Calls #merge_synonym_keys! first, then returns the value under the specified key. The first argument is the main key, synonym keys may be supplied as a named argument :syn!. (Bang indicates that the synonym keys will be merged with the main key, thus modifying the hash.)



31
32
33
34
# File 'lib/y_support/typing/hash/typing.rb', line 31

def may_have key, options={}
  merge_synonym_keys!( key, *options[:syn!] ).nil?
  return self[key]
end

#merge_synonym_keys!(key, *synonyms) ⇒ Object

Merges the synonymous hash keys into a single key - useful for argument validation. Returns nil if neither main key, nor synonyms are found. Returns false (no merging) if the main key was found, but no synonym keys. Returns true (yes merging) if any of the synonym keys is found and renamed/merged to the main key. Value collisions in synonym keys (detected by #==) raise ArgumentError.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/y_support/typing/hash/typing.rb', line 9

def merge_synonym_keys!( key, *synonyms )
  synonyms.reduce has_key?( key ) ? false : nil do |acc, syn|
    next acc unless has_key? syn
    if acc.nil? then
      self[key] = self[syn]
      delete syn
      next true
    end
    if self[key] == self[syn] then
      delete syn
      next true
    else
      raise TypeError, "Value collision between #{key} and its synonym #{syn}!"
    end
  end
end

#modifyObject

Like #map that returns a hash.



92
93
94
95
96
97
# File 'lib/y_support/core_ext/hash/misc.rb', line 92

def modify
  each_with_object self.class.new do |pair, hsh|
    key, val = yield pair
    hsh[key] = val
  end
end

#modify_keysObject

The difference from #with_keys is that modify_keys expects block that takes 2 arguments (key: value pair) and returns the new key.



55
56
57
58
59
# File 'lib/y_support/core_ext/hash/misc.rb', line 55

def modify_keys
  each_with_object self.class.new do |pair, hsh|
    hsh[ yield pair ] = self[ pair[0] ]
  end
end

#modify_valuesObject

The difference from #do_with_values is that modify_values expects block that takes 2 arguments (key: value pair) and returns the new value.



76
77
78
79
80
# File 'lib/y_support/core_ext/hash/misc.rb', line 76

def modify_values
  each_with_object self.class.new do |pair, hsh|
    hsh[ pair[0] ] = yield pair
  end
end

#modify_values!Object

Like #modify_values, but modifies the receiver.



84
85
86
87
88
# File 'lib/y_support/core_ext/hash/misc.rb', line 84

def modify_values!
  each_with_object self do |pair, hsh|
    hsh[ pair[0] ] = yield pair
  end
end

#pretty_print_numeric_values(gap: 0, precision: 2) ⇒ Object

Pretty-prints the hash consisting of names as keys, and numeric values. Takes 2 named arguments: :gap and :precision.



133
134
135
136
137
138
139
140
141
# File 'lib/y_support/core_ext/hash/misc.rb', line 133

def pretty_print_numeric_values gap: 0, precision: 2
  lmax = keys.map( &:to_s ).map( &:size ).max
  value_strings = values.map { |n| "%.#{precision}f" % n rescue "%s" % n }
  rmax = value_strings.map( &:size ).max
  lgap = gap / 2
  rgap = gap - lgap
  line = "%- #{lmax+lgap+1}s%#{rmax+rgap+1}.#{precision}f"
  puts keys.zip( values ).map &line.method( :% )
end

#slice(matcher) ⇒ Object

A bit like Array#slice, but only takes 1 argument, which is either a Range, or an Array, and returns the selection of the hash for the keys that match the range or are present in the array.



109
110
111
112
113
# File 'lib/y_support/core_ext/hash/misc.rb', line 109

def slice matcher
  self.class[ case matcher
              when Array then select { |key, _| matcher.include? key }
              else select { |key, _| matcher === key } end ]
end

#with_keysObject

Applies a block as a mapping on all keys, returning a new hash.



46
47
48
49
50
# File 'lib/y_support/core_ext/hash/misc.rb', line 46

def with_keys
  keys.each_with_object self.class.new do |key, hsh|
    hsh[ yield key ] = self[ key ]
  end
end

#with_valuesObject

Applies a block as a mapping on all values, returning a new hash.



63
64
65
# File 'lib/y_support/core_ext/hash/misc.rb', line 63

def with_values
  each_with_object self.class.new do |(k, v), hsh| hsh[ k ] = yield v end
end

#with_values!Object

Like #do_with_values, but modifies the receiver.



69
70
71
# File 'lib/y_support/core_ext/hash/misc.rb', line 69

def with_values!
  each_with_object self do |(k, v), hsh| hsh[ k ] = yield v end
end