dense 0.0.1 → 1.0.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: 3f1a1474f7754c17da61c379303adc4c454412cf
4
- data.tar.gz: 14721003a79f251faf0ae7fe3ab48a50cbf12133
3
+ metadata.gz: bebe1c11161f7007a745279fd573cb11591b9325
4
+ data.tar.gz: '082cb4ac9f1e6412ecf44445bd1e51206a094faf'
5
5
  SHA512:
6
- metadata.gz: b526940155800dca33200004b9e21b68a0c9edd4292507f3c9156f34326f8663fdb4edf6a0d46d3214c5caa2f42ec49850c4ca9949391933c44d7cd9f165a605
7
- data.tar.gz: 51f6622e8a5bb399946c78fe4028d2d045a9786f65192dddfd7da6d33844c805dc4b582fe7b4cabdb3b7e70d7cd4ffd0bae44004ed7b097123941723f40e7d64
6
+ metadata.gz: 2e5bbd4c1f7e915b66228cb2e494d46fd43338b9d0f822e42051c36864a45d08a18e2637d6b320cf238512fedad0e10d0f289b99727d7c850c2d18e204f7b63f
7
+ data.tar.gz: f7250e6416b6f781d00c7c1f094adfea7dc4983aad5fc37984f64614748a0c1e36a5b8623d52eed5056e37fa71464a4d4cf550ad16ec0e26451365e4c9cbf6a2
data/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+
2
+ # dense
3
+
4
+
5
+ ## dense 1.0.0 released 2017-09-29
6
+
7
+ * Accept `owner[age]` (unquoted key name in bracket index)
8
+ * Accept '=' and '?' in key names
9
+ * Introduce Dense::Path#last
10
+ * Introduce Dense::Path indexation and equality
11
+ * Introduce Dense::Path #length and #size
12
+ * Introduce Dense::Path::NotIndexableError#relabel
13
+ * Introduce Dense::Path::NotIndexableError
14
+ * Differentiate `Dense.get(col, path)` from `Dense.fetch(col, path[, default])`
15
+ * Provide Dense::Path.to_s
16
+ * Introduce Dense.has_key?(collection, path)
17
+ * Introduce Dense.insert(collection, path, value)
18
+ * Accept `.first` and `.last` when indexing arrays
19
+ * Introduce Dense.unset(collection, path)
20
+ * Introduce Dense.set(collection, path, value)
21
+ * Introduce Dense.get(collection, path)
22
+
23
+
24
+ ## dense 0.1.0 released 2017-08-06
25
+
26
+ * initial release
27
+
data/LICENSE.txt CHANGED
@@ -19,3 +19,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
19
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
20
  THE SOFTWARE.
21
21
 
22
+
23
+ Made in Japan
24
+
data/README.md CHANGED
@@ -1,7 +1,10 @@
1
1
 
2
2
  # dense
3
3
 
