dense 1.0.0 → 1.1.0
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 +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:
|