dottie 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/dottie/ext.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'dottie'
2
+ require 'dottie/ext/array'
3
+ require 'dottie/ext/hash'
@@ -0,0 +1,17 @@
1
+ class Array
2
+
3
+ ##
4
+ # Creates a new Dottie::Freckle that wraps this Array.
5
+
6
+ def dottie
7
+ Dottie::Freckle.new(self)
8
+ end
9
+
10
+ ##
11
+ # Adds Dottie's behaviors to this Array.
12
+
13
+ def dottie!
14
+ self.extend(Dottie::Methods)
15
+ end
16
+
17
+ end
@@ -0,0 +1,17 @@
1
+ class Hash
2
+
3
+ ##
4
+ # Creates a new Dottie::Freckle that wraps this Hash.
5
+
6
+ def dottie
7
+ Dottie::Freckle.new(self)
8
+ end
9
+
10
+ ##
11
+ # Adds Dottie's behaviors to this Hash.
12
+
13
+ def dottie!
14
+ self.extend(Dottie::Methods)
15
+ end
16
+
17
+ end
@@ -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,6 @@
1
+ ##
2
+ # Creates a new Dottie::Freckle from a standard Hash or Array.
3
+
4
+ def Dottie(obj)
5
+ Dottie[obj]
6
+ 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
@@ -0,0 +1,3 @@
1
+ module Dottie
2
+ VERSION = '0.0.1'
3
+ end
@@ -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
@@ -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