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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bebe1c11161f7007a745279fd573cb11591b9325
4
- data.tar.gz: '082cb4ac9f1e6412ecf44445bd1e51206a094faf'
3
+ metadata.gz: 314a76ad0969382d65813cfa4ab3eb62db8a1ecf
4
+ data.tar.gz: ff138616d42d086fd7abbd1beee76c9f81c55172
5
5
  SHA512:
6
- metadata.gz: 2e5bbd4c1f7e915b66228cb2e494d46fd43338b9d0f822e42051c36864a45d08a18e2637d6b320cf238512fedad0e10d0f289b99727d7c850c2d18e204f7b63f
7
- data.tar.gz: f7250e6416b6f781d00c7c1f094adfea7dc4983aad5fc37984f64614748a0c1e36a5b8623d52eed5056e37fa71464a4d4cf550ad16ec0e26451365e4c9cbf6a2
6
+ metadata.gz: bcc1ff383a330a6da5068fd16774663e65577290bc7737a02d5102188f5c79d0fb2cbd24a627d607c7366ec13262d738b12121d61ac62042cbdf37883d7d0cf7
7
+ data.tar.gz: '098e9342bec870d2f21339c89e70d8825aaa24381234276d1e22f20391fd9488009589dcc1e1d01a1a75c57a0048dd8cb62b591ba2db55c5989519695dc86dff'
@@ -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)
@@ -1,5 +1,5 @@
1
1
 
2
- Copyright (c) 2017-2017, John Mettraux, jmettraux+flor@gmail.com
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)
@@ -1,11 +1,12 @@
1
1
 
2
2
  module Dense
3
3
 
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
6
6
 
7
7
  require 'raabro'
8
8
 
9
9
  require 'dense/path'
10
+ require 'dense/parser'
10
11
  require 'dense/methods'
11
12
 
@@ -3,141 +3,172 @@ module Dense; class << self
3
3
 
4
4
  def get(o, path)
5
5
 
6
- Dense::Path.new(path).walk(o) { nil }
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=IndexError, &block)
12
+ def fetch(o, path, default=::KeyError, &block)
10
13
 
11
- Dense::Path.new(path).walk(o, default, &block)
12
- end
14
+ pa = Dense::Path.new(path)
15
+ r = pa.gather(o).partition(&:first)
13
16
 
14
- def set(o, path, value)
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
- path = Dense::Path.new(path)
17
- key = path.pop
23
+ return pa.narrow(
24
+ r[1].collect { |m| default }
25
+ ) if default != KeyError
18
26
 
19
- case c = path.walk(o)
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
- path = Dense::Path.new(path)
31
- key = path.pop
46
+ pa = Dense::Path.new(path)
47
+ hits = pa.gather(o)
32
48
 
33
- case c = path.walk(o)
34
- when Array then array_unset(c, key)
35
- when Hash then hash_unset(c, key.to_s)
36
- else fail KeyError.new("Found no collection at #{path.to_s.inspect}")
37
- end
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
- path = Dense::Path.new(path)
43
- key = path.pop
44
-
45
- case c = path.walk(o)
46
- when Array then array_insert(c, key, value)
47
- when Hash then c[key.to_s] = value
48
- else fail KeyError.new("Found no collection at #{path.to_s.inspect}")
49
- end
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
- path = Dense::Path.new(path)
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
- protected
67
-
68
- def array_i(k, may_fail=true)
83
+ def path(path)
69
84
 
70
- case k
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 array_index(a, k)
88
+ def gather(o, path)
82
89
 
83
- i = array_i(k)
84
- i = a.length + i if i < 0
90
+ Dense::Path.new(path).gather(o)
91
+ end
92
+
93
+ protected
85
94
 