4
- Fetching deep in a dense structure. A kind of bastard of JSONPath.
4
+ [![Build Status](https://secure.travis-ci.org/floraison/dense.svg)](http://travis-ci.org/floraison/dense)
5
+ [![Gem Version](https://badge.fury.io/rb/dense.svg)](http://badge.fury.io/rb/dense)
6
+
7
+ Fetching deep in a dense structure. A kind of bastard of [JSONPath](http://goessner.net/articles/JsonPath/).
5
8
 
6
9
  ## LICENSE
7
10
 
data/dense.gemspec CHANGED
@@ -27,7 +27,7 @@ Fetching deep in a dense structure. A kind of bastard of JSONPath.
27
27
  "#{s.name}.gemspec",
28
28
  ]
29
29
 
30
- s.add_runtime_dependency 'raabro', '~> 1.1'
30
+ s.add_runtime_dependency 'raabro', '>= 1.1.5'
31
31
 
32
32
  s.add_development_dependency 'rspec', '~> 3.4'
33
33
 
data/lib/dense.rb CHANGED
@@ -1,6 +1,11 @@
1
1
 
2
2
  module Dense
3
3
 
4
- VERSION = '0.0.1'
4
+ VERSION = '1.0.0'
5
5
  end
6
6
 
7
+ require 'raabro'
8
+
9
+ require 'dense/path'
10
+ require 'dense/methods'
11
+
@@ -0,0 +1,143 @@
1
+
2
+ module Dense; class << self
3
+
4
+ def get(o, path)
5
+
6
+ Dense::Path.new(path).walk(o) { nil }
7
+ end
8
+
9
+ def fetch(o, path, default=IndexError, &block)
10
+
11
+ Dense::Path.new(path).walk(o, default, &block)
12
+ end
13
+
14
+ def set(o, path, value)
15
+
16
+ path = Dense::Path.new(path)
17
+ key = path.pop
18
+
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}")
23
+ end
24
+
25
+ value
26
+ end
27
+
28
+ def unset(o, path)
29
+
30
+ path = Dense::Path.new(path)
31
+ key = path.pop
32
+
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
38
+ end
39
+
40
+ def insert(o, path, value)
41
+
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
50
+
51
+ value
52
+ end
53
+
54
+ def has_key?(o, path)
55
+
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
64
+ end
65
+
66
+ protected
67
+
68
+ def array_i(k, may_fail=true)
69
+
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
79
+ end
80
+
81
+ def array_index(a, k)
82
+
83
+ i = array_i(k)
84
+ i = a.length + i if i < 0
85
+
86
+ fail IndexError.new(
87
+ "Array has length of #{a.length}, index is at #{k.inspect}"
88
+ ) if i < 0 || i >= a.length
89
+
90
+ i
91
+ end
92
+
93
+ def array_set(a, k, v)
94
+
95
+ i = array_index(a, k)
96
+
97
+ a[i] = v
98
+ end
99
+
100
+ def array_unset(a, k)
101
+
102
+ i = array_index(a, k)
103
+
104
+ a.delete_at(i)
105
+ end
106
+
107
+ def hash_unset(h, k)
108
+
109
+ fail KeyError.new("No key #{k.inspect} for hash") unless h.has_key?(k)
110
+
111
+ h.delete(k)
112
+ end
113
+
114
+ def array_insert(a, k, v)
115
+
116
+ i = array_i(k)
117
+
118
+ a.insert(i, v)
119
+ end
120
+
121
+ def array_has_key?(a, k)
122
+
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
133
+
134
+ i > -1 && i < a.length
135
+ end
136
+
137
+ def hash_has_key?(h, k)
138
+
139
+ return true if k.is_a?(Integer) && h.has_key?(k.to_s)
140
+ h.has_key?(k)
141
+ end
142
+ end; end
143
+
data/lib/dense/path.rb ADDED
@@ -0,0 +1,384 @@
1
+
2
+ class Dense::Path
3
+
4
+ attr_reader :original
5
+
6
+ def initialize(s)
7
+
8
+ @original = s
9
+
10
+ fail ArgumentError.new(
11
+ "Argument is a #{s.class}, not a String"
12
+ ) unless s.is_a?(String)
13
+
14
+ s = ".#{s}" unless s[0, 1] == '[' || s[0, 2] == '.['
15
+
16
+ #Raabro.pp(Parser.parse(s, debug: 3))
17
+ @path = Parser.parse(s)
18
+
19
+ #Raabro.pp(Parser.parse(s, debug: 3), colors: true) unless @path
20
+ fail ArgumentError.new(
21
+ "couldn't determine path from #{s.inspect}"
22
+ ) unless @path
23
+ end
24
+
25
+ def self.make(path_array)
26
+
27
+ return nil if path_array.nil?
28
+ return path_array if path_array.is_a?(Dense::Path)
29
+
30
+ path = Dense::Path.allocate
31
+ path.instance_eval { @path = path_array }
32
+ path.instance_eval { @original = path.to_s }
33
+
34
+ path
35
+ end
36
+
37
+ module Parser include Raabro
38
+
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
120
+
121
+ def rewrite_path(t)
122
+ t.subgather.collect { |tt| rewrite(tt) }
123
+ end
124
+
125
+ end
126
+
127
+ def to_a
128
+
129
+ @path
130
+ end
131
+
132
+ def length; @path.length; end
133
+ alias size length
134
+
135
+ def to_s
136
+
137
+ o = StringIO.new
138
+
139
+ @path.each { |e|
140
+ s = _to_s(e, false)
141
+ o << '.' unless o.size == 0 || '[.'.index(s[0, 1])
142
+ o << s }
143
+
144
+ s = o.string
145
+
146
+ s[0, 2] == '..' ? s[1..-1] : s
147
+ end
148
+
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
+ def [](offset, count=nil)
164
+
165
+ if count == nil && offset.is_a?(Integer)
166
+ @path[offset]
167
+ elsif count
168
+ self.class.make(@path[offset, count])
169
+ else
170
+ self.class.make(@path[offset])
171
+ end
172
+ end
173
+
174
+ def ==(other)
175
+
176
+ other.class == self.class &&
177
+ other.to_a == @path
178
+ end
179
+
180
+ def last
181
+
182
+ @path.last
183
+ end
184
+
185
+ def pop
186
+
187
+ @path.pop
188
+ end
189
+
190
+ def -(path)
191
+
192
+ self.class.make(subtract(@path.dup, path.to_a.dup))
193
+ end
194
+
195
+ protected
196
+
197
+ class NotIndexableError < ::IndexError
198
+
199
+ attr_reader :container_class, :root_path, :remaining_path
200
+
201
+ def initialize(container, root_path, remaining_path, message=nil)
202
+
203
+ @container_class = container.is_a?(Class) ? container : container.class
204
+
205
+ @root_path = Dense::Path.make(root_path)
206
+ @remaining_path = Dense::Path.make(remaining_path)
207
+
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
221
+
222
+ def expand(root_path)
223
+
224
+ err = self.class.new(container_class, root_path, remaining_path, nil)
225
+ err.set_backtrace(self.backtrace)
226
+
227
+ err
228
+ end
229
+
230
+ def relabel(message)
231
+
232
+ err = self.class.new(container_class, root_path, remaining_path, message)
233
+ err.set_backtrace(self.backtrace)
234
+
235
+ err
236
+ end
237
+
238
+ def fail_path
239
+
240
+ @fail_path ||= (@root_path ? @root_path - @remaining_path : nil)
241
+ end
242
+ end
243
+
244
+ def subtract(apath0, apath1)
245
+
246
+ while apath0.any? && apath1.any? && apath0.last == apath1.last
247
+ apath0.pop
248
+ apath1.pop
249
+ end
250
+
251
+ apath0
252
+ end
253
+
254
+ def _to_s(elt, in_array)
255
+
256
+ case elt
257
+ when Hash
258
+ s = [ "#{elt[:start]}:#{elt[:end]}", elt[:step] ].compact.join(':')
259
+ in_array ? s : "[#{s}]"
260
+ when Array
261
+ "[#{elt.map { |e| _to_s(e, true) }.join(',')}#{elt.size < 2 ? ',' : ''}]"
262
+ when String
263
+ #in_array ? elt.inspect : elt.to_s
264
+ #in_array ? _quote_s(elt) : _maybe_quote_s(elt)
265
+ _str_to_s(elt, in_array)
266
+ when :star
267
+ '*'
268
+ when :dot
269
+ '.'
270
+ else
271
+ elt.to_s
272
+ end
273
+ end
274
+
275
+ def _str_to_s(elt, in_array)
276
+
277
+ return elt.inspect if in_array
278
+
279
+ s = elt.to_s
280
+
281
+ return "\\#{s}" if s == '.' || s == '*'
282
+ return "[#{elt.inspect}]" if s =~ /["']/
283
+ s
284
+ 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
384
+
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dense
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.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-08-05 00:00:00.000000000 Z
11
+ date: 2017-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: raabro
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.1'
19
+ version: 1.1.5
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.1'
26
+ version: 1.1.5
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -45,11 +45,14 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - CHANGELOG.md
48
49
  - LICENSE.txt
49
50
  - Makefile
50
51
  - README.md
51
52
  - dense.gemspec
52
53
  - lib/dense.rb
54
+ - lib/dense/methods.rb
55
+ - lib/dense/path.rb
53
56
  homepage: http://github.com/floraison/dense
54
57
  licenses:
55
58
  - MIT
@@ -70,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
73
  version: '0'
71
74
  requirements: []
72
75
  rubyforge_project:
73
- rubygems_version: 2.5.2
76
+ rubygems_version: 2.6.13
74
77
  signing_key:
75
78
  specification_version: 4
76
79
  summary: fetching deep in a dense structure