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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
|
# frozen_string_literal: true
module Sinatra
# A poor man's ActiveSupport::HashWithIndifferentAccess, with all the Rails-y
# stuff removed.
#
# Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are
# considered to be the same.
#
# rgb = Sinatra::IndifferentHash.new
#
# rgb[:black] = '#000000' # symbol assignment
# rgb[:black] # => '#000000' # symbol retrieval
# rgb['black'] # => '#000000' # string retrieval
#
# rgb['white'] = '#FFFFFF' # string assignment
# rgb[:white] # => '#FFFFFF' # symbol retrieval
# rgb['white'] # => '#FFFFFF' # string retrieval
#
# Internally, symbols are mapped to strings when used as keys in the entire
# writing interface (calling e.g. <tt>[]=</tt>, <tt>merge</tt>). This mapping
# belongs to the public interface. For example, given:
#
# hash = Sinatra::IndifferentHash.new
# hash[:a] = 1
#
# You are guaranteed that the key is returned as a string:
#
# hash.keys # => ["a"]
#
# Technically other types of keys are accepted:
#
# hash = Sinatra::IndifferentHash
# hash[:a] = 1
# hash[0] = 0
# hash # => { "a"=>1, 0=>0 }
#
# But this class is intended for use cases where strings or symbols are the
# expected keys and it is convenient to understand both as the same. For
# example the +params+ hash in Sinatra.
class IndifferentHash < Hash
def self.[](*args)
new.merge!(Hash[*args])
end
def default(*args)
args.map!(&method(:convert_key))
super(*args)
end
def default=(value)
super(convert_value(value))
end
def assoc(key)
super(convert_key(key))
end
def rassoc(value)
super(convert_value(value))
end
def fetch(key, *args)
args.map!(&method(:convert_value))
super(convert_key(key), *args)
end
def [](key)
super(convert_key(key))
end
def []=(key, value)
super(convert_key(key), convert_value(value))
end
alias store []=
def key(value)
super(convert_value(value))
end
def key?(key)
super(convert_key(key))
end
alias has_key? key?
alias include? key?
alias member? key?
def value?(value)
super(convert_value(value))
end
alias has_value? value?
def delete(key)
super(convert_key(key))
end
# Added in Ruby 2.3
def dig(key, *other_keys)
super(convert_key(key), *other_keys)
end
def fetch_values(*keys)
keys.map!(&method(:convert_key))
super(*keys)
end
def slice(*keys)
keys.map!(&method(:convert_key))
self.class[super(*keys)]
end
def values_at(*keys)
keys.map!(&method(:convert_key))
super(*keys)
end
def merge!(*other_hashes)
other_hashes.each do |other_hash|
if other_hash.is_a?(self.class)
super(other_hash)
else
other_hash.each_pair do |key, value|
key = convert_key(key)
value = yield(key, self[key], value) if block_given? && key?(key)
self[key] = convert_value(value)
end
end
end
self
end
alias update merge!
def merge(*other_hashes, &block)
dup.merge!(*other_hashes, &block)
end
def replace(other_hash)
super(other_hash.is_a?(self.class) ? other_hash : self.class[other_hash])
end
def transform_values(&block)
dup.transform_values!(&block)
end
def transform_values!
super
super(&method(:convert_value))
end
def transform_keys(&block)
dup.transform_keys!(&block)
end
def transform_keys!
super
super(&method(:convert_key))
end
def select(*args, &block)
return to_enum(:select) unless block_given?
dup.tap { |hash| hash.select!(*args, &block) }
end
def reject(*args, &block)
return to_enum(:reject) unless block_given?
dup.tap { |hash| hash.reject!(*args, &block) }
end
def compact
dup.tap(&:compact!)
end
def except(*keys)
keys.map!(&method(:convert_key))
self.class[super(*keys)]
end if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.0")
private
def convert_key(key)
key.is_a?(Symbol) ? key.to_s : key
end
def convert_value(value)
case value
when Hash
value.is_a?(self.class) ? value : self.class[value]
when Array
value.map(&method(:convert_value))
else
value
end
end
end
end
|