File: indifferent_hash.rb

package info (click to toggle)
ruby-sinatra 4.2.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,932 kB
  • sloc: ruby: 17,700; sh: 25; makefile: 8
file content (208 lines) | stat: -rw-r--r-- 4,537 bytes parent folder | download | duplicates (2)
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