dense 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/LICENSE.txt +1 -1
- data/README.md +313 -0
- data/lib/dense.rb +2 -1
- data/lib/dense/methods.rb +114 -83
- data/lib/dense/parser.rb +102 -0
- data/lib/dense/path.rb +135 -237
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 314a76ad0969382d65813cfa4ab3eb62db8a1ecf
|
4
|
+
data.tar.gz: ff138616d42d086fd7abbd1beee76c9f81c55172
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bcc1ff383a330a6da5068fd16774663e65577290bc7737a02d5102188f5c79d0fb2cbd24a627d607c7366ec13262d738b12121d61ac62042cbdf37883d7d0cf7
|
7
|
+
data.tar.gz: '098e9342bec870d2f21339c89e70d8825aaa24381234276d1e22f20391fd9488009589dcc1e1d01a1a75c57a0048dd8cb62b591ba2db55c5989519695dc86dff'
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,15 @@
|
|
2
2
|
# dense
|
3
3
|
|
4
4
|
|
5
|
+
## dense 1.1.0 released 2018-04-29
|
6
|
+
|
7
|
+
* Add Dense.path(path) and Dense.gather(collection, path)
|
8
|
+
* Use but enhance KeyError and TypeError
|
9
|
+
* Differentiate between `*` and `.*`
|
10
|
+
* Complete ework around Path#gather
|
11
|
+
* Straighten Dense.fetch
|
12
|
+
|
13
|
+
|
5
14
|
## dense 1.0.0 released 2017-09-29
|
6
15
|
|
7
16
|
* Accept `owner[age]` (unquoted key name in bracket index)
|
data/LICENSE.txt
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
Copyright (c) 2017-
|
2
|
+
Copyright (c) 2017-2018, John Mettraux, jmettraux+flor@gmail.com
|
3
3
|
|
4
4
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -6,6 +6,319 @@
|
|
6
6
|
|
7
7
|
Fetching deep in a dense structure. A kind of bastard of [JSONPath](http://goessner.net/articles/JsonPath/).
|
8
8
|
|
9
|
+
## usage
|
10
|
+
|
11
|
+
Let
|
12
|
+
```ruby
|
13
|
+
data = # taken from http://goessner.net/articles/JsonPath/
|
14
|
+
{ 'store' => {
|
15
|
+
'book' => [
|
16
|
+
{ 'category' => 'reference',
|
17
|
+
'author' => 'Nigel Rees',
|
18
|
+
'title' => 'Sayings of the Century',
|
19
|
+
'price' => 8.95
|
20
|
+
},
|
21
|
+
{ 'category' => 'fiction',
|
22
|
+
'author' => 'Evelyn Waugh',
|
23
|
+
'title' => 'Sword of Honour',
|
24
|
+
'price' => 12.99
|
25
|
+
},
|
26
|
+
{ 'category' => 'fiction',
|
27
|
+
'author' => 'Herman Melville',
|
28
|
+
'title' => 'Moby Dick',
|
29
|
+
'isbn' => '0-553-21311-3',
|
30
|
+
'price' => 8.99
|
31
|
+
},
|
32
|
+
{ 'category' => 'fiction',
|
33
|
+
'author' => 'J. R. R. Tolkien',
|
34
|
+
'title' => 'The Lord of the Rings',
|
35
|
+
'isbn' => '0-395-19395-8',
|
36
|
+
'price' => 22.99
|
37
|
+
}
|
38
|
+
],
|
39
|
+
'bicycle' => {
|
40
|
+
'color' => 'red',
|
41
|
+
'price' => 19.95,
|
42
|
+
'7' => 'seven'
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
```
|
47
|
+
|
48
|
+
### paths
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
"store.book.1.title" # the title of the second book in the store
|
52
|
+
"store.book[1].title" # the title of the second book in the store
|
53
|
+
"store.book.1['french title']" # the french title of the 2nd book
|
54
|
+
"store.book.1[title,author]" # the title and the author of the 2nd book
|
55
|
+
"store.book[1,3].title" # the titles of the 2nd and 4th books
|
56
|
+
"store.book[1:8:2].title" # titles of books at offset 1, 3, 5, 7
|
57
|
+
"store.book[::3].title" # titles of books at offset 0, 3, 6, 9, ...
|
58
|
+
"store.book[:3].title" # titles of books at offset 0, 1, 2, 3
|
59
|
+
"store.*.price" # the price of everything directly in the store
|
60
|
+
"store..price" # the price of everything in the store
|
61
|
+
# ...
|
62
|
+
```
|
63
|
+
|
64
|
+
### `Dense.get(collection, path)`
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
Dense.get(data, 'store.book.1.title')
|
68
|
+
# => "Sword of Honour"
|
69
|
+
|
70
|
+
Dense.get(data, 'store.book.*.title')
|
71
|
+
# => [
|
72
|
+
# 'Sayings of the Century',
|
73
|
+
# 'Sword of Honour',
|
74
|
+
# 'Moby Dick',
|
75
|
+
# 'The Lord of the Rings' ]
|
76
|
+
|
77
|
+
Dense.get(data, 'store.bicycle.7')
|
78
|
+
# => "seven"
|
79
|
+
```
|
80
|
+
|
81
|
+
When `Dense.get(collection, path)` doesn't find, it returns `nil`.
|
82
|
+
|
83
|
+
As seen above `Dense.get` might return a single value or an array of values. A "single" path like `"store.book.1.title"` will return a single value, while a "multiple" path like `"store.book.*.title"` or `"store.book[1,2].title"` will return an array of values.
|
84
|
+
|
85
|
+
|
86
|
+
### `Dense.has_key?(collection, path)`
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
Dense.has_key?(data, 'store.book.1.title')
|
90
|
+
# => true
|
91
|
+
Dense.has_key?(data, 'store.book.1["social security number"]')
|
92
|
+
# => false
|
93
|
+
```
|
94
|
+
|
95
|
+
|
96
|
+
### `Dense.fetch(collection, path)`
|
97
|
+
|
98
|
+
`Dense.fetch` is modelled after `Hash.fetch`.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
Dense.fetch(data, 'store.book.1.title')
|
102
|
+
# => 'Sword of Honour'
|
103
|
+
|
104
|
+
Dense.fetch(data, 'store.book.*.title')
|
105
|
+
# => [ 'Sayings of the Century', 'Sword of Honour', 'Moby Dick',
|
106
|
+
# 'The Lord of the Rings' ]
|
107
|
+
|
108
|
+
Dense.fetch(data, 'store.bicycle.7')
|
109
|
+
# => 'seven'
|
110
|
+
|
111
|
+
Dense.fetch(data, 'store.bicycle[7]')
|
112
|
+
# => 'seven'
|
113
|
+
```
|
114
|
+
|
115
|
+
When it doesn't find, it raises an instance of `KeyError`:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
Dense.fetch({}, 'a.0.b')
|
119
|
+
# raises
|
120
|
+
# KeyError: Found nothing at "a" ("0.b" remains)
|
121
|
+
```
|
122
|
+
|
123
|
+
It might instead raise an instance of `TypeError` if a non-integer key is requested of an array:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
Dense.fetch({ 'a' => [] }, 'a.k.b')
|
127
|
+
# raises
|
128
|
+
# TypeError: No key "k" for Array at "a"
|
129
|
+
```
|
130
|
+
|
131
|
+
See KeyError and TypeError below for more details.
|
132
|
+
|
133
|
+
`Dense.fetch(collection, path)` raises when it doesn't find, while `Dense.get(collection, path)` returns `nil`.
|
134
|
+
|
135
|
+
|
136
|
+
### `Dense.fetch(collection, path, default)`
|
137
|
+
|
138
|
+
`Dense.fetch` is modelled after `Hash.fetch` so it features a `default` optional argument.
|
139
|
+
|
140
|
+
If `fetch` doesn't find, it will return the provided default value.
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
Dense.fetch(data, 'store.book.1.title', -1)
|
144
|
+
# => "Sword of Honour" (found)
|
145
|
+
Dense.fetch(data, 'a.0.b', -1)
|
146
|
+
# => -1
|
147
|
+
Dense.fetch(data, 'store.nada', 'x')
|
148
|
+
# => "x"
|
149
|
+
Dense.fetch(data, 'store.bicycle.seven', false)
|
150
|
+
# => false
|
151
|
+
```
|
152
|
+
|
153
|
+
|
154
|
+
### `Dense.fetch(collection, path) { block }`
|
155
|
+
|
156
|
+
`Dense.fetch` is modelled after `Hash.fetch` so it features a 'default' optional block.
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
Dense.fetch(data, 'store.book.1.title') do |coll, path|
|
160
|
+
"len:#{coll.length},path:#{path}"
|
161
|
+
end
|
162
|
+
# => "Sword of Honour" (found)
|
163
|
+
|
164
|
+
Dense.fetch(@data, 'store.bicycle.otto') do |coll, path|
|
165
|
+
"len:#{coll.length},path:#{path}"
|
166
|
+
end
|
167
|
+
# => "len:18,path:store.bicycle.otto" (not found)
|
168
|
+
|
169
|
+
not_found = lambda { |coll, path| "not found!" }
|
170
|
+
#
|
171
|
+
Dense.fetch(@data, 'store.bicycle.otto', not_found)
|
172
|
+
# => "not found!"
|
173
|
+
Dense.fetch(@data, 'store.bicycle.sept', not_found)
|
174
|
+
# => "not found!"
|
175
|
+
```
|
176
|
+
|
177
|
+
|
178
|
+
### `Dense.set(collection, path, value)`
|
179
|
+
|
180
|
+
Sets a value "deep" in a collection. Returns the value if successful.
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
c = {}
|
184
|
+
r = Dense.set(c, 'a', 1)
|
185
|
+
c # => { 'a' => 1 }
|
186
|
+
r # => 1
|
187
|
+
|
188
|
+
c = { 'h' => {} }
|
189
|
+
r = Dense.set(c, 'h.i', 1)
|
190
|
+
c # => { 'h' => { 'i' => 1 } }
|
191
|
+
r # => 1
|
192
|
+
|
193
|
+
c = { 'a' => [ 1, 2, 3 ] }
|
194
|
+
r = Dense.set(c, 'a.1', 1)
|
195
|
+
c # => { 'a' => [ 1, 1, 3 ] }
|
196
|
+
r # => 1
|
197
|
+
|
198
|
+
c = { 'h' => { 'a' => [ 1, 2, 3 ] } }
|
199
|
+
r = Dense.set(c, 'h.a.first', 'one')
|
200
|
+
c # => { 'h' => { 'a' => [ "one", 2, 3 ] } }
|
201
|
+
r # => 'one'
|
202
|
+
|
203
|
+
c = { 'h' => { 'a' => [ 1, 2, 3 ] } }
|
204
|
+
r = Dense.set(c, 'h.a.last', 'three')
|
205
|
+
c # => { 'h' => { 'a' => [ 1, 2, 'three' ] } }
|
206
|
+
r # => 'three'
|
207
|
+
|
208
|
+
c = { 'a' => [] }
|
209
|
+
Dense.set(c, 'a.b', 1)
|
210
|
+
# => TypeError: No key "b" for Array at "a"
|
211
|
+
|
212
|
+
|
213
|
+
c = { 'a' => {} }
|
214
|
+
r = Dense.set(c, 'a.1', 1)
|
215
|
+
c # => { 'a' => { '1' => 1 } }
|
216
|
+
r # => 1
|
217
|
+
|
218
|
+
c = {}
|
219
|
+
Dense.set(c, 'a.0', 1)
|
220
|
+
# => KeyError: Found nothing at "a" ("0" remains)
|
221
|
+
```
|
222
|
+
|
223
|
+
Setting at multiple places in one go is possible:
|
224
|
+
```ruby
|
225
|
+
c = { 'h' => {} }
|
226
|
+
Dense.set(c, 'h[k0,k1,k2]', 123)
|
227
|
+
c
|
228
|
+
# => { 'h' => { 'k0' => 123, 'k1' => 123, 'k2' => 123 } }
|
229
|
+
```
|
230
|
+
|
231
|
+
|
232
|
+
### `Dense.insert(collection, path, value)`
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
c = { 'a' => [ 0, 1, 2, 3 ] }
|
236
|
+
r = Dense.insert(c, 'b', 1234)
|
237
|
+
c
|
238
|
+
# => { "a" => [ 0, 1, 2, 3 ], "b" => 1234 }
|
239
|
+
|
240
|
+
c = { 'a' => [ 0, 1, 2, 3 ] }
|
241
|
+
r = Dense.insert(c, 'a.1', 'ONE')
|
242
|
+
c
|
243
|
+
# => { "a" => [ 0, "ONE", 1, 2, 3 ] }
|
244
|
+
|
245
|
+
c = { 'a' => [ 0, 1, 2, 3 ], 'a1' => [ 0, 1 ] }
|
246
|
+
r = Dense.insert(c, '.1', 'ONE')
|
247
|
+
c
|
248
|
+
# => { "a" => [ 0, "ONE", 1, 2, 3 ], "a1" => [ 0, "ONE", 1 ] }
|
249
|
+
```
|
250
|
+
|
251
|
+
|
252
|
+
### `Dense.unset(collection, path)`
|
253
|
+
|
254
|
+
Removes an element deep in a collection.
|
255
|
+
```ruby
|
256
|
+
c = { 'a' => 1 }
|
257
|
+
r = Dense.unset(c, 'a')
|
258
|
+
c # => {}
|
259
|
+
r # => 1
|
260
|
+
|
261
|
+
c = { 'h' => { 'i' => 1 } }
|
262
|
+
r = Dense.unset(c, 'h.i')
|
263
|
+
c # => { 'h' => {} }
|
264
|
+
r # => 1
|
265
|
+
|
266
|
+
c = { 'a' => [ 1, 2, 3 ] }
|
267
|
+
r = Dense.unset(c, 'a.1')
|
268
|
+
c # => { 'a' => [ 1, 3 ] }
|
269
|
+
r # => 2
|
270
|
+
|
271
|
+
c = { 'h' => { 'a' => [ 1, 2, 3 ] } }
|
272
|
+
r = Dense.unset(c, 'h.a.first')
|
273
|
+
c # => { 'h' => { 'a' => [ 2, 3 ] } }
|
274
|
+
r # => 1
|
275
|
+
|
276
|
+
c = { 'h' => { 'a' => [ 1, 2, 3 ] } }
|
277
|
+
r = Dense.unset(c, 'h.a.last')
|
278
|
+
c # => { 'h' => { 'a' => [ 1, 2 ] } }
|
279
|
+
r # => 3
|
280
|
+
```
|
281
|
+
|
282
|
+
It fails with a `KeyError` or a `TypeError` if it cannot unset.
|
283
|
+
```ruby
|
284
|
+
Dense.unset({}, 'a')
|
285
|
+
# => KeyError: Found nothing at "a"
|
286
|
+
Dense.unset([], 'a')
|
287
|
+
# => TypeError: No key "a" for Array at root
|
288
|
+
Dense.unset([], '1')
|
289
|
+
# => KeyError: Found nothing at "1"
|
290
|
+
```
|
291
|
+
|
292
|
+
Unsetting multiple values is OK:
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
c = { 'h' => { 'a' => [ 1, 2, 3, 4, 5 ] } }
|
296
|
+
r = Dense.unset(c, 'h.a[2,3]')
|
297
|
+
c
|
298
|
+
# => { 'h' => { 'a' => [ 1, 2, 5 ] } }
|
299
|
+
```
|
300
|
+
|
301
|
+
### KeyError and TypeError
|
302
|
+
|
303
|
+
Dense might raise instances of `KeyError` and `TypeError`. Those instances have extra `#full_path` and `#miss` methods.
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
e =
|
307
|
+
begin
|
308
|
+
Dense.fetch({}, 'a.b')
|
309
|
+
rescue => err
|
310
|
+
err
|
311
|
+
end
|
312
|
+
# => #<KeyError: Found nothing at "a" ("b" remains)>
|
313
|
+
e.full_path
|
314
|
+
# => "a"
|
315
|
+
e.miss
|
316
|
+
# => [false, [], {}, "a", [ "b" ]]
|
317
|
+
```
|
318
|
+
|
319
|
+
The "miss" is an array `[ false, path-to-miss, collection-at-miss, key-at-miss, path-post-miss ]`.
|
320
|
+
|
321
|
+
|
9
322
|
## LICENSE
|
10
323
|
|
11
324
|
MIT, see [LICENSE.txt](LICENSE.txt)
|
data/lib/dense.rb
CHANGED
data/lib/dense/methods.rb
CHANGED
@@ -3,141 +3,172 @@ module Dense; class << self
|
|
3
3
|
|
4
4
|
def get(o, path)
|
5
5
|
|
6
|
-
Dense::Path.new(path)
|
6
|
+
pa = Dense::Path.new(path)
|
7
|
+
r = pa.gather(o).inject([]) { |a, e| a << e[2][e[3]] if e.first; a }
|
8
|
+
|
9
|
+
pa.narrow(r)
|
7
10
|
end
|
8
11
|
|
9
|
-
def fetch(o, path, default
|
12
|
+
def fetch(o, path, default=::KeyError, &block)
|
10
13
|
|
11
|
-
Dense::Path.new(path)
|
12
|
-
|
14
|
+
pa = Dense::Path.new(path)
|
15
|
+
r = pa.gather(o).partition(&:first)
|
13
16
|
|
14
|
-
|
17
|
+
if r[0].empty?
|
18
|
+
|
19
|
+
return pa.narrow(
|
20
|
+
r[1].collect { |m| call_default_block(o, path, block, m) }
|
21
|
+
) if block
|
15
22
|
|
16
|
-
|
17
|
-
|
23
|
+
return pa.narrow(
|
24
|
+
r[1].collect { |m| default }
|
25
|
+
) if default != KeyError
|
18
26
|
|
19
|
-
|
20
|
-
when Array then array_set(c, key, value)
|
21
|
-
when Hash then c[key.to_s] = value
|
22
|
-
else fail KeyError.new("Found no collection at #{path.to_s.inspect}")
|
27
|
+
fail miss_error(path, r[1].first)
|
23
28
|
end
|
24
29
|
|
30
|
+
pa.narrow(r[0].collect { |e| e[2][e[3]] })
|
31
|
+
end
|
32
|
+
|
33
|
+
def set(o, path, value)
|
34
|
+
|
35
|
+
Dense::Path.new(path)
|
36
|
+
.gather(o)
|
37
|
+
.each { |hit|
|
38
|
+
fail_miss_error(path, hit) if hit[0] == false
|
39
|
+
hit[2][hit[3]] = value }
|
40
|
+
|
25
41
|
value
|
26
42
|
end
|
27
43
|
|
28
|
-
def unset(o, path)
|
44
|
+
def unset(o, path, nofail=false)
|
29
45
|
|
30
|
-
|
31
|
-
|
46
|
+
pa = Dense::Path.new(path)
|
47
|
+
hits = pa.gather(o)
|
32
48
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
49
|
+
hits.each { |h| fail miss_error(path, h) unless h[0] } unless nofail
|
50
|
+
|
51
|
+
r = hits
|
52
|
+
.sort_by { |e| "#{e[2].hash}|#{e[3]}" }
|
53
|
+
.reverse
|
54
|
+
.inject([]) { |a, e|
|
55
|
+
next a.push(nil) unless e[0]
|
56
|
+
k = e[3]
|
57
|
+
a.push(e[2].is_a?(Array) ? e[2].delete_at(k) : e[2].delete(k)) }
|
58
|
+
.reverse
|
59
|
+
|
60
|
+
pa.narrow(r)
|
38
61
|
end
|
39
62
|
|
40
63
|
def insert(o, path, value)
|
41
64
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
65
|
+
Dense::Path.new(path)
|
66
|
+
.gather(o)
|
67
|
+
.each { |hit|
|
68
|
+
fail_miss_error(path, hit) if hit[0] == false
|
69
|
+
if hit[2].is_a?(Array)
|
70
|
+
hit[2].insert(hit[3], value)
|
71
|
+
else
|
72
|
+
hit[2][hit[3]] = value
|
73
|
+
end }
|
50
74
|
|
51
75
|
value
|
52
76
|
end
|
53
77
|
|
54
78
|
def has_key?(o, path)
|
55
79
|
|
56
|
-
|
57
|
-
key = path.pop
|
58
|
-
|
59
|
-
case c = path.walk(o)
|
60
|
-
when Array then array_has_key?(c, key)
|
61
|
-
when Hash then hash_has_key?(c, key)
|
62
|
-
else fail IndexError.new("Found no collection at #{path.to_s.inspect}")
|
63
|
-
end
|
80
|
+
!! Dense::Path.new(path).gather(o).find { |m| m[0] }
|
64
81
|
end
|
65
82
|
|
66
|
-
|
67
|
-
|
68
|
-
def array_i(k, may_fail=true)
|
83
|
+
def path(path)
|
69
84
|
|
70
|
-
|
71
|
-
when 'first' then 0
|
72
|
-
when 'last' then -1
|
73
|
-
when Integer then k
|
74
|
-
else
|
75
|
-
may_fail ?
|
76
|
-
fail(IndexError.new("Cannot index array at #{k.inspect}")) :
|
77
|
-
nil
|
78
|
-
end
|
85
|
+
Dense::Path.new(path)
|
79
86
|
end
|
80
87
|
|
81
|
-
def
|
88
|
+
def gather(o, path)
|
82
89
|
|
83
|
-
|
84
|
-
|
90
|
+
Dense::Path.new(path).gather(o)
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
85
94
|
|
86
|
-
|
87
|
-
"Array has length of #{a.length}, index is at #{k.inspect}"
|
88
|
-
) if i < 0 || i >= a.length
|
95
|
+
module DenseError
|
89
96
|
|
90
|
-
|
91
|
-
end
|
97
|
+
attr_accessor :full_path, :miss
|
92
98
|
|
93
|
-
|
99
|
+
# Used by some "clients" (like flor) to relabel (change the error message)
|
100
|
+
# a reraise.
|
101
|
+
#
|
102
|
+
def relabel(message)
|
94
103
|
|
95
|
-
|
104
|
+
err = self.class.new(message)
|
105
|
+
class << err; include DenseError; end
|
106
|
+
err.set_backtrace(self.backtrace)
|
107
|
+
err.full_path = self.full_path
|
108
|
+
err.miss = self.miss
|
96
109
|
|
97
|
-
|
110
|
+
err
|
111
|
+
end
|
98
112
|
end
|
99
113
|
|
100
|
-
def
|
114
|
+
def make_error(error_class, message, path, miss)
|
101
115
|
|
102
|
-
|
116
|
+
err = error_class.new(message)
|
117
|
+
class << err; include DenseError; end
|
118
|
+
err.full_path = path
|
119
|
+
err.miss = miss
|
103
120
|
|
104
|
-
|
121
|
+
err
|
105
122
|
end
|
106
123
|
|
107
|
-
def
|
124
|
+
def key_error(path, miss)
|
125
|
+
|
126
|
+
path1 = Dense::Path.make(miss[1] + [ miss[3] ]).to_s.inspect
|
127
|
+
path2 = Dense::Path.make(miss[4]).to_s.inspect
|
108
128
|
|
109
|
-
|
129
|
+
msg = "Found nothing at #{path1}"
|
130
|
+
msg = "#{msg} (#{path2} remains)" if path2 != '""'
|
110
131
|
|
111
|
-
|
132
|
+
make_error(KeyError, msg, path, miss)
|
112
133
|
end
|
113
134
|
|
114
|
-
def
|
135
|
+
def type_error(path, miss)
|
115
136
|
|
116
|
-
|
137
|
+
key = miss[3].inspect
|
138
|
+
cla = miss[2].class
|
139
|
+
pat = miss[1].empty? ? 'root' : Dense::Path.make(miss[1]).to_s.inspect
|
117
140
|
|
118
|
-
|
141
|
+
make_error(TypeError, "No key #{key} for #{cla} at #{pat}", path, miss)
|
119
142
|
end
|
120
143
|
|
121
|
-
def
|
144
|
+
def miss_error(path, miss)
|
122
145
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
i
|
132
|
-
end
|
146
|
+
if miss[2].is_a?(Array) && ! miss[3].is_a?(Integer)
|
147
|
+
type_error(path, miss)
|
148
|
+
else
|
149
|
+
key_error(path, miss)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def fail_miss_error(path, miss)
|
133
154
|
|
134
|
-
|
155
|
+
fail miss_error(path, miss) \
|
156
|
+
if miss[4].any?
|
157
|
+
fail type_error(path, miss) \
|
158
|
+
if miss[2].is_a?(Array) && ! miss[2].is_a?(Integer)
|
135
159
|
end
|
136
160
|
|
137
|
-
def
|
161
|
+
def call_default_block(o, path, block, miss)
|
162
|
+
|
163
|
+
# [ collection, path,
|
164
|
+
# path before miss, collection at miss, key at miss, path after miss ]
|
165
|
+
#
|
166
|
+
args = [
|
167
|
+
o, path,
|
168
|
+
Dense::Path.make(miss[1]), miss[2], miss[3], Dense::Path.make(miss[4])
|
169
|
+
][0, block.arity]
|
138
170
|
|
139
|
-
|
140
|
-
h.has_key?(k)
|
171
|
+
block.call(*args)
|
141
172
|
end
|
142
|
-
end; end
|
173
|
+
end; end # Dense
|
143
174
|
|
data/lib/dense/parser.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
|
2
|
+
module Dense::Path::Parser include ::Raabro
|
3
|
+
|
4
|
+
# piece parsers bottom to top
|
5
|
+
|
6
|
+
def dqname(i)
|
7
|
+
|
8
|
+
rex(:qname, i, %r{
|
9
|
+
"(
|
10
|
+
\\["\\\/bfnrt] |
|
11
|
+
\\u[0-9a-fA-F]{4} |
|
12
|
+
[^"\\\b\f\n\r\t]
|
13
|
+
)*"
|
14
|
+
}x)
|
15
|
+
end
|
16
|
+
|
17
|
+
def sqname(i)
|
18
|
+
|
19
|
+
rex(:qname, i, %r{
|
20
|
+
'(
|
21
|
+
\\['\\\/bfnrt] |
|
22
|
+
\\u[0-9a-fA-F]{4} |
|
23
|
+
[^'\\\b\f\n\r\t]
|
24
|
+
)*'
|
25
|
+
}x)
|
26
|
+
end
|
27
|
+
|
28
|
+
def dot(i); str(nil, i, '.'); end
|
29
|
+
def comma(i); rex(nil, i, / *, */); end
|
30
|
+
def bend(i); str(nil, i, ']'); end
|
31
|
+
def bstart(i); str(nil, i, '['); end
|
32
|
+
def blank(i); str(:blank, i, ''); end
|
33
|
+
|
34
|
+
def name(i); rex(:name, i, /[-+%^<>a-zA-Z0-9_\/\\=?]+/); end
|
35
|
+
def off(i); rex(:off, i, /-?\d+/); end
|
36
|
+
|
37
|
+
def star(i); str(:star, i, '*'); end
|
38
|
+
|
39
|
+
def ses(i) # start:end:step
|
40
|
+
rex(
|
41
|
+
:ses,
|
42
|
+
i,
|
43
|
+
/(
|
44
|
+
(-?\d+)?:(-?\d+)?:(-?\d+)? |
|
45
|
+
(-?\d+)?:(-?\d+)? |
|
46
|
+
-?\d+
|
47
|
+
)/x)
|
48
|
+
end
|
49
|
+
|
50
|
+
def escape(i); rex(:esc, i, /\\[.*]/); end
|
51
|
+
|
52
|
+
def bindex(i); alt(:index, i, :dqname, :sqname, :star, :ses, :name, :blank); end
|
53
|
+
def bindexes(i); jseq(:bindexes, i, :bindex, :comma); end
|
54
|
+
def simple_index(i); alt(:index, i, :off, :escape, :star, :name); end
|
55
|
+
|
56
|
+
def dotdot(i); str(:dotdot, i, '.'); end
|
57
|
+
def dotdotstar(i); rex(:dotdotstar, i, /(\.\.\*|\.\[\*\])/); end
|
58
|
+
def bracket_index(i); seq(nil, i, :bstart, :bindexes, :bend); end
|
59
|
+
def dot_then_index(i); seq(nil, i, :dot, :simple_index); end
|
60
|
+
|
61
|
+
def index(i)
|
62
|
+
alt(nil, i, :dot_then_index, :bracket_index, :dotdotstar, :dotdot)
|
63
|
+
end
|
64
|
+
|
65
|
+
def path(i); rep(:path, i, :index, 1); end # it starts here
|
66
|
+
|
67
|
+
# rewrite parsed tree
|
68
|
+
|
69
|
+
def rewrite_ses(t)
|
70
|
+
a = t.string.split(':').collect { |e| e.empty? ? nil : e.to_i }
|
71
|
+
return a[0] if a[1] == nil && a[2] == nil
|
72
|
+
{ start: a[0], end: a[1], step: a[2] }
|
73
|
+
end
|
74
|
+
def rewrite_esc(t); t.string[1, 1]; end
|
75
|
+
def rewrite_star(t); :star; end
|
76
|
+
def rewrite_dotdot(t); :dot; end
|
77
|
+
def rewrite_dotdotstar(t); :dotstar; end
|
78
|
+
def rewrite_off(t); t.string.to_i; end
|
79
|
+
def rewrite_index(t); rewrite(t.sublookup); end
|
80
|
+
def rewrite_bindexes(t);
|
81
|
+
indexes = t.subgather.collect { |tt| rewrite(tt) }
|
82
|
+
indexes.length == 1 ? indexes[0] : indexes.compact
|
83
|
+
end
|
84
|
+
|
85
|
+
def rewrite_blank(t); nil; end
|
86
|
+
|
87
|
+
def rewrite_qname(t); t.string[1..-2]; end
|
88
|
+
def rewrite_name(t); t.string; end
|
89
|
+
|
90
|
+
def rewrite_path(t)
|
91
|
+
|
92
|
+
t.subgather
|
93
|
+
.collect { |tt|
|
94
|
+
rewrite(tt) }
|
95
|
+
.inject([]) { |a, e| # remove double :dot
|
96
|
+
next (a << e) unless a.last == :dot
|
97
|
+
a.pop if e == :dotstar
|
98
|
+
a << e unless e == :dot
|
99
|
+
a }
|
100
|
+
end
|
101
|
+
end # Dense::Path::Parser
|
102
|
+
|
data/lib/dense/path.rb
CHANGED
@@ -11,9 +11,10 @@ class Dense::Path
|
|
11
11
|
"Argument is a #{s.class}, not a String"
|
12
12
|
) unless s.is_a?(String)
|
13
13
|
|
14
|
-
s = ".#{s}"
|
14
|
+
s = ".#{s}" \
|
15
|
+
unless s[0, 1] == '[' || s[0, 2] == '.['
|
15
16
|
|
16
|
-
#Raabro.pp(Parser.parse(s, debug: 3))
|
17
|
+
#Raabro.pp(Parser.parse(s, debug: 3), colors: true)
|
17
18
|
@path = Parser.parse(s)
|
18
19
|
|
19
20
|
#Raabro.pp(Parser.parse(s, debug: 3), colors: true) unless @path
|
@@ -34,94 +35,14 @@ class Dense::Path
|
|
34
35
|
path
|
35
36
|
end
|
36
37
|
|
37
|
-
|
38
|
+
def single?
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
def dqname(i)
|
42
|
-
|
43
|
-
rex(:qname, i, %r{
|
44
|
-
"(
|
45
|
-
\\["\\\/bfnrt] |
|
46
|
-
\\u[0-9a-fA-F]{4} |
|
47
|
-
[^"\\\b\f\n\r\t]
|
48
|
-
)*"
|
49
|
-
}x)
|
50
|
-
end
|
51
|
-
|
52
|
-
def sqname(i)
|
53
|
-
|
54
|
-
rex(:qname, i, %r{
|
55
|
-
'(
|
56
|
-
\\['\\\/bfnrt] |
|
57
|
-
\\u[0-9a-fA-F]{4} |
|
58
|
-
[^'\\\b\f\n\r\t]
|
59
|
-
)*'
|
60
|
-
}x)
|
61
|
-
end
|
62
|
-
|
63
|
-
def dot(i); str(nil, i, '.'); end
|
64
|
-
def comma(i); rex(nil, i, / *, */); end
|
65
|
-
def bend(i); str(nil, i, ']'); end
|
66
|
-
def bstart(i); str(nil, i, '['); end
|
67
|
-
def blank(i); str(:blank, i, ''); end
|
68
|
-
|
69
|
-
def name(i); rex(:name, i, /[-+%^<>a-zA-Z0-9_\/\\=?]+/); end
|
70
|
-
def off(i); rex(:off, i, /-?\d+/); end
|
71
|
-
|
72
|
-
def star(i); str(:star, i, '*'); end
|
73
|
-
|
74
|
-
def ses(i) # start:end:step
|
75
|
-
rex(
|
76
|
-
:ses,
|
77
|
-
i,
|
78
|
-
/(
|
79
|
-
(-?\d+)?:(-?\d+)?:(-?\d+)? |
|
80
|
-
(-?\d+)?:(-?\d+)? |
|
81
|
-
-?\d+
|
82
|
-
)/x)
|
83
|
-
end
|
84
|
-
|
85
|
-
def escape(i); rex(:esc, i, /\\[.*]/); end
|
86
|
-
|
87
|
-
def bindex(i); alt(:index, i, :dqname, :sqname, :star, :ses, :name, :blank); end
|
88
|
-
def bindexes(i); jseq(:bindexes, i, :bindex, :comma); end
|
89
|
-
def bracket_index(i); seq(nil, i, :bstart, :bindexes, :bend); end
|
90
|
-
def simple_index(i); alt(:index, i, :off, :escape, :star, :name); end
|
91
|
-
|
92
|
-
def dotdot(i); str(:dotdot, i, '.'); end
|
93
|
-
|
94
|
-
def dot_then_index(i); seq(nil, i, :dot, :simple_index); end
|
95
|
-
def index(i); alt(nil, i, :dot_then_index, :bracket_index, :dotdot); end
|
96
|
-
|
97
|
-
def path(i); rep(:path, i, :index, 1); end
|
98
|
-
|
99
|
-
# rewrite parsed tree
|
100
|
-
|
101
|
-
def rewrite_ses(t)
|
102
|
-
a = t.string.split(':').collect { |e| e.empty? ? nil : e.to_i }
|
103
|
-
return a[0] if a[1] == nil && a[2] == nil
|
104
|
-
{ start: a[0], end: a[1], step: a[2] }
|
105
|
-
end
|
106
|
-
def rewrite_esc(t); t.string[1, 1]; end
|
107
|
-
def rewrite_star(t); :star; end
|
108
|
-
def rewrite_dotdot(t); :dot; end
|
109
|
-
def rewrite_off(t); t.string.to_i; end
|
110
|
-
def rewrite_index(t); rewrite(t.sublookup); end
|
111
|
-
def rewrite_bindexes(t);
|
112
|
-
indexes = t.subgather.collect { |tt| rewrite(tt) }
|
113
|
-
indexes.length == 1 ? indexes[0] : indexes.compact
|
114
|
-
end
|
115
|
-
|
116
|
-
def rewrite_blank(t); nil; end
|
117
|
-
|
118
|
-
def rewrite_qname(t); t.string[1..-2]; end
|
119
|
-
def rewrite_name(t); t.string; end
|
40
|
+
! @path.find { |e| e.is_a?(Symbol) || e.is_a?(Hash) || e.is_a?(Array) }
|
41
|
+
end
|
120
42
|
|
121
|
-
|
122
|
-
t.subgather.collect { |tt| rewrite(tt) }
|
123
|
-
end
|
43
|
+
def multiple?
|
124
44
|
|
45
|
+
! single?
|
125
46
|
end
|
126
47
|
|
127
48
|
def to_a
|
@@ -132,6 +53,15 @@ class Dense::Path
|
|
132
53
|
def length; @path.length; end
|
133
54
|
alias size length
|
134
55
|
|
56
|
+
def any?; @path.any?; end
|
57
|
+
def empty?; @path.empty?; end
|
58
|
+
|
59
|
+
def first; @path.first; end
|
60
|
+
def last; @path.last; end
|
61
|
+
|
62
|
+
def pop; @path.pop; end
|
63
|
+
def shift; @path.shift; end
|
64
|
+
|
135
65
|
def to_s
|
136
66
|
|
137
67
|
o = StringIO.new
|
@@ -146,20 +76,6 @@ class Dense::Path
|
|
146
76
|
s[0, 2] == '..' ? s[1..-1] : s
|
147
77
|
end
|
148
78
|
|
149
|
-
def walk(data, default=nil, &block)
|
150
|
-
|
151
|
-
_walk(data, @path)
|
152
|
-
|
153
|
-
rescue IndexError => ie
|
154
|
-
|
155
|
-
return yield(@original, self) if block
|
156
|
-
return default if default != nil && default != IndexError
|
157
|
-
|
158
|
-
fail ie.expand(self) if ie.respond_to?(:expand)
|
159
|
-
|
160
|
-
raise
|
161
|
-
end
|
162
|
-
|
163
79
|
def [](offset, count=nil)
|
164
80
|
|
165
81
|
if count == nil && offset.is_a?(Integer)
|
@@ -177,70 +93,150 @@ class Dense::Path
|
|
177
93
|
other.to_a == @path
|
178
94
|
end
|
179
95
|
|
180
|
-
def
|
96
|
+
def -(path)
|
181
97
|
|
182
|
-
@path.
|
98
|
+
self.class.make(subtract(@path.dup, path.to_a.dup))
|
183
99
|
end
|
184
100
|
|
185
|
-
def
|
101
|
+
def narrow(outcome)
|
186
102
|
|
187
|
-
|
103
|
+
single? ? outcome.first : outcome
|
188
104
|
end
|
189
105
|
|
190
|
-
def
|
106
|
+
def gather(data)
|
191
107
|
|
192
|
-
|
108
|
+
_gather(0, [], nil, data, @path, [])
|
109
|
+
.inject({}) { |h, hit| h[(hit[1] + [ hit[3] ]).inspect] ||= hit; h }
|
110
|
+
.values
|
193
111
|
end
|
194
112
|
|
195
113
|
protected
|
196
114
|
|
197
|
-
|
115
|
+
def _keys(o)
|
198
116
|
|
199
|
-
|
117
|
+
return (0..o.length - 1).to_a if o.is_a?(Array)
|
118
|
+
return o.keys if o.is_a?(Hash)
|
119
|
+
nil
|
120
|
+
end
|
200
121
|
|
201
|
-
|
122
|
+
def _resolve_hash_key(o, k)
|
202
123
|
|
203
|
-
|
124
|
+
return [ nil ] unless o.is_a?(Array)
|
204
125
|
|
205
|
-
|
206
|
-
|
126
|
+
be = k[:start] || 0
|
127
|
+
en = k[:end] || o.length - 1
|
128
|
+
st = k[:step] || 1
|
207
129
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
super(
|
213
|
-
"Found nothing at #{fail_path.to_s.inspect} " +
|
214
|
-
"(#{@remaining_path.original.inspect} remains)")
|
215
|
-
else
|
216
|
-
super(
|
217
|
-
"Cannot index instance of #{container_class} " +
|
218
|
-
"with #{@remaining_path.original.inspect}")
|
219
|
-
end
|
220
|
-
end
|
130
|
+
Range.new(be, en).step(st).to_a
|
131
|
+
end
|
132
|
+
|
133
|
+
def _resolve_key(o, k)
|
221
134
|
|
222
|
-
|
135
|
+
return _resolve_hash_key(o, k) if k.is_a?(Hash)
|
223
136
|
|
224
|
-
|
225
|
-
err.set_backtrace(self.backtrace)
|
137
|
+
return [ k.to_s ] if o.is_a?(Hash)
|
226
138
|
|
227
|
-
|
139
|
+
case k
|
140
|
+
when /\Afirst\z/i then [ 0 ]
|
141
|
+
when /\Alast\z/i then [ -1 ]
|
142
|
+
else [ k ]
|
228
143
|
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def _resolve_keys(o, k)
|
229
147
|
|
230
|
-
|
148
|
+
ks = k.is_a?(Hash) ? [ k ] : Array(k)
|
149
|
+
ks = ks.inject([]) { |a, kk| a.concat(_resolve_key(o, kk)) }
|
150
|
+
end
|
231
151
|
|
232
|
-
|
233
|
-
err.set_backtrace(self.backtrace)
|
152
|
+
def _stars(data0, data, key, path=[], acc=[])
|
234
153
|
|
235
|
-
|
154
|
+
#p [ :_stars, key, path, data0, data ]
|
155
|
+
acc.push([ path, data0, data ]) if path.any?
|
156
|
+
|
157
|
+
return acc unless data.is_a?(Hash) || data.is_a?(Array)
|
158
|
+
|
159
|
+
return acc unless key
|
160
|
+
key = key == :dotstar ? key : nil
|
161
|
+
|
162
|
+
if data.is_a?(Array)
|
163
|
+
data.each_with_index { |e, i| _stars(data, e, key, path + [ i ], acc) }
|
164
|
+
else
|
165
|
+
data.each { |k, v| _stars(data, v, key, path + [ k ], acc) }
|
236
166
|
end
|
237
167
|
|
238
|
-
|
168
|
+
acc
|
169
|
+
end
|
170
|
+
|
171
|
+
def _dot_gather(depth, path0, data0, data, path, acc)
|
239
172
|
|
240
|
-
|
173
|
+
#ind = ' ' * depth
|
174
|
+
#puts ind + "+--- _dot_gather()"
|
175
|
+
#puts ind + "| path0: #{path0.inspect}"
|
176
|
+
#puts ind + "| data: #{data.inspect}"
|
177
|
+
#puts ind + "| depth: #{depth} / path: #{path.inspect}"
|
178
|
+
|
179
|
+
a = _gather(depth, path0, data0, data, path, []).select { |r| r.first }
|
180
|
+
return acc.concat(a) if a.any?
|
181
|
+
|
182
|
+
keys = _keys(data)
|
183
|
+
|
184
|
+
return acc unless keys
|
185
|
+
|
186
|
+
keys.each { |k|
|
187
|
+
_dot_gather(depth + 1, path0 + [ k ], data, data[k], path, acc) }
|
188
|
+
|
189
|
+
acc
|
190
|
+
end
|
191
|
+
|
192
|
+
def _index(o, k)
|
193
|
+
|
194
|
+
case o
|
195
|
+
when Array then k.is_a?(Integer) ? o[k] : nil
|
196
|
+
when Hash then o[k]
|
197
|
+
else nil
|
241
198
|
end
|
242
199
|
end
|
243
200
|
|
201
|
+
def _gather(depth, path0, data0, data, path, acc)
|
202
|
+
|
203
|
+
k = path.first
|
204
|
+
#ind = ' ' * depth
|
205
|
+
#print [ LG, DG, LB ][depth % 3]
|
206
|
+
#puts ind + "+--- _gather()"
|
207
|
+
#puts ind + "| path0: #{path0.inspect}"
|
208
|
+
#puts ind + "| data: #{data.inspect}"
|
209
|
+
#puts ind + "| depth: #{depth} / path: #{path.inspect}"
|
210
|
+
#puts ind + "| k: " + k.inspect
|
211
|
+
|
212
|
+
#puts RD + ind + "| -> " + [ false, path0[0..-2], data0, path0.last, path ].inspect if k.nil? && data.nil?
|
213
|
+
return acc.push([ false, path0[0..-2], data0, path0.last, path ]) \
|
214
|
+
if data.nil?
|
215
|
+
|
216
|
+
#puts GN + ind + "| -> " + [ true, path0[0..-2], data0, path0.last ].inspect if k.nil? && data.nil?
|
217
|
+
return acc.push([ true, path0[0..-2], data0, path0.last ]) \
|
218
|
+
if k.nil?
|
219
|
+
|
220
|
+
#puts RD + ind + "| -> " + [ false, path0[0..-2], data0, path0.last, path ].inspect unless data.is_a?(Array) || data.is_a?(Hash)
|
221
|
+
return acc.push([ false, path0[0..-2], data0, path0.last, path ]) \
|
222
|
+
unless data.is_a?(Array) || data.is_a?(Hash)
|
223
|
+
|
224
|
+
return _dot_gather(depth, path0, data0, data, path[1..-1], acc) \
|
225
|
+
if k == :dot
|
226
|
+
|
227
|
+
#puts ind + "| stars:\n" + _stars(data0, data, k).collect(&:first).to_pp if k == :star || k == :dotstar
|
228
|
+
return _stars(data0, data, k).inject(acc) { |a, (pa, da0, da)|
|
229
|
+
_gather(depth + 1, path0 + pa, da0, da, path[1..-1], a)
|
230
|
+
} if k == :star || k == :dotstar
|
231
|
+
|
232
|
+
keys = _resolve_keys(data, k)
|
233
|
+
#puts ind + "| keys: " + keys.inspect
|
234
|
+
|
235
|
+
keys.inject(acc) { |a, kk|
|
236
|
+
_gather(
|
237
|
+
depth + 1, path0 + [ kk ], data, _index(data, kk), path[1..-1], a) }
|
238
|
+
end
|
239
|
+
|
244
240
|
def subtract(apath0, apath1)
|
245
241
|
|
246
242
|
while apath0.any? && apath1.any? && apath0.last == apath1.last
|
@@ -260,13 +256,13 @@ class Dense::Path
|
|
260
256
|
when Array
|
261
257
|
"[#{elt.map { |e| _to_s(e, true) }.join(',')}#{elt.size < 2 ? ',' : ''}]"
|
262
258
|
when String
|
263
|
-
#in_array ? elt.inspect : elt.to_s
|
264
|
-
#in_array ? _quote_s(elt) : _maybe_quote_s(elt)
|
265
259
|
_str_to_s(elt, in_array)
|
266
260
|
when :star
|
267
261
|
'*'
|
268
262
|
when :dot
|
269
263
|
'.'
|
264
|
+
when :dotstar
|
265
|
+
'..*'
|
270
266
|
else
|
271
267
|
elt.to_s
|
272
268
|
end
|
@@ -282,103 +278,5 @@ class Dense::Path
|
|
282
278
|
return "[#{elt.inspect}]" if s =~ /["']/
|
283
279
|
s
|
284
280
|
end
|
285
|
-
|
286
|
-
def _walk(data, path)
|
287
|
-
|
288
|
-
return data if path.empty?
|
289
|
-
|
290
|
-
case pa = path.first
|
291
|
-
when :dot then _walk_dot(data, pa, path)
|
292
|
-
when :star then _walk_star(data, pa, path)
|
293
|
-
when Hash then _walk_start_end_step(data, pa, path)
|
294
|
-
when Integer then _walk_int(data, pa, path)
|
295
|
-
when String then _walk(_sindex(data, pa), path[1..-1])
|
296
|
-
else fail IndexError.new("Unwalkable index in path: #{pa.inspect}")
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
def _walk_star(data, pa, path)
|
301
|
-
|
302
|
-
case data
|
303
|
-
when Array then data.collect { |d| _walk(d, path[1..-1]) }
|
304
|
-
when Hash then data.values.collect { |d| _walk(d, path[1..-1]) }
|
305
|
-
else data
|
306
|
-
end
|
307
|
-
end
|
308
|
-
|
309
|
-
def _walk_dot(data, pa, path)
|
310
|
-
|
311
|
-
_run(data, path[1])
|
312
|
-
.inject([]) { |a, d|
|
313
|
-
a.concat(
|
314
|
-
begin
|
315
|
-
[ _walk(d, path[2..-1]) ]
|
316
|
-
rescue NotIndexableError
|
317
|
-
[]
|
318
|
-
end) }
|
319
|
-
end
|
320
|
-
|
321
|
-
def _walk_start_end_step(data, pa, path)
|
322
|
-
|
323
|
-
be = pa[:start] || 0
|
324
|
-
en = pa[:end] || data.length - 1
|
325
|
-
st = pa[:step] || 1
|
326
|
-
Range.new(be, en).step(st).collect { |i| _walk(data[i], path[1..-1]) }
|
327
|
-
end
|
328
|
-
|
329
|
-
def _walk_int(data, pa, path)
|
330
|
-
|
331
|
-
if data.is_a?(Array)
|
332
|
-
return _walk(data[pa], path[1..-1])
|
333
|
-
end
|
334
|
-
|
335
|
-
if data.is_a?(Hash)
|
336
|
-
return _walk(data[pa], path[1..-1]) if data.has_key?(pa)
|
337
|
-
pa = pa.to_s
|
338
|
-
return _walk(data[pa], path[1..-1]) if data.has_key?(pa)
|
339
|
-
end
|
340
|
-
|
341
|
-
fail NotIndexableError.new(data, nil, path)
|
342
|
-
end
|
343
|
-
|
344
|
-
def _sindex(data, key)
|
345
|
-
|
346
|
-
case data
|
347
|
-
when Hash
|
348
|
-
data[key]
|
349
|
-
when Array
|
350
|
-
case key
|
351
|
-
when /\Afirst\z/i then data[0]
|
352
|
-
when /\Alast\z/i then data[-1]
|
353
|
-
else fail IndexError.new("Cannot index array with #{key.inspect}")
|
354
|
-
end
|
355
|
-
else
|
356
|
-
fail IndexError.new("Cannot index #{data.class} with #{key.inspect}")
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
|
-
def _run(d, key)
|
361
|
-
|
362
|
-
case d
|
363
|
-
when Hash then _run_hash(d, key)
|
364
|
-
when Array then _run_array(d, key)
|
365
|
-
else key == :star ? [ d ] : []
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
|
-
def _run_hash(d, key)
|
370
|
-
|
371
|
-
if key == :star
|
372
|
-
[ d ] + d.values.inject([]) { |a, v| a.concat(_run(v, key)) }
|
373
|
-
else
|
374
|
-
d.inject([]) { |a, (k, v)| a.concat(k == key ? [ v ] : _run(v, key)) }
|
375
|
-
end
|
376
|
-
end
|
377
|
-
|
378
|
-
def _run_array(d, key)
|
379
|
-
|
380
|
-
(key == :star ? [ d ] : []) +
|
381
|
-
d.inject([]) { |r, e| r.concat(_run(e, key)) }
|
382
|
-
end
|
383
|
-
end
|
281
|
+
end # Dense::Path
|
384
282
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dense
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Mettraux
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: raabro
|
@@ -52,6 +52,7 @@ files:
|
|
52
52
|
- dense.gemspec
|
53
53
|
- lib/dense.rb
|
54
54
|
- lib/dense/methods.rb
|
55
|
+
- lib/dense/parser.rb
|
55
56
|
- lib/dense/path.rb
|
56
57
|
homepage: http://github.com/floraison/dense
|
57
58
|
licenses:
|