dottie 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +289 -0
- data/Rakefile +8 -0
- data/dottie.gemspec +26 -0
- data/lib/dottie.rb +153 -0
- data/lib/dottie/ext.rb +3 -0
- data/lib/dottie/ext/array.rb +17 -0
- data/lib/dottie/ext/hash.rb +17 -0
- data/lib/dottie/freckle.rb +49 -0
- data/lib/dottie/helper.rb +6 -0
- data/lib/dottie/methods.rb +76 -0
- data/lib/dottie/version.rb +3 -0
- data/spec/array_spec.rb +55 -0
- data/spec/dottie_spec.rb +434 -0
- data/spec/freckle_spec.rb +274 -0
- data/spec/hash_spec.rb +55 -0
- data/spec/spec_helper.rb +1 -0
- metadata +114 -0
data/lib/dottie/ext.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Dottie
|
2
|
+
class Freckle
|
3
|
+
include Methods
|
4
|
+
|
5
|
+
##
|
6
|
+
# Creates a new Freckle to wrap the supplied object.
|
7
|
+
|
8
|
+
def initialize(obj)
|
9
|
+
@_wrapped_object = obj
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Returns the wrapped Hash, and raises an error if the wrapped object is
|
14
|
+
# not a Hash.
|
15
|
+
|
16
|
+
def hash
|
17
|
+
wrapped_object(Hash)
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Returns the wrapped Array, and raises an error if the wrapped object is
|
22
|
+
# not an Array.
|
23
|
+
|
24
|
+
def array
|
25
|
+
wrapped_object(Array)
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Returns the wrapped object, and raises an error if a type class is
|
30
|
+
# provided and the wrapped object is not of that type.
|
31
|
+
|
32
|
+
def wrapped_object(type = nil)
|
33
|
+
if type.nil? || @_wrapped_object.is_a?(type)
|
34
|
+
@_wrapped_object
|
35
|
+
else
|
36
|
+
raise TypeError.new("expected #{type.name} but got #{@_wrapped_object.class.name}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def inspect
|
41
|
+
"<Dottie::Freckle #{wrapped_object.inspect}>"
|
42
|
+
end
|
43
|
+
|
44
|
+
def method_missing(method, *args)
|
45
|
+
wrapped_object.send(method, *args)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Dottie
|
2
|
+
module Methods
|
3
|
+
|
4
|
+
##
|
5
|
+
# Reads from the Hash or Array with special handling for Dottie-style keys.
|
6
|
+
|
7
|
+
def [](key)
|
8
|
+
if Dottie.dottie_key?(key)
|
9
|
+
Dottie.get(wrapped_object_or_self, key)
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Writes to the Hash or Array with special handling for Dottie-style keys,
|
17
|
+
# adding missing Hash nodes or Array elements where necessary.
|
18
|
+
|
19
|
+
def []=(key, value)
|
20
|
+
if Dottie.dottie_key?(key)
|
21
|
+
Dottie.set(wrapped_object_or_self, key, value)
|
22
|
+
else
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Checks whether the Hash has the specified key with special handling for
|
29
|
+
# Dottie-style keys.
|
30
|
+
|
31
|
+
def has_key?(key)
|
32
|
+
if Dottie.dottie_key?(key)
|
33
|
+
Dottie.has_key?(wrapped_object_or_self, key)
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Fetches a value from the Hash with special handling for Dottie-style keys.
|
41
|
+
# Handles the optional default value and block the same as Hash#fetch.
|
42
|
+
|
43
|
+
def fetch(key, default = :_fetch_default_, &block)
|
44
|
+
if Dottie.dottie_key?(key)
|
45
|
+
if default != :_fetch_default_
|
46
|
+
Dottie.fetch(wrapped_object_or_self, key, default, &block)
|
47
|
+
else
|
48
|
+
Dottie.fetch(wrapped_object_or_self, key, &block)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
if default != :_fetch_default_
|
52
|
+
wrapped_object_or_self.fetch(key, default, &block)
|
53
|
+
else
|
54
|
+
wrapped_object_or_self.fetch(key, &block)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
##
|
62
|
+
# Gets the Hash or Array, whether it is a wrapped object (a
|
63
|
+
# Dottie::Freckle) or this object (self).
|
64
|
+
|
65
|
+
def wrapped_object_or_self
|
66
|
+
if is_a?(Hash) || is_a?(Array)
|
67
|
+
self
|
68
|
+
elsif respond_to?(:wrapped_object)
|
69
|
+
wrapped_object || self
|
70
|
+
else
|
71
|
+
self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
data/spec/array_spec.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dottie/ext'
|
3
|
+
|
4
|
+
describe Array do
|
5
|
+
|
6
|
+
describe 'Dottie extensions' do
|
7
|
+
before :each do
|
8
|
+
@arr = [{ 'a' => { 'b' => 'c' } }, 'd']
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'untouched' do
|
12
|
+
it "should not have Dottie's behavior" do
|
13
|
+
expect{ @arr['[0].a.b'] }.to raise_error TypeError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'wrapped' do
|
18
|
+
let(:freckle) { @arr.dottie }
|
19
|
+
|
20
|
+
it 'is no longer an Array' do
|
21
|
+
expect(freckle).to_not be_an Array
|
22
|
+
end
|
23
|
+
it 'wraps an Array in a Dottie::Freckle' do
|
24
|
+
expect(freckle).to be_a Dottie::Freckle
|
25
|
+
end
|
26
|
+
it 'acts like a regular Array for standard keys' do
|
27
|
+
expect(freckle[1]).to eq 'd'
|
28
|
+
end
|
29
|
+
it "has Dottie's behavior" do
|
30
|
+
expect(freckle['[0].a.b']).to eq 'c'
|
31
|
+
end
|
32
|
+
it "does not add Dottie's behavior to the original Array" do
|
33
|
+
expect{ @arr['[0].a.b'] }.to raise_error TypeError
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'mixed in' do
|
38
|
+
before :each do
|
39
|
+
@arr.dottie!
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'is still an Array' do
|
43
|
+
expect(@arr).to be_a Array
|
44
|
+
end
|
45
|
+
it 'acts like a regular Array for standard keys' do
|
46
|
+
expect(@arr[1]).to eq 'd'
|
47
|
+
end
|
48
|
+
it "adds Dottie's behavior to a Array" do
|
49
|
+
expect(@arr['[0].a.b']).to eq 'c'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
data/spec/dottie_spec.rb
ADDED
@@ -0,0 +1,434 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dottie do
|
4
|
+
|
5
|
+
describe 'instantiation' do
|
6
|
+
|
7
|
+
it 'creates a Dottie::Freckle from a Hash using Dottie[]' do
|
8
|
+
hash = { 'a' => 'b' }
|
9
|
+
expect(Dottie[hash]).to be_a Dottie::Freckle
|
10
|
+
end
|
11
|
+
it 'creates a Dottie::Freckle from a Hash using Dottie()' do
|
12
|
+
hash = { 'a' => 'b' }
|
13
|
+
expect(Dottie(hash)).to be_a Dottie::Freckle
|
14
|
+
end
|
15
|
+
it 'creates a Dottie::Freckle from an Array using Dottie[]' do
|
16
|
+
arr = ['a', 'b', 'c']
|
17
|
+
expect(Dottie[arr]).to be_a Dottie::Freckle
|
18
|
+
end
|
19
|
+
it 'creates a Dottie::Freckle from an Array using Dottie()' do
|
20
|
+
arr = ['a', 'b', 'c']
|
21
|
+
expect(Dottie(arr)).to be_a Dottie::Freckle
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'reading' do
|
27
|
+
|
28
|
+
context 'simple' do
|
29
|
+
let(:hash) {{ 'a' => 'b', 'c' => { 'd' => 'e' }}}
|
30
|
+
|
31
|
+
it 'reads a standard key' do
|
32
|
+
expect(Dottie.get(hash, 'a')).to eq 'b'
|
33
|
+
end
|
34
|
+
it 'returns nil for a missing standard key' do
|
35
|
+
expect(Dottie.get(hash, 'd')).to be_nil
|
36
|
+
end
|
37
|
+
it 'reads a dotted key' do
|
38
|
+
expect(Dottie.get(hash, 'c.d')).to eq 'e'
|
39
|
+
end
|
40
|
+
it 'returns nil for a missing dotted key' do
|
41
|
+
expect(Dottie.get(hash, 'c.e')).to be_nil
|
42
|
+
end
|
43
|
+
it 'returns nil for a missing nested dotted key' do
|
44
|
+
expect(Dottie.get(hash, 'c.e.g.x.y')).to be_nil
|
45
|
+
end
|
46
|
+
it 'returns nil when trying to walk into a non-Hash/Array' do
|
47
|
+
expect(Dottie.get(hash, 'a.b')).to be_nil
|
48
|
+
end
|
49
|
+
it 'returns nil when trying to walk deep into a non-Hash/Array' do
|
50
|
+
expect(Dottie.get(hash, 'a.b.c')).to be_nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'ending array indexes' do
|
55
|
+
let(:hash) {{ 'a' => 'b', 'c' => ['d', 'e', 'f'] }}
|
56
|
+
|
57
|
+
it 'reads an integer key' do
|
58
|
+
expect(Dottie.get(hash, 'c[0]')).to eq 'd'
|
59
|
+
end
|
60
|
+
it 'reads a negative integer key' do
|
61
|
+
expect(Dottie.get(hash, 'c[-1]')).to eq 'f'
|
62
|
+
end
|
63
|
+
it 'reads a named key (first)' do
|
64
|
+
expect(Dottie.get(hash, 'c[first]')).to eq 'd'
|
65
|
+
end
|
66
|
+
it 'reads a named key (last)' do
|
67
|
+
expect(Dottie.get(hash, 'c[last]')).to eq 'f'
|
68
|
+
end
|
69
|
+
it 'returns nil for a missing index' do
|
70
|
+
expect(Dottie.get(hash, 'c[4]')).to be_nil
|
71
|
+
end
|
72
|
+
it 'returns nil for a missing array' do
|
73
|
+
expect(Dottie.get(hash, 'x[4]')).to be_nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'middle array indexes' do
|
78
|
+
let(:hash) {{ 'a' => 'b', 'c' => [{ 'd' => 'e' }, { 'f' => 'g' }] }}
|
79
|
+
|
80
|
+
it 'reads an integer key' do
|
81
|
+
expect(Dottie.get(hash, 'c[0].d')).to eq 'e'
|
82
|
+
end
|
83
|
+
it 'reads a negative integer key' do
|
84
|
+
expect(Dottie.get(hash, 'c[-1].f')).to eq 'g'
|
85
|
+
end
|
86
|
+
it 'reads a named key (first)' do
|
87
|
+
expect(Dottie.get(hash, 'c[first].d')).to eq 'e'
|
88
|
+
end
|
89
|
+
it 'reads a named key (last)' do
|
90
|
+
expect(Dottie.get(hash, 'c[last].f')).to eq 'g'
|
91
|
+
end
|
92
|
+
it 'returns nil for a missing index' do
|
93
|
+
expect(Dottie.get(hash, 'c[4].r')).to be_nil
|
94
|
+
end
|
95
|
+
it 'returns nil for a missing array' do
|
96
|
+
expect(Dottie.get(hash, 'x[4].s')).to be_nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'consecutive array indexes' do
|
101
|
+
let(:hash) {{ 'a' => 'b', 'c' => [ [{}, { 'd' => 'e' }] ] }}
|
102
|
+
|
103
|
+
it 'reads an integer key' do
|
104
|
+
expect(Dottie.get(hash, 'c[0][1].d')).to eq 'e'
|
105
|
+
end
|
106
|
+
it 'reads a negative integer key' do
|
107
|
+
expect(Dottie.get(hash, 'c[-1][1].d')).to eq 'e'
|
108
|
+
end
|
109
|
+
it 'reads a named key (first)' do
|
110
|
+
expect(Dottie.get(hash, 'c[first][last].d')).to eq 'e'
|
111
|
+
end
|
112
|
+
it 'reads a named key (last)' do
|
113
|
+
expect(Dottie.get(hash, 'c[last][last].d')).to eq 'e'
|
114
|
+
end
|
115
|
+
it 'returns nil for a missing index' do
|
116
|
+
expect(Dottie.get(hash, 'c[4][5]')).to be_nil
|
117
|
+
end
|
118
|
+
it 'returns nil for a missing array' do
|
119
|
+
expect(Dottie.get(hash, 'x[4][5]')).to be_nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'key existence' do
|
126
|
+
let(:hash) {{ 'a' => 'b', 'c' => { 'd' => ['e', 'f', 'g'] } }}
|
127
|
+
|
128
|
+
it "finds a standard key" do
|
129
|
+
expect(Dottie.has_key?(hash, 'a')).to be_true
|
130
|
+
end
|
131
|
+
it "does not find a missing standard key" do
|
132
|
+
expect(Dottie.has_key?(hash, 'x')).to be_false
|
133
|
+
end
|
134
|
+
it "finds a Dottie key (Hash value)" do
|
135
|
+
expect(Dottie.has_key?(hash, 'c.d')).to be_true
|
136
|
+
end
|
137
|
+
it "finds a Dottie key (Array element)" do
|
138
|
+
expect(Dottie.has_key?(hash, 'c.d[0]')).to be_true
|
139
|
+
end
|
140
|
+
it "does not find a missing Dottie key (first part is a String)" do
|
141
|
+
expect(Dottie.has_key?(hash, 'a.b')).to be_false
|
142
|
+
end
|
143
|
+
it "does not find a missing Dottie key (first part exists)" do
|
144
|
+
expect(Dottie.has_key?(hash, 'c.x')).to be_false
|
145
|
+
end
|
146
|
+
it "does not find a missing Dottie key (outside Array bounds)" do
|
147
|
+
expect(Dottie.has_key?(hash, 'c.d[4]')).to be_false
|
148
|
+
end
|
149
|
+
it "does not find a missing Dottie key (no part exists)" do
|
150
|
+
expect(Dottie.has_key?(hash, 'x.y')).to be_false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe 'fetching' do
|
155
|
+
let(:hash) {{ 'a' => 'b', 'c' => { 'd' => ['e', 'f', 'g'] } }}
|
156
|
+
|
157
|
+
context 'no default' do
|
158
|
+
it 'fetches a standard key' do
|
159
|
+
expect(Dottie.fetch(hash, 'a')).to eq 'b'
|
160
|
+
end
|
161
|
+
it 'fetches a Dottie key (Hash value)' do
|
162
|
+
expect(Dottie.fetch(hash, 'c.d')).to eq ['e', 'f', 'g']
|
163
|
+
end
|
164
|
+
it 'fetches a Dottie key (Array element)' do
|
165
|
+
expect(Dottie.fetch(hash, 'c.d[1]')).to eq 'f'
|
166
|
+
end
|
167
|
+
it 'raises on a missing standard key' do
|
168
|
+
expect{ Dottie.fetch(hash, 'x') }.to raise_error KeyError
|
169
|
+
end
|
170
|
+
it 'raises on a missing Dottie key' do
|
171
|
+
expect{ Dottie.fetch(hash, 'x.y') }.to raise_error KeyError
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'with default' do
|
176
|
+
it 'fetches a standard key' do
|
177
|
+
expect(Dottie.fetch(hash, 'a', 'z')).to eq 'b'
|
178
|
+
end
|
179
|
+
it 'fetches a Dottie key' do
|
180
|
+
expect(Dottie.fetch(hash, 'c.d', 'z')).to eq ['e', 'f', 'g']
|
181
|
+
end
|
182
|
+
it 'returns a default for a missing standard key' do
|
183
|
+
expect(Dottie.fetch(hash, 'x', 'z')).to eq 'z'
|
184
|
+
end
|
185
|
+
it 'returns a default for a missing Dottie key' do
|
186
|
+
expect(Dottie.fetch(hash, 'x', 'z')).to eq 'z'
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'with block' do
|
191
|
+
it 'fetches a standard key' do
|
192
|
+
expect(Dottie.fetch(hash, 'a'){ |key| key.upcase }).to eq 'b'
|
193
|
+
end
|
194
|
+
it 'fetches a Dottie key' do
|
195
|
+
expect(Dottie.fetch(hash, 'c.d'){ |key| key.upcase }).to eq ['e', 'f', 'g']
|
196
|
+
end
|
197
|
+
it 'yields to a block for a missing standard key' do
|
198
|
+
expect(Dottie.fetch(hash, 'x'){ |key| key.upcase }).to eq 'X'
|
199
|
+
end
|
200
|
+
it 'yields to a block for a missing Dottie key' do
|
201
|
+
expect(Dottie.fetch(hash, 'x.y'){ |key| key.upcase }).to eq 'X.Y'
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
describe 'writing' do
|
208
|
+
|
209
|
+
context 'simple' do
|
210
|
+
before :each do
|
211
|
+
@hash = { 'a' => 'b', 'c' => { 'd' => 'e' } }
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'overwrites a standard key' do
|
215
|
+
Dottie.set(@hash, 'c', 'd')
|
216
|
+
expect(@hash).to eq({ 'a' => 'b', 'c' => 'd' })
|
217
|
+
end
|
218
|
+
it 'overwrites a dotted key' do
|
219
|
+
Dottie.set(@hash, 'c.d', 'm')
|
220
|
+
expect(@hash).to eq({ 'a' => 'b', 'c' => { 'd' => 'm' } })
|
221
|
+
end
|
222
|
+
it 'creates a value at a non-existent standard key' do
|
223
|
+
Dottie.set(@hash, 'n', 'p')
|
224
|
+
expect(@hash).to eq({ 'a' => 'b', 'c' => { 'd' => 'e' }, 'n' => 'p' })
|
225
|
+
end
|
226
|
+
it 'creates a hash at a non-existent dotted key' do
|
227
|
+
Dottie.set(@hash, 'n.o', 'p')
|
228
|
+
expect(@hash).to eq({ 'a' => 'b', 'c' => { 'd' => 'e' }, 'n' => { 'o' => 'p' } })
|
229
|
+
end
|
230
|
+
it 'raises an error when trying to write to a non-Hash/Array' do
|
231
|
+
expect{ Dottie.set(@hash, 'a.b', 'r') }.to raise_error TypeError
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
context 'array indexes' do
|
236
|
+
before :each do
|
237
|
+
@hash = { 'a' => 'b', 'c' => ['d', 'e', 'f'] }
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'overwrites an array element (positive index)' do
|
241
|
+
Dottie.set(@hash, 'c[0]', 'x')
|
242
|
+
expect(@hash).to eq({ 'a' => 'b', 'c' => ['x', 'e', 'f'] })
|
243
|
+
end
|
244
|
+
it 'overwrites an array element (negative index)' do
|
245
|
+
Dottie.set(@hash, 'c[-2]', 'y')
|
246
|
+
expect(@hash).to eq({ 'a' => 'b', 'c' => ['d', 'y', 'f'] })
|
247
|
+
end
|
248
|
+
it 'creates an array at a non-existent key (positive index)' do
|
249
|
+
Dottie.set(@hash, 'r[0]', 's')
|
250
|
+
expect(@hash).to eq({ 'a' => 'b', 'c' => ['d', 'e', 'f'], 'r' => ['s'] })
|
251
|
+
end
|
252
|
+
it 'adds an array element' do
|
253
|
+
Dottie.set(@hash, 'c[3]', 'g')
|
254
|
+
expect(@hash).to eq({ 'a' => 'b', 'c' => ['d', 'e', 'f', 'g'] })
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context 'invalid' do
|
259
|
+
before :each do
|
260
|
+
@hash = { 'a' => 'b', 'c' => { 'd' => 'e' }, 'f' => ['g', 'h'] }
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'raises an error when trying to write a Hash key to an Array' do
|
264
|
+
expect{ Dottie.set(@hash, 'f.x', 'y') }.to raise_error TypeError
|
265
|
+
end
|
266
|
+
it 'raises an error when trying to write a Hash key to a non-Hash/Array' do
|
267
|
+
expect{ Dottie.set(@hash, 'a.x', 'y') }.to raise_error TypeError
|
268
|
+
end
|
269
|
+
it 'raises an error when trying to write an Array index to a non-Hash/Array' do
|
270
|
+
expect{ Dottie.set(@hash, 'a[0]', 'r') }.to raise_error TypeError
|
271
|
+
end
|
272
|
+
it 'does not raise an error when trying to write an Array index to a Hash' do
|
273
|
+
Dottie.set(@hash, 'c[0]', 'm')
|
274
|
+
expect(@hash).to eq({ 'a' => 'b', 'c' => { 'd' => 'e', 0 => 'm' }, 'f' => ['g', 'h'] })
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
end
|
279
|
+
|
280
|
+
describe 'key identification' do
|
281
|
+
|
282
|
+
it 'recognizes a dotted key' do
|
283
|
+
key = 'a.b.c'
|
284
|
+
expect(Dottie.dottie_key?(key)).to be_true
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'recognizes a bracketed key' do
|
288
|
+
key = 'a[0]b'
|
289
|
+
expect(Dottie.dottie_key?(key)).to be_true
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'recognizes an array as a Dottie key' do
|
293
|
+
key = ['a', 'b', 'c']
|
294
|
+
expect(Dottie.dottie_key?(key)).to be_true
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'does not recognize a normal key' do
|
298
|
+
key = 'a_b_c'
|
299
|
+
expect(Dottie.dottie_key?(key)).to be_false
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
|
304
|
+
describe 'key parsing' do
|
305
|
+
|
306
|
+
it 'returns a key array untouched' do
|
307
|
+
arr = ['a', 'b', 'c']
|
308
|
+
expect(Dottie.key_parts(arr)).to eq arr
|
309
|
+
end
|
310
|
+
|
311
|
+
it 'returns a non-Dottie key as a single-element array' do
|
312
|
+
str = 'some_key'
|
313
|
+
arr = [str]
|
314
|
+
expect(Dottie.key_parts(str)).to eq arr
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'converts a dotted key into an array' do
|
318
|
+
str = 'a.b.c'
|
319
|
+
arr = ['a', 'b', 'c']
|
320
|
+
expect(Dottie.key_parts(str)).to eq arr
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'converts a bracketed string key into an array' do
|
324
|
+
str = 'a[b]c'
|
325
|
+
arr = ['a', 'b', 'c']
|
326
|
+
expect(Dottie.key_parts(str)).to eq arr
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'treats integers as strings when part of a string (prefix)' do
|
330
|
+
str = 'a.0b.c'
|
331
|
+
arr = ['a', '0b', 'c']
|
332
|
+
expect(Dottie.key_parts(str)).to eq arr
|
333
|
+
end
|
334
|
+
|
335
|
+
it 'treats integers as strings when part of a string (postfix)' do
|
336
|
+
str = 'a.b1.c'
|
337
|
+
arr = ['a', 'b1', 'c']
|
338
|
+
expect(Dottie.key_parts(str)).to eq arr
|
339
|
+
end
|
340
|
+
|
341
|
+
it 'treats dashes as strings' do
|
342
|
+
str = 'a.-.c'
|
343
|
+
arr = ['a', '-', 'c']
|
344
|
+
expect(Dottie.key_parts(str)).to eq arr
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'converts a Dottie key with array indexes into a string/integer array' do
|
348
|
+
str = 'a.b[0].c[-1]'
|
349
|
+
arr = ['a', 'b', 0, 'c', -1]
|
350
|
+
expect(Dottie.key_parts(str)).to eq arr
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'converts a Dottie key with array references into a string/integer array' do
|
354
|
+
str = 'a.b[first].c[last]'
|
355
|
+
arr = ['a', 'b', 0, 'c', -1]
|
356
|
+
expect(Dottie.key_parts(str)).to eq arr
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'converts a Dottie key with bracketed array references into a string/integer array' do
|
360
|
+
str = 'a.b[first].c[last]'
|
361
|
+
arr = ['a', 'b', 0, 'c', -1]
|
362
|
+
expect(Dottie.key_parts(str)).to eq arr
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'converts a complex mix of strings and array indexes and references' do
|
366
|
+
str = 'a.b.first.c[2].-3.d[last]'
|
367
|
+
arr = ['a', 'b', 'first', 'c', 2, '-3', 'd', -1]
|
368
|
+
expect(Dottie.key_parts(str)).to eq arr
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'allows arbitrary strings using array index syntax' do
|
372
|
+
str = 'a.b[middle].c'
|
373
|
+
arr = ['a', 'b', 'middle', 'c']
|
374
|
+
expect(Dottie.key_parts(str)).to eq arr
|
375
|
+
end
|
376
|
+
|
377
|
+
it 'allows dots as part of a key segment when enclosed in brackets' do
|
378
|
+
str = 'a.[b.c].d'
|
379
|
+
arr = ['a', 'b.c', 'd']
|
380
|
+
expect(Dottie.key_parts(str)).to eq arr
|
381
|
+
end
|
382
|
+
|
383
|
+
it 'allows a dot before a bracketed array index' do
|
384
|
+
str = 'a.b.[2].c'
|
385
|
+
arr = ['a', 'b', 2, 'c']
|
386
|
+
expect(Dottie.key_parts(str)).to eq arr
|
387
|
+
end
|
388
|
+
|
389
|
+
it 'does not require a dot after a bracketed array index' do
|
390
|
+
str = 'a.b[2]c'
|
391
|
+
arr = ['a', 'b', 2, 'c']
|
392
|
+
expect(Dottie.key_parts(str)).to eq arr
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'collapses multiple dots into a single dot' do
|
396
|
+
str = 'a.b..c'
|
397
|
+
arr = ['a', 'b', 'c']
|
398
|
+
expect(Dottie.key_parts(str)).to eq arr
|
399
|
+
end
|
400
|
+
|
401
|
+
end
|
402
|
+
|
403
|
+
describe 'key format variants' do
|
404
|
+
let(:arr) { ['a', 0, 'b', 1, 'c', -2, 'd', -1, 'e'] }
|
405
|
+
|
406
|
+
it 'parses dotted format' do
|
407
|
+
str = 'a[0].b[1].c[-2].d[-1].e'
|
408
|
+
expect(Dottie.key_parts(str)).to eq arr
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'parses dotted format (with named array positions)' do
|
412
|
+
str = 'a[first].b[1].c[-2].d[last].e'
|
413
|
+
expect(Dottie.key_parts(str)).to eq arr
|
414
|
+
end
|
415
|
+
|
416
|
+
it 'parses mixed format (with optional dots)' do
|
417
|
+
str = 'a.[0].b.[1].c.[-2].d.[-1].e'
|
418
|
+
expect(Dottie.key_parts(str)).to eq arr
|
419
|
+
end
|
420
|
+
|
421
|
+
it 'parses mixed format (without optional dots)' do
|
422
|
+
str = 'a[0]b[1]c[-2]d[-1]e'
|
423
|
+
expect(Dottie.key_parts(str)).to eq arr
|
424
|
+
end
|
425
|
+
|
426
|
+
it 'parses consecutive array indexes and positions' do
|
427
|
+
str = 'a.first[1][2]3[4]5.6.-7[-8]-9.[last]'
|
428
|
+
arr = ['a', 'first', 1, 2, '3', 4, '5', '6', '-7', -8, '-9', -1]
|
429
|
+
expect(Dottie.key_parts(str)).to eq arr
|
430
|
+
end
|
431
|
+
|
432
|
+
end
|
433
|
+
|
434
|
+
end
|