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