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