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 +4 -5
- data/README.markdown +9 -13
- data/lib/insensitive_hash.rb +3 -1
- data/lib/insensitive_hash/insensitive_hash.rb +41 -8
- data/lib/insensitive_hash/version.rb +1 -1
- data/test/test_insensitive_hash.rb +49 -5
- metadata +2 -2
data/CHANGELOG.markdown
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
### 0.3.
|
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
|
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
|
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
|
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
|
-
|
143
|
-
|
144
|
-
|
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)
|
data/lib/insensitive_hash.rb
CHANGED
@@ -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
|
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 [
|
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 [
|
46
|
-
# @return [
|
47
|
+
# @param [#call] prc Key encoder. Determines the level of insensitivity.
|
48
|
+
# @return [#call]
|
47
49
|
def encoder= prc
|
48
|
-
raise ArgumentError, "
|
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.
|
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
|
-
|
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
|
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
|
@@ -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
|
-
|
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
|
-
|
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.
|
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-
|
12
|
+
date: 2013-02-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: test-unit
|