86
- fail IndexError.new(
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
- i
91
- end
97
+ attr_accessor :full_path, :miss
92
98
 
93
- def array_set(a, k, v)
99
+ # Used by some "clients" (like flor) to relabel (change the error message)
100
+ # a reraise.
101
+ #
102
+ def relabel(message)
94
103
 
95
- i = array_index(a, k)
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
- a[i] = v
110
+ err
111
+ end
98
112
  end
99
113
 
100
- def array_unset(a, k)
114
+ def make_error(error_class, message, path, miss)
101
115
 
102
- i = array_index(a, k)
116
+ err = error_class.new(message)
117
+ class << err; include DenseError; end
118
+ err.full_path = path
119
+ err.miss = miss
103
120
 
104
- a.delete_at(i)
121
+ err
105
122
  end
106
123
 
107
- def hash_unset(h, k)
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
- fail KeyError.new("No key #{k.inspect} for hash") unless h.has_key?(k)
129
+ msg = "Found nothing at #{path1}"
130
+ msg = "#{msg} (#{path2} remains)" if path2 != '""'
110
131
 
111
- h.delete(k)
132
+ make_error(KeyError, msg, path, miss)
112
133
  end
113
134
 
114
- def array_insert(a, k, v)
135
+ def type_error(path, miss)
115
136
 
116
- i = array_i(k)
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
- a.insert(i, v)
141
+ make_error(TypeError, "No key #{key} for #{cla} at #{pat}", path, miss)
119
142
  end
120
143
 
121
- def array_has_key?(a, k)
144
+ def miss_error(path, miss)
122
145
 
123
- i =
124
- array_i(k, false)
125
- i =
126
- if i.nil?
127
- -1
128
- elsif i < 0
129
- a.length + i
130
- else
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
- i > -1 && i < a.length
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 hash_has_key?(h, k)
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
- return true if k.is_a?(Integer) && h.has_key?(k.to_s)
140
- h.has_key?(k)
171
+ block.call(*args)
141
172
  end
142
- end; end
173
+ end; end # Dense
143
174
 
@@ -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
+
@@ -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}" unless s[0, 1] == '[' || s[0, 2] == '.['
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
- module Parser include Raabro
38
+ def single?
38
39
 
39
- # piece parsers bottom to top
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
- def rewrite_path(t)
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 last
96
+ def -(path)
181
97
 
182
- @path.last
98
+ self.class.make(subtract(@path.dup, path.to_a.dup))
183
99
  end
184
100
 
185
- def pop
101
+ def narrow(outcome)
186
102
 
187
- @path.pop
103
+ single? ? outcome.first : outcome
188
104
  end
189
105
 
190
- def -(path)
106
+ def gather(data)
191
107
 
192
- self.class.make(subtract(@path.dup, path.to_a.dup))
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
- class NotIndexableError < ::IndexError
115
+ def _keys(o)
198
116
 
199
- attr_reader :container_class, :root_path, :remaining_path
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
- def initialize(container, root_path, remaining_path, message=nil)
122
+ def _resolve_hash_key(o, k)
202
123
 
203
- @container_class = container.is_a?(Class) ? container : container.class
124
+ return [ nil ] unless o.is_a?(Array)
204
125
 
205
- @root_path = Dense::Path.make(root_path)
206
- @remaining_path = Dense::Path.make(remaining_path)
126
+ be = k[:start] || 0
127
+ en = k[:end] || o.length - 1
128
+ st = k[:step] || 1
207
129
 
208
- if message
209
- super(
210
- message)
211
- elsif @root_path
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
- def expand(root_path)
135
+ return _resolve_hash_key(o, k) if k.is_a?(Hash)
223
136
 
224
- err = self.class.new(container_class, root_path, remaining_path, nil)
225
- err.set_backtrace(self.backtrace)
137
+ return [ k.to_s ] if o.is_a?(Hash)
226
138
 
227
- err
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
- def relabel(message)
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
- err = self.class.new(container_class, root_path, remaining_path, message)
233
- err.set_backtrace(self.backtrace)
152
+ def _stars(data0, data, key, path=[], acc=[])
234
153
 
235
- err
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
- def fail_path
168
+ acc
169
+ end
170
+
171
+ def _dot_gather(depth, path0, data0, data, path, acc)
239
172
 
240
- @fail_path ||= (@root_path ? @root_path - @remaining_path : nil)
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.0.0
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: 2017-09-29 00:00:00.000000000 Z
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: