insensitive_hash 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.markdown CHANGED
@@ -1,14 +1,14 @@
1
- ### 0.3.1
1
+ ### 0.3.2
2
2
  * Supports custom key encoder
3
+ * Added `InsensitiveHash#merge_recursive!`
3
4
 
4
5
  ### 0.3.0
6
+ * Now allowing underscores for spaces is by default on, and cannot be turned off.
5
7
  * "Inherited insensitivity" had been a great source of confusion,
6
8
  as it stores transformed version of the given Array of Hash.
7
9
  From 0.3.0, Hash values and descendant Hashes are converted to be insensitive
8
- only on the initialization of InsensitiveHash or on `underscore=` call.
10
+ only on the initialization of InsensitiveHash.
9
11
  Refer to the following code
10
- * Now allowing underscores for spaces is by default on, and cannot be turned off.
11
-
12
12
  ```ruby
13
13
  ih = {}.insensitive
14
14
  ih[:a] = { :b => :c }
@@ -19,7 +19,6 @@
19
19
  ih2 = ih.insensitive
20
20
  ih2[:a]['B'] # :c
21
21
  ```
22
- * `:underscore` option is on by default
23
22
 
24
23
  ### 0.2.4
25
24
  * Bug fix: Invalid `dup`, `clone` behavior
data/README.markdown CHANGED
@@ -76,9 +76,8 @@ ih.keys # ['DEF']
76
76
  Inherited insensitivity
77
77
  -----------------------
78
78
 
79
- When InsensitiveHash is built from another Hash,
80
- descendant Hash values are recursively converted to be insensitive
81
- (Useful when processing YAML inputs)
79
+ When an InsensitiveHash is built from another Hash,
80
+ descendant Hash values are recursively converted to be insensitive.
82
81
 
83
82
  ```ruby
84
83
 
@@ -90,7 +89,7 @@ ih['one'].first.first.first['A']['b'][:C] # 'd'
90
89
  ```
91
90
 
92
91
  However, once InsensitiveHash is initialized,
93
- descendant Hashes are not automatically converted.
92
+ descendant Hashes (or Hashes in Arrays) are not automatically converted.
94
93
 
95
94
  ```ruby
96
95
  ih = {}.insensitive
@@ -99,7 +98,7 @@ ih[:abc] = { :def => true }
99
98
  ih['ABC']['DEF'] # nil
100
99
  ```
101
100
 
102
- Simply build a new InsensitiveHash again if you need recursive conversion.
101
+ Simply build a new InsensitiveHash out of it if you need recursive conversion.
103
102
 
104
103
  ```ruby
105
104
  ih2 = ih.insensitive
@@ -107,6 +106,7 @@ ih2['ABC']['DEF'] # true
107
106
  ```
108
107
 
109
108
  ### Example: Processing case-insensitive YAML input
109
+
110
110
  ```ruby
111
111
  db = YAML.load(File.read 'database.yml').insensitive
112
112
 
@@ -118,7 +118,7 @@ db[:production][:adapter]
118
118
  Customizing insensitivity
119
119
  -------------------------
120
120
 
121
- You can provide a Proc object as the key encoder which determines the level of insensitivity.
121
+ You can provide a `#call`-able object (duck-typing) as the key encoder which determines the level of insensitivity.
122
122
 
123
123
  ### Default encoder
124
124
 
@@ -139,13 +139,9 @@ InsensitiveHash::DEFAULT_ENCODER =
139
139
  ### Encoder examples
140
140
 
141
141
  ```ruby
142
- h1 = {}.insensitive(:encoder => proc { |key| key.to_s })
143
- h2 = {}.insensitive(:encoder => proc { |key| key.to_s.downcase })
144
- h3 = {}.insensitive(:encoder => proc { |key| key.to_s.downcase.gsub(/\s+/, '_') })
145
-
146
- # Without `insensitive` method
147
- h4 = InsensitiveHash.new
148
- h4.encoder = proc { |key| key.to_s }
142
+ ih1 = {}.insensitive(:encoder => proc { |key| key.to_s })
143
+ ih2 = {}.insensitive(:encoder => proc { |key| key.to_s.downcase })
144
+ ih3 = {}.insensitive(:encoder => proc { |key| key.to_s.downcase.gsub(/\s+/, '_') })
149
145
  ```
150
146
 
151
147
  Enabling key-clash detection (Safe mode)
@@ -7,11 +7,13 @@ class Hash
7
7
  # @option options [Proc] :encoder Key encoding Proc
8
8
  # @return [InsensitiveHash]
9
9
  def insensitive options = {}
10
- InsensitiveHash[self].tap do |ih|
10
+ InsensitiveHash.new.tap do |ih|
11
11
  ih.safe = options[:safe] if options.has_key?(:safe)
12
12
  ih.encoder = options[:encoder] if options.has_key?(:encoder)
