insensitive_hash 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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