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 +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
|