13
13
  ih.default = self.default
14
14
  ih.default_proc = self.default_proc if self.default_proc
15
+
16
+ ih.merge_recursive!(self)
15
17
  end
16
18
  end
17
19
  end
@@ -1,9 +1,11 @@
1
+ # Insensitive Hash.
1
2
  # @author Junegunn Choi <junegunn.c@gmail.com>
2
3
  # @!attribute [r] encoder
3
- # @return [Proc] Key encoding Proc
4
+ # @return [#call] Key encoder. Determines the level of insensitivity.
4
5
  class InsensitiveHash < Hash
5
6
  attr_reader :encoder
6
7
 
8
+ # Thrown when safe mode is on and another Hash with conflicting keys cannot be merged safely
7
9
  class KeyClashError < Exception
8
10
  end
9
11
 
@@ -42,10 +44,10 @@ class InsensitiveHash < Hash
42
44
  @safe
43
45
  end
44
46
 
45
- # @param [Proc] prc Key encoding Proc
46
- # @return [Proc]
47
+ # @param [#call] prc Key encoder. Determines the level of insensitivity.
48
+ # @return [#call]
47
49
  def encoder= prc
48
- raise ArgumentError, "Proc object required" unless prc.is_a?(Proc)
50
+ raise ArgumentError, "Encoder must respond to :call" unless prc.respond_to?(:call)
49
51
 
50
52
  kvs = to_a
51
53
  clear
@@ -64,10 +66,11 @@ class InsensitiveHash < Hash
64
66
  end
65
67
  alias sensitive to_hash
66
68
 
69
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
67
70
  def self.[] *init
68
71
  h = Hash[*init]
69
72
  InsensitiveHash.new.tap do |ih|
70
- ih.merge! h
73
+ ih.merge_recursive! h
71
74
  end
72
75
  end
73
76
 
@@ -79,6 +82,7 @@ class InsensitiveHash < Hash
79
82
  EVAL
80
83
  end
81
84
 
85
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
82
86
  def []= key, value
83
87
  delete key
84
88
  ekey = encode key
@@ -87,15 +91,17 @@ class InsensitiveHash < Hash
87
91
  end
88
92
  alias store []=
89
93
 
94
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
90
95
  def merge! other_hash
91
96
  detect_clash other_hash
92
97
  other_hash.each do |key, value|
93
- deep_set key, value
98
+ store key, value
94
99
  end
95
100
  self
96
101
  end
97
102
  alias update! merge!
98
103
 
104
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
99
105
  def merge other_hash
100
106
  InsensitiveHash.new.tap do |ih|
101
107
  ih.replace self
@@ -104,15 +110,30 @@ class InsensitiveHash < Hash
104
110
  end
105
111
  alias update merge
106
112
 
113
+ # Merge another hash recursively.
114
+ # @param [Hash|InsensitiveHash] other_hash
115
+ # @return [self]
116
+ def merge_recursive! other_hash
117
+ detect_clash other_hash
118
+ other_hash.each do |key, value|
119
+ deep_set key, value
120
+ end
121
+ self
122
+ end
123
+ alias update_recursive! merge_recursive!
124
+
125
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
107
126
  def delete key, &block
108
127
  super lookup_key(key, true), &block
109
128
  end
110
129
 
130
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
111
131
  def clear
112
132
  @key_map.clear
113
133
  super
114
134
  end
115
135
 
136
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
116
137
  def replace other
117
138
  super other
118
139
 
@@ -126,27 +147,32 @@ class InsensitiveHash < Hash
126
147
  end
127
148
  end
128
149
 
150
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
129
151
  def shift
130
152
  super.tap do |ret|
131
153
  @key_map.delete_if { |k, v| v == ret.first }
132
154
  end
133
155
  end
134
156
 
157
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
135
158
  def values_at *keys
136
159
  keys.map { |k| self[k] }
137
160
  end
138
161
 
162
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
139
163
  def fetch *args, &block
140
164
  args[0] = lookup_key(args[0]) if args.first
141
165
  super *args, &block
142
166
  end
143
167
 
168
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
144
169
  def dup
145
170
  super.tap { |copy|
146
171
  copy.instance_variable_set :@key_map, @key_map.dup
147
172
  }
148
173
  end
149
174
 
175
+ # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
150
176
  def clone
