insensitive_hash 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.markdown +6 -0
- data/README.markdown +20 -3
- data/lib/insensitive_hash.rb +6 -2
- data/lib/insensitive_hash/insensitive_hash.rb +64 -19
- data/lib/insensitive_hash/version.rb +1 -1
- data/test/test_insensitive_hash.rb +256 -3
- metadata +4 -4
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
### 0.2.2 / 2012/02/13
|
2
|
+
* :underscore option added
|
3
|
+
* `Insensitive::KeyClashError` added for safer operations
|
4
|
+
* Bug fix: Preserve default/default_proc on merge/replace/insensitive
|
5
|
+
* Subtle behavioral change in `Insensitive#replace` when called with an ordinary Hash
|
6
|
+
|
1
7
|
### 0.2.1 / 2012/02/10
|
2
8
|
* Bug fix: Insensitive `fetch`
|
3
9
|
|
data/README.markdown
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
-
|
1
|
+
insensitive_hash
|
2
|
+
================
|
2
3
|
Hash with case-insensitive, Symbol/String-indifferent key access.
|
3
4
|
|
4
|
-
|
5
|
+
Installation
|
6
|
+
------------
|
5
7
|
```
|
6
8
|
gem install insensitive_hash
|
7
9
|
```
|
8
10
|
|
9
|
-
|
11
|
+
Examples
|
12
|
+
--------
|
10
13
|
|
11
14
|
### Instantiation
|
12
15
|
```ruby
|
@@ -69,6 +72,20 @@ db['Development']['ADAPTER']
|
|
69
72
|
db[:production][:adapter]
|
70
73
|
```
|
71
74
|
|
75
|
+
### Replacing spaces in String keys to underscores
|
76
|
+
```ruby
|
77
|
+
h = { 'A key with spaces' => true }
|
78
|
+
|
79
|
+
ih = h.insensitive :underscore => true
|
80
|
+
ih[:a_key_with_spaces] # true
|
81
|
+
|
82
|
+
# Or,
|
83
|
+
ih = InsensitiveHash[ h ]
|
84
|
+
ih.underscore = true
|
85
|
+
ih.underscore? # true
|
86
|
+
ih[:a_key_with_spaces] # true
|
87
|
+
```
|
88
|
+
|
72
89
|
## Contributing to insensitive_hash
|
73
90
|
|
74
91
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
data/lib/insensitive_hash.rb
CHANGED
@@ -2,8 +2,12 @@ require 'insensitive_hash/version'
|
|
2
2
|
require 'insensitive_hash/insensitive_hash'
|
3
3
|
|
4
4
|
class Hash
|
5
|
-
|
6
|
-
|
5
|
+
# @return [InsensitiveHash]
|
6
|
+
def insensitive options = {}
|
7
|
+
InsensitiveHash.new.tap do |ih|
|
8
|
+
ih.replace self
|
9
|
+
ih.underscore = options[:underscore] if options.has_key?(:underscore)
|
10
|
+
end
|
7
11
|
end
|
8
12
|
end
|
9
13
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
class InsensitiveHash < Hash
|
2
|
-
|
2
|
+
class KeyClashError < Exception
|
3
|
+
end
|
3
4
|
|
4
5
|
def initialize default = nil, &block
|
5
6
|
if block_given?
|
@@ -8,7 +9,31 @@ class InsensitiveHash < Hash
|
|
8
9
|
super
|
9
10
|
end
|
10
11
|
|
11
|
-
@key_map
|
12
|
+
@key_map = {}
|
13
|
+
@underscore = false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Sets whether to replace spaces in String keys to underscores
|
17
|
+
# @param [Boolean] us
|
18
|
+
# @return [Boolean]
|
19
|
+
def underscore= us
|
20
|
+
raise ArgumentError.new("Not true or false") unless [true, false].include?(us)
|
21
|
+
|
22
|
+
# Check key clash
|
23
|
+
detect_clash self, us
|
24
|
+
|
25
|
+
@underscore = us
|
26
|
+
@key_map = {}
|
27
|
+
self.keys.each do |k|
|
28
|
+
self[k] = self.delete(k)
|
29
|
+
end
|
30
|
+
|
31
|
+
us
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Boolean] Whether to replace spaces in String keys to underscores
|
35
|
+
def underscore?
|
36
|
+
@underscore
|
12
37
|
end
|
13
38
|
|
14
39
|
# Returns a normal, sensitive Hash
|
@@ -36,17 +61,15 @@ class InsensitiveHash < Hash
|
|
36
61
|
end
|
37
62
|
|
38
63
|
def []= key, value
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
@key_map[encode key] = key
|
45
|
-
super(lookup_key(key), InsensitiveHash.wrap(value))
|
64
|
+
delete key
|
65
|
+
ekey = encode key, @underscore
|
66
|
+
@key_map[ekey] = key
|
67
|
+
super key, wrap(value, underscore?)
|
46
68
|
end
|
47
69
|
alias store []=
|
48
70
|
|
49
71
|
def merge! other_hash
|
72
|
+
detect_clash other_hash, underscore?
|
50
73
|
other_hash.each do |key, value|
|
51
74
|
self[key] = value
|
52
75
|
end
|
@@ -55,7 +78,8 @@ class InsensitiveHash < Hash
|
|
55
78
|
alias update! merge!
|
56
79
|
|
57
80
|
def merge other_hash
|
58
|
-
InsensitiveHash
|
81
|
+
InsensitiveHash.new.tap do |ih|
|
82
|
+
ih.replace self
|
59
83
|
ih.merge! other_hash
|
60
84
|
end
|
61
85
|
end
|
@@ -71,6 +95,16 @@ class InsensitiveHash < Hash
|
|
71
95
|
end
|
72
96
|
|
73
97
|
def replace other
|
98
|
+
# TODO
|
99
|
+
# What is the correct behavior of replace when the other hash is not an InsensitiveHash?
|
100
|
+
# underscore property precedence: other => self (FIXME)
|
101
|
+
us = other.respond_to?(:underscore?) ? other.underscore? : self.underscore?
|
102
|
+
detect_clash other, us
|
103
|
+
|
104
|
+
self.default = other.default
|
105
|
+
self.default_proc = other.default_proc if other.default_proc
|
106
|
+
self.underscore = us
|
107
|
+
|
74
108
|
clear
|
75
109
|
other.each do |k, v|
|
76
110
|
self[k] = v
|
@@ -93,19 +127,21 @@ class InsensitiveHash < Hash
|
|
93
127
|
end
|
94
128
|
|
95
129
|
private
|
96
|
-
def
|
130
|
+
def wrap value, us
|
97
131
|
case value
|
132
|
+
when InsensitiveHash
|
133
|
+
value.tap { |ih| ih.underscore = us || ih.underscore? }
|
98
134
|
when Hash
|
99
|
-
value.
|
135
|
+
InsensitiveHash[value].tap { |ih| ih.underscore = us }
|
100
136
|
when Array
|
101
|
-
value.map { |v|
|
137
|
+
value.map { |v| wrap v, us }
|
102
138
|
else
|
103
139
|
value
|
104
140
|
end
|
105
141
|
end
|
106
142
|
|
107
143
|
def lookup_key key, delete = false
|
108
|
-
ekey = encode key
|
144
|
+
ekey = encode key, @underscore
|
109
145
|
if @key_map.has_key?(ekey)
|
110
146
|
delete ? @key_map.delete(ekey) : @key_map[ekey]
|
111
147
|
else
|
@@ -113,15 +149,24 @@ private
|
|
113
149
|
end
|
114
150
|
end
|
115
151
|
|
116
|
-
def encode key
|
152
|
+
def encode key, us
|
117
153
|
case key
|
118
|
-
when String
|
119
|
-
key.downcase
|
120
|
-
|
121
|
-
|
154
|
+
when String, Symbol
|
155
|
+
key = key.to_s.downcase
|
156
|
+
if us
|
157
|
+
key.gsub(' ', '_')
|
158
|
+
else
|
159
|
+
key
|
160
|
+
end
|
122
161
|
else
|
123
162
|
key
|
124
163
|
end
|
125
164
|
end
|
165
|
+
|
166
|
+
def detect_clash hash, us
|
167
|
+
hash.keys.map { |k| encode k, us }.tap { |ekeys|
|
168
|
+
raise KeyClashError.new("Key clash detected") if ekeys != ekeys.uniq
|
169
|
+
}
|
170
|
+
end
|
126
171
|
end
|
127
172
|
|
@@ -73,6 +73,60 @@ class TestInsensitiveHash < Test::Unit::TestCase
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
end
|
76
|
+
|
77
|
+
# InsensitiveHash#insensitive
|
78
|
+
ihash1 = hash.insensitive
|
79
|
+
ihash1.default = :default
|
80
|
+
ihash1.underscore = true
|
81
|
+
ihash2 = ihash1.insensitive.insensitive.insensitive
|
82
|
+
|
83
|
+
assert_equal InsensitiveHash, ihash2.class
|
84
|
+
assert_equal :default, ihash2.default
|
85
|
+
assert_equal true, ihash2.underscore?
|
86
|
+
|
87
|
+
# Preserving default
|
88
|
+
[:default, nil].each do |d|
|
89
|
+
hash.default = d
|
90
|
+
assert_equal d, hash.insensitive.insensitive.insensitive[:anything]
|
91
|
+
end
|
92
|
+
|
93
|
+
# Preserving default_proc
|
94
|
+
hash2 = {}
|
95
|
+
hash2.replace hash
|
96
|
+
hash2.default_proc = proc { |h, k| h[k] = :default }
|
97
|
+
|
98
|
+
ihash2 = hash2.insensitive.insensitive.insensitive
|
99
|
+
assert !ihash2.has_key?(:anything)
|
100
|
+
assert_equal :default, ihash2[:anything]
|
101
|
+
assert ihash2.has_key?(:anything)
|
102
|
+
|
103
|
+
# FIXME: test insensitive call with encoder
|
104
|
+
h = InsensitiveHash[{'Key with spaces' => 1}]
|
105
|
+
h2 = h.insensitive :underscore => true
|
106
|
+
h3 = h.insensitive :underscore => false
|
107
|
+
assert_raise(ArgumentError) { h.insensitive :underscore => :wat }
|
108
|
+
|
109
|
+
assert_equal 1, h['key with spaces']
|
110
|
+
assert_nil h[:key_with_spaces]
|
111
|
+
assert_equal 1, h3['key with spaces']
|
112
|
+
assert_nil h3[:key_with_spaces]
|
113
|
+
assert_equal 1, h2[:KEY_WITH_SPACES]
|
114
|
+
assert_equal 1, h2[:Key_with_spaces]
|
115
|
+
|
116
|
+
assert_equal ['Key with spaces'], h.keys
|
117
|
+
assert_equal ['Key with spaces'], h2.keys
|
118
|
+
assert_equal ['Key with spaces'], h3.keys
|
119
|
+
|
120
|
+
h = { 'A key with spaces' => true }
|
121
|
+
ih = h.insensitive :underscore => true
|
122
|
+
assert ih[:a_key_with_spaces]
|
123
|
+
assert ih.underscore?
|
124
|
+
|
125
|
+
# FIXME: from README
|
126
|
+
ih = InsensitiveHash[ h ]
|
127
|
+
ih.underscore = true
|
128
|
+
assert ih[:a_key_with_spaces]
|
129
|
+
assert ih.underscore?
|
76
130
|
end
|
77
131
|
|
78
132
|
def test_delete
|
@@ -88,13 +142,36 @@ class TestInsensitiveHash < Test::Unit::TestCase
|
|
88
142
|
|
89
143
|
def test_merge
|
90
144
|
[:merge, :update].each do |method|
|
91
|
-
ih = InsensitiveHash[:a => 1]
|
145
|
+
ih = InsensitiveHash[:a => 1, 'hello world' => 2]
|
92
146
|
ih2 = ih.send(method, :b => 2)
|
93
147
|
|
94
|
-
assert_equal [:a], ih.keys
|
95
|
-
assert_equal [:a, :b], ih2.keys
|
148
|
+
assert_equal [:a, 'hello world'], ih.keys
|
149
|
+
assert_equal [:a, 'hello world', :b], ih2.keys
|
96
150
|
assert ih2.has_key?('B'), 'correctly merged another hash'
|
97
151
|
|
152
|
+
assert_nil ih[:hello_world]
|
153
|
+
assert_nil ih2[:hello_world]
|
154
|
+
|
155
|
+
ih.underscore = true
|
156
|
+
assert_equal 2, ih[:hello_world]
|
157
|
+
assert_equal 2, ih.send(method, :b => 2)[:hello_world]
|
158
|
+
|
159
|
+
ih.underscore = false
|
160
|
+
assert_nil ih[:hello_world]
|
161
|
+
assert_nil ih.send(method, :b => 2)[:hello_world]
|
162
|
+
|
163
|
+
ih.default = 10
|
164
|
+
assert_equal 10, ih.send(method, :b => 2)[:anything]
|
165
|
+
|
166
|
+
ih.default = nil
|
167
|
+
assert_nil ih.send(method, :b => 2)[:anything]
|
168
|
+
|
169
|
+
ih.default_proc = proc { |h, k| h[k] = :default }
|
170
|
+
mh = ih.send(method, :b => 2)
|
171
|
+
assert !mh.has_key?(:anything)
|
172
|
+
assert_equal :default, mh[:anything]
|
173
|
+
assert mh.has_key?(:anything)
|
174
|
+
|
98
175
|
ih2.delete 'b'
|
99
176
|
assert ih2.has_key?('B') == false
|
100
177
|
end
|
@@ -114,6 +191,32 @@ class TestInsensitiveHash < Test::Unit::TestCase
|
|
114
191
|
end
|
115
192
|
end
|
116
193
|
|
194
|
+
def test_merge_clash
|
195
|
+
ih = InsensitiveHash.new
|
196
|
+
ih2 = InsensitiveHash.new
|
197
|
+
ih2.underscore = true
|
198
|
+
|
199
|
+
[:merge, :merge!, :update, :update!].each do |method|
|
200
|
+
[ih, ih2].each do |h|
|
201
|
+
assert_raise(InsensitiveHash::KeyClashError) {
|
202
|
+
h.send(method, { :a => 1, :A => 1, 'A' => 1})
|
203
|
+
}
|
204
|
+
assert_raise(InsensitiveHash::KeyClashError) {
|
205
|
+
h.send(method, { 'a' => 1, 'A' => 1 })
|
206
|
+
}
|
207
|
+
end
|
208
|
+
|
209
|
+
ih.send(method, { :hello_world => 1, 'hello world' => 2})
|
210
|
+
assert_raise(InsensitiveHash::KeyClashError) {
|
211
|
+
ih2.send(method, { :hello_world => 1, 'hello world' => 2})
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
215
|
+
assert_raise(InsensitiveHash::KeyClashError) {
|
216
|
+
ih.merge({ :a => 1, :A => 1, 'A' => 1})
|
217
|
+
}
|
218
|
+
end
|
219
|
+
|
117
220
|
def test_assoc
|
118
221
|
h = InsensitiveHash[{
|
119
222
|
"colors" => ["red", "blue", "green"],
|
@@ -248,6 +351,41 @@ class TestInsensitiveHash < Test::Unit::TestCase
|
|
248
351
|
|
249
352
|
assert !h.has_key?('A')
|
250
353
|
assert h.has_key?('B')
|
354
|
+
|
355
|
+
# Default value
|
356
|
+
h.replace(Hash.new(5))
|
357
|
+
assert_equal 5, h[:anything]
|
358
|
+
assert_equal [], h.keys
|
359
|
+
|
360
|
+
# Default proc
|
361
|
+
h.replace(Hash.new { |h, k| h[k] = :default })
|
362
|
+
assert_equal :default, h[:anything]
|
363
|
+
assert_equal [:anything], h.keys
|
364
|
+
|
365
|
+
# underscore property of self
|
366
|
+
assert !h.underscore?
|
367
|
+
h.replace(InsensitiveHash['hello world' => 1].tap { |ih| ih.underscore = true })
|
368
|
+
assert h.underscore?
|
369
|
+
assert_equal 1, h[:hello_world]
|
370
|
+
|
371
|
+
h = InsensitiveHash.new
|
372
|
+
assert_raise(InsensitiveHash::KeyClashError) {
|
373
|
+
h.replace({ 'a' => 1, :A => 2 })
|
374
|
+
}
|
375
|
+
|
376
|
+
# TODO: FIXME
|
377
|
+
# underscore property of other
|
378
|
+
h = InsensitiveHash.new
|
379
|
+
oh = InsensitiveHash[ 'hello world' => 1 ]
|
380
|
+
oh.underscore = true
|
381
|
+
assert !h.underscore?
|
382
|
+
h.replace(oh)
|
383
|
+
assert h.underscore?
|
384
|
+
|
385
|
+
oh.underscore = false
|
386
|
+
oh[:hello_world => 2]
|
387
|
+
h.replace(oh)
|
388
|
+
assert !h.underscore?
|
251
389
|
end
|
252
390
|
|
253
391
|
def test_rassoc
|
@@ -278,5 +416,120 @@ class TestInsensitiveHash < Test::Unit::TestCase
|
|
278
416
|
assert_equal 3, h.fetch(:c) { 3 }
|
279
417
|
assert_raise(KeyError) { h.fetch('D') }
|
280
418
|
end
|
419
|
+
|
420
|
+
def test_underscore
|
421
|
+
h = InsensitiveHash[{'Key with spaces' => 1}]
|
422
|
+
|
423
|
+
assert_equal 1, h['key with spaces']
|
424
|
+
assert_nil h[:key_with_spaces]
|
425
|
+
|
426
|
+
test_keys = [
|
427
|
+
:KEY_WITH_SPACES,
|
428
|
+
:Key_with_spaces,
|
429
|
+
:key_with_spaces,
|
430
|
+
'key with_spaces',
|
431
|
+
'KEY_WITH spaces'
|
432
|
+
]
|
433
|
+
|
434
|
+
10.times do
|
435
|
+
assert_raise(ArgumentError) { h.underscore = 'wat' }
|
436
|
+
assert !h.underscore?
|
437
|
+
h.underscore = true
|
438
|
+
assert h.underscore?
|
439
|
+
|
440
|
+
|
441
|
+
test_keys.each do |k|
|
442
|
+
assert_equal 1, h[k]
|
443
|
+
end
|
444
|
+
assert_equal ['Key with spaces'], h.keys
|
445
|
+
|
446
|
+
h.underscore = false
|
447
|
+
assert !h.underscore?
|
448
|
+
assert_equal 1, h['KEY WITH SPACES']
|
449
|
+
test_keys.each do |k|
|
450
|
+
assert_nil h[k]
|
451
|
+
end
|
452
|
+
assert_equal ['Key with spaces'], h.keys
|
453
|
+
end
|
454
|
+
|
455
|
+
h.underscore = true
|
456
|
+
test_keys.each do |tk|
|
457
|
+
h[tk] = 1
|
458
|
+
end
|
459
|
+
|
460
|
+
assert_equal [test_keys.last], h.keys
|
461
|
+
end
|
462
|
+
|
463
|
+
def test_underscore_inheritance
|
464
|
+
h = InsensitiveHash[
|
465
|
+
{
|
466
|
+
'Key with spaces' => {
|
467
|
+
'Another key with spaces' => 1
|
468
|
+
},
|
469
|
+
'Key 2 with spaces' =>
|
470
|
+
InsensitiveHash['Yet another key with spaces' => 2].tap { |ih| ih.underscore = true },
|
471
|
+
'Key 3 with spaces' =>
|
472
|
+
InsensitiveHash['Yet another key with spaces' => 3]
|
473
|
+
}
|
474
|
+
]
|
475
|
+
|
476
|
+
assert_equal 1, h['key with spaces']['another key with spaces']
|
477
|
+
assert_equal false, h['key with spaces'].underscore?
|
478
|
+
assert_nil h['key with spaces'][:another_key_with_spaces]
|
479
|
+
assert_nil h[:key_with_spaces]
|
480
|
+
|
481
|
+
assert_equal 2, h['key 2 with spaces']['yet another key with spaces']
|
482
|
+
assert_equal 2, h['key 2 with spaces'][:yet_another_key_with_spaces]
|
483
|
+
assert_nil h[:key_2_with_spaces]
|
484
|
+
|
485
|
+
assert_equal 3, h['key 3 with spaces']['yet another key with spaces']
|
486
|
+
assert_nil h['key 3 with spaces'][:yet_another_key_with_spaces]
|
487
|
+
assert_nil h[:key_3_with_spaces]
|
488
|
+
|
489
|
+
h.underscore = true
|
490
|
+
assert_equal true, h[:key_with_spaces].underscore?
|
491
|
+
assert_equal 1, h[:key_with_spaces][:another_key_with_spaces]
|
492
|
+
assert_equal true, h[:key_2_with_spaces].underscore?
|
493
|
+
assert_equal 2, h[:key_2_with_spaces][:yet_another_key_with_spaces]
|
494
|
+
assert_equal true, h[:key_3_with_spaces].underscore?
|
495
|
+
assert_equal 3, h[:key_3_with_spaces][:yet_another_key_with_spaces]
|
496
|
+
|
497
|
+
h.underscore = false
|
498
|
+
assert_nil h[:key_with_spaces]
|
499
|
+
assert_nil h[:key_2_with_spaces]
|
500
|
+
assert_nil h[:key_3_with_spaces]
|
501
|
+
|
502
|
+
assert_equal false, h.underscore?
|
503
|
+
assert_equal true, h['key 2 with spaces'].underscore?
|
504
|
+
assert_equal true, h['key 3 with spaces'].underscore?
|
505
|
+
end
|
506
|
+
|
507
|
+
def test_underscore_clash
|
508
|
+
h = InsensitiveHash.new
|
509
|
+
h['hello world'] = 1
|
510
|
+
h['HELLO_world'] = 2
|
511
|
+
assert_equal 1, h['HELLO WORLD']
|
512
|
+
assert_equal 2, h['HELLO_WORLD']
|
513
|
+
assert_equal ['hello world', 'HELLO_world'], h.keys
|
514
|
+
|
515
|
+
assert_raise(InsensitiveHash::KeyClashError) { h.underscore = true }
|
516
|
+
h.delete('hello world')
|
517
|
+
h.underscore = true
|
518
|
+
|
519
|
+
assert_equal ['HELLO_world'], h.keys
|
520
|
+
assert_equal 2, h['HELLO WORLD']
|
521
|
+
assert_equal 2, h[:HELLO_WORLD]
|
522
|
+
end
|
523
|
+
|
524
|
+
def test_unset_underscore
|
525
|
+
h = InsensitiveHash.new
|
526
|
+
h.underscore = true
|
527
|
+
h[:hello_world] = 1
|
528
|
+
h.underscore = false
|
529
|
+
h['hello world'] = 2
|
530
|
+
|
531
|
+
assert_equal [:hello_world, 'hello world'], h.keys
|
532
|
+
assert_equal 2, h['Hello World']
|
533
|
+
end
|
281
534
|
end
|
282
535
|
|
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.2.
|
4
|
+
version: 0.2.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-02-
|
12
|
+
date: 2012-02-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: test-unit
|
16
|
-
requirement: &
|
16
|
+
requirement: &2152875840 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: 2.3.0
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2152875840
|
25
25
|
description: Hash with case-insensitive, Symbol/String-indifferent key access
|
26
26
|
email:
|
27
27
|
- junegunn.c@gmail.com
|