151
177
  super.tap { |copy|
152
178
  copy.instance_variable_set :@key_map, @key_map.dup
@@ -162,9 +188,16 @@ private
162
188
  def wrap value
163
189
  case value
164
190
  when InsensitiveHash
165
- value
191
+ value.tap { |ih|
192
+ ih.safe = safe?
193
+ ih.encoder = encoder
194
+ }
166
195
  when Hash
167
- InsensitiveHash[value]
196
+ InsensitiveHash.new.tap { |ih|
197
+ ih.safe = safe?
198
+ ih.encoder = encoder
199
+ ih.merge_recursive!(value)
200
+ }
168
201
  when Array
169
202
  value.map { |v| wrap v }
170
203
  else
@@ -1,3 +1,3 @@
1
1
  class InsensitiveHash < Hash
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  end
@@ -177,11 +177,16 @@ class TestInsensitiveHash < Test::Unit::TestCase
177
177
  def test_merge
178
178
  [:merge, :update].each do |method|
179
179
  ih = InsensitiveHash[:a => 1, 'hello world' => 2]
180
- ih2 = ih.send(method, :b => 2)
180
+ nh = { :d => :e }
181
+ ih2 = ih.send(method, :b => 2, :c => nh)
181
182
 
182
183
  assert_keys [:a, 'hello world'], ih.keys
183
- assert_keys [:a, 'hello world', :b], ih2.keys
184
+ assert_keys [:a, 'hello world', :b, :c], ih2.keys
184
185
  assert ih2.has_key?('B'), 'correctly merged another hash'
186
+ # No recursive merge
187
+ assert_equal :e, ih2[:c][:d]
188
+ assert_equal nil, ih2[:c][:D]
189
+ assert_equal nh.object_id, ih2[:c].object_id
185
190
 
186
191
  assert_equal 2, ih[:hello_world]
187
192
  assert_equal 2, ih.send(method, :b => 2)[:hello_world]
@@ -219,6 +224,25 @@ class TestInsensitiveHash < Test::Unit::TestCase
219
224
  end
220
225
  end
221
226
 
227
+ def test_merge_recursive!
228
+ [:merge_recursive!, :update_recursive!].each do |method|
229
+ ih = InsensitiveHash.new
230
+ ih[:a] = 1
231
+ ih[:b] = 2
232
+
233
+ nh = { 'b' => 3, :c => :d }
234
+ ih.send(method, nh)
235
+ assert_equal 3, ih[:b]
236
+ assert_equal :d, ih['C']
237
+
238
+ ih.safe = true
239
+ nh = { 'd' => 4, 'D' => 5 }
240
+ assert_raise(InsensitiveHash::KeyClashError) { ih.send(method, nh) }
241
+ ih.safe = false
242
+ ih.send(method, nh)
243
+ end
244
+ end
245
+
222
246
  def test_merge_clash_overwritten
223
247
  ih = InsensitiveHash.new
224
248
 
@@ -395,7 +419,7 @@ class TestInsensitiveHash < Test::Unit::TestCase
395
419
  def test_has_key_after_delete
396
420
  set = [:a, :A, 'a', 'A', :b, :B, 'b', 'B']
397
421
  h = InsensitiveHash[ :a => 1, :b => 2 ]
398
-
422
+
399
423
  set.each { |s| assert h.has_key?(s) }
400
424
  h.delete_if { |k, v| true }
401
425
  set.each { |s| assert !h.has_key?(s) }
@@ -476,7 +500,7 @@ class TestInsensitiveHash < Test::Unit::TestCase
476
500
  end
477
501
 
478
502
  def test_underscore_inheritance
479
- h =
503
+ h =
480
504
  {
481
505
  'Key with spaces' => {
482
506
  'Another key with spaces' => 1
@@ -545,7 +569,12 @@ class TestInsensitiveHash < Test::Unit::TestCase
545
569
  assert_equal true, a[:HELLO_WORLD]
546
570
 
547
571
  # Update again
548
- a.encoder = proc { |key| key.to_s }
572
+ callable = Class.new {
573
+ def call key
574
+ key.to_s
575
+ end
576
+ }.new
577
+ a.encoder = callable
549
578
  assert_equal true, a[:"hello world"]
550
579
  assert_equal nil, a['HELLO WORLD']
551
580
  assert_equal nil, a[:hello_world]
@@ -576,5 +605,20 @@ class TestInsensitiveHash < Test::Unit::TestCase
576
605
  a[:key2] = 2
577
606
  assert_equal 2, a['key2']
578
607
  end
608
+
609
+ def test_encoder_nested
610
+ [ { :a => { :b => { :c => :d } } },
611
+ { :a => { :b => { :c => :d }.insensitive } },
612
+ ].each do |h|
613
+ ih = h.insensitive(:encoder => proc { |key| 1 })
614
+ assert ih.has_key?(:anything)
615
+ assert_equal :d, ih[:a][:b][:c]
616
+ assert_equal :b, ih[:any].keys.first
617
+ assert_equal :d, ih[:any][:any][:c]
618
+ assert_equal :d, ih[:any][:any][:C]
619
+ assert_instance_of InsensitiveHash, ih[:any][:any]
620
+ assert_equal :d, ih[:any][:any][:any]
621
+ end
622
+ end
579
623
  end
580
624
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: insensitive_hash
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-20 00:00:00.000000000 Z
12
+ date: 2013-02-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: test-unit