json-streamer 1.3.0 → 2.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: aac1b068e94999ad0ef0f317d819273b14069fdf
4
- data.tar.gz: e9245d59c50a842953d63bfedb3aed2859b15c75
3
+ metadata.gz: 16e5cef72d5fa26617ed43d67982c956bf00942f
4
+ data.tar.gz: 067e090f2a1ec4f2a259e51b5c5a078652fd08cf
5
5
  SHA512:
6
- metadata.gz: 93f9766a9086fb81f43e96f3d2c267b434148b9bcdb3bfdef2595a187507f056494b9a00955cc29a49cfc6f56883a7b04cb5d601f66661e9366203b576c71b9f
7
- data.tar.gz: b5c174919a39b4b8d03ee05a6888e0b6cf134335bf3bf2ccdeaebda88fa0a1eeb8eae99875ba8e72d7e68f7b80f2e8505030570a92aa668a1912c74ee9370210
6
+ metadata.gz: 15cb106e6a78fc4271e4ec250279f5b1c10534e89e24ef9e5517617e1356e1b8903ecff3047d2d93466e87a085d0392e15cb46101cee87b3e1e2b879d8f36517
7
+ data.tar.gz: 15d3415923f32b5d2cb7edcb093e9b6ec1a7bda307ea4ace1a4fadb9710a49b89a22e421b6c8b513134d4a8de36c528f648253ec91fbceb3232e9a964442fc60
data/Guardfile ADDED
@@ -0,0 +1,20 @@
1
+ guard 'bundler' do
2
+ watch('Gemfile')
3
+ end
4
+
5
+ guard 'rspec', cmd: "bundle exec spring rspec #{ENV['FOCUS']}", all_after_pass: ENV['FOCUS'].nil? do
6
+ watch(%r{^spec/.+_spec\.rb$})
7
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
8
+ watch('spec/spec_helper.rb') { 'spec' }
9
+
10
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12
+ watch(%r{^app/models/(.+)\.rb$}) { |m| "spec/builders/#{m[1]}_builder_spec.rb" }
13
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
14
+ watch(%r{^spec/support/(.+)\.rb$}) { 'spec' }
15
+ watch(%r{^spec/factories/(.+)\.rb$}) { 'spec' }
16
+ watch('config/routes.rb') { 'spec/routing' }
17
+ watch('app/controllers/application_controller.rb') { 'spec/controllers' }
18
+
19
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
20
+ end
data/README.md CHANGED
@@ -80,7 +80,7 @@ Input:
80
80
  ```
81
81
 
82
82
  Output:
83
- ```json
83
+ ```ruby
84
84
  "first_level_value"
85
85
  {}
86
86
  ```
@@ -109,11 +109,11 @@ Input:
109
109
  ```
110
110
 
111
111
  Output:
112
- ```json
112
+ ```ruby
113
113
  "value1"
114
114
  "value2"
115
115
  "value3"
116
- {"desired_key" : "value3"}
116
+ {"desired_key" => "value3"}
117
117
  ```
118
118
 
119
119
  #### Skip values
@@ -155,7 +155,7 @@ Input:
155
155
  ```
156
156
 
157
157
  Output:
158
- ```json
158
+ ```ruby
159
159
  {:obj1=>{:key=>"value"}}
160
160
  ```
161
161
 
@@ -179,6 +179,71 @@ def receive_data(data)
179
179
  end
180
180
  ```
181
181
 
182
+ #### Custom yield conditions
183
+
184
+ [v2.0.0](https://github.com/thisismydesign/json-streamer/releases/tag/v2.0.0) introduces custom conditions which provide ultimate control over what to yield.
185
+
186
+ The Conditions API exposes 3 callbacks:
187
+ - `yield_value`
188
+ - `yield_array`
189
+ - `yield_object`
190
+
191
+ Each of them may be redefined. They are called once the corresponding data (value, array or object) is available. They should return whether the data should be yielded for the outside. They receive the data and the `aggregator` as parameters.
192
+
193
+ The `aggregator` exposes data about the current state of the partly parsed JSON such as:
194
+ - `level` - current level
195
+ - `key` - current key
196
+ - `value` - current value
197
+ - `key_for_level(level)` - key for custom level
198
+ - `value_for_level(level)` - value for custom level
199
+ - `get` - the raw data (in a custom format)
200
+
201
+ Example usage (inspired by [this issue](https://github.com/thisismydesign/json-streamer/issues/7#issuecomment-330232484)):
202
+
203
+ ```ruby
204
+ conditions = Json::Streamer::Conditions.new
205
+ conditions.yield_value = ->(aggregator:, value:) { false }
206
+ conditions.yield_array = ->(aggregator:, array:) { false }
207
+ conditions.yield_object = lambda do |aggregator:, object:|
208
+ aggregator.level.eql?(2) && aggregator.key_for_level(1).eql?('items1')
209
+ end
210
+
211
+ streamer.get_with_conditions(conditions) do |object|
212
+ p object
213
+ end
214
+ ```
215
+
216
+ Input:
217
+
218
+ ```ruby
219
+ {
220
+ "other": "stuff",
221
+ "items1": [
222
+ {
223
+ "key1": "value"
224
+ },
225
+ {
226
+ "key2": "value"
227
+ }
228
+ ],
229
+ "items2": [
230
+ {
231
+ "key3": "value"
232
+ },
233
+ {
234
+ "key4": "value"
235
+ }
236
+ ]
237
+ }
238
+ ```
239
+
240
+ Output:
241
+
242
+ ```ruby
243
+ {"key1"=>"value"}
244
+ {"key2"=>"value"}
245
+ ```
246
+
182
247
  ### Legacy API (pre-v1.2)
183
248
 
184
249
  This functionality is deprecated but kept for compatibility reasons.
@@ -27,6 +27,9 @@ Gem::Specification.new do |spec|
27
27
  spec.add_development_dependency "coveralls"
28
28
  spec.add_development_dependency "ndhash"
29
29
  spec.add_development_dependency "get_process_mem"
30
+ spec.add_development_dependency "guard"
31
+ spec.add_development_dependency "guard-bundler"
32
+ spec.add_development_dependency "guard-rspec"
30
33
 
31
34
  spec.add_dependency "json-stream"
32
35
  end
@@ -0,0 +1,56 @@
1
+ require 'forwardable'
2
+
3
+ module Json
4
+ module Streamer
5
+ class Aggregator
6
+ extend Forwardable
7
+ def_delegators :@aggregator, :pop, :push, :empty?
8
+
9
+ def initialize
10
+ @aggregator = []
11
+ end
12
+
13
+ def get
14
+ @aggregator
15
+ end
16
+
17
+ def level
18
+ @aggregator.size
19
+ end
20
+
21
+ def key
22
+ @aggregator.last[:key] unless @aggregator.last.nil?
23
+ end
24
+
25
+ def key=(k)
26
+ @aggregator.last[:key] = k
27
+ end
28
+
29
+ def value
30
+ @aggregator.last[:value]
31
+ end
32
+
33
+ def value=(d)
34
+ if array_level?
35
+ value << d
36
+ else
37
+ value[key] = d
38
+ end
39
+ end
40
+
41
+ def key_for_level(level)
42
+ @aggregator[level - 1][:key] unless @aggregator[level - 1].nil?
43
+ end
44
+
45
+ def value_for_level(level)
46
+ @aggregator[level - 1][:key] unless @aggregator[level - 1].nil?
47
+ end
48
+
49
+ private
50
+
51
+ def array_level?
52
+ value.is_a?(Array)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,55 @@
1
+ module Json
2
+ module Streamer
3
+ class Callbacks
4
+ attr_reader :aggregator
5
+
6
+ def initialize(aggregator)
7
+ @aggregator = aggregator
8
+ end
9
+
10
+ def start_object
11
+ new_level(Hash.new)
12
+ end
13
+
14
+ def start_array
15
+ new_level(Array.new)
16
+ end
17
+
18
+ def key(k, symbolize_keys)
19
+ @aggregator.key = symbolize_keys ? k.to_sym : k
20
+ end
21
+
22
+ def value(value)
23
+ used = yield value
24
+ add_value(value) unless used
25
+ end
26
+
27
+ def end_object
28
+ end_level { |obj| yield obj }
29
+ end
30
+
31
+ def end_array
32
+ end_level { |obj| yield obj }
33
+ end
34
+
35
+ private
36
+
37
+ def end_level
38
+ data = @aggregator.value.clone
39
+
40
+ @aggregator.pop
41
+
42
+ used = yield data
43
+ add_value(data) unless used or @aggregator.empty?
44
+ end
45
+
46
+ def add_value(value)
47
+ @aggregator.value = value
48
+ end
49
+
50
+ def new_level(type)
51
+ @aggregator.push(value: type)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,22 @@
1
+ module Json
2
+ module Streamer
3
+ class Conditions
4
+ attr_accessor :yield_value, :yield_object, :yield_array
5
+
6
+ def initialize(yield_level: -1, yield_key: nil)
7
+ @yield_level = yield_level
8
+ @yield_key = yield_key
9
+
10
+ @yield_value = ->(aggregator:, value:nil) { yield?(aggregator) }
11
+ @yield_object = ->(aggregator:, object:nil) { yield?(aggregator) }
12
+ @yield_array = ->(aggregator:, array:nil) { yield?(aggregator) }
13
+ end
14
+
15
+ private
16
+
17
+ def yield?(aggregator)
18
+ aggregator.level.eql?(@yield_level) or (not @yield_key.nil? and @yield_key == aggregator.key)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,143 +1,57 @@
1
1
  require "json/stream"
2
2
 
3
+ require_relative 'conditions'
4
+ require_relative 'parser'
5
+
3
6
  module Json
4
7
  module Streamer
5
8
  class JsonStreamer
6
9
 
7
- attr_reader :aggregator
8
10
  attr_reader :parser
9
11
 
10
12
  def initialize(file_io = nil, chunk_size = 1000)
11
- @parser = JSON::Stream::Parser.new
13
+ @event_generator = JSON::Stream::Parser.new
12
14
 
13
15
  @file_io = file_io
14
16
  @chunk_size = chunk_size
15
-
16
- @current_level = -1
17
- @aggregator = {}
18
- @aggregator_keys = {}
19
-
20
- @parser.start_object {start_object}
21
- @parser.start_array {start_array}
22
- @parser.key {|k| key(k)}
23
17
  end
24
18
 
25
19
  def <<(data)
26
- @parser << data
20
+ parser << data
27
21
  end
28
22
 
29
- # Callbacks containing `yield` have to be defined in the method called via block otherwise yield won't work
30
23
  def get(nesting_level: -1, key: nil, yield_values: true, symbolize_keys: false)
31
- @yield_level = nesting_level
32
- @yield_key = key
33
- @yield_values = yield_values
34
- @symbolize_keys = symbolize_keys
35
-
36
- @parser.value do |v|
37
- value(v) { |desired_object| yield desired_object }
38
- end
39
-
40
- @parser.end_object do
41
- end_level(Hash.new) { |desired_object| yield desired_object }
42
- end
43
-
44
- @parser.end_array do
45
- end_level(Array.new) { |desired_object| yield desired_object }
46
- end
47
-
48
- @file_io.each(@chunk_size) { |chunk| @parser << chunk } if @file_io
49
- end
50
-
51
- def start_object
52
- new_level(Hash.new)
53
- end
54
-
55
- def start_array
56
- new_level(Array.new)
57
- end
58
-
59
- def key(k)
60
- @current_key = @symbolize_keys ? k.to_sym : k
61
- end
62
-
63
- def value(value)
64
- reset_current_key if array_level?(@current_level)
65
- yield value if yield_value?
66
- add_value(value)
67
- end
24
+ conditions = Conditions.new(yield_level: nesting_level, yield_key: key)
25
+ conditions.yield_value = ->(aggregator:, value:) { false } unless yield_values
68
26
 
69
- def add_value(value)
70
- if array_level?(@current_level)
71
- @aggregator[@current_level] << value
72
- else
73
- @aggregator[@current_level][@current_key] = value
74
- end
75
- end
27
+ # TODO: deprecate symbolize_keys and move to initialize
28
+ @parser = Parser.new(@event_generator, symbolize_keys: symbolize_keys)
76
29
 
77
- def end_level(type)
78
- if yield_object?
79
- yield @aggregator[@current_level].clone
80
- reset_current_level(type)
81
- else
82
- merge_up
30
+ parser.get(conditions) do |obj|
31
+ yield obj
83
32
  end
84
33
 
85
- @current_level -= 1
86
- end
87
-
88
- def yield_object?
89
- @current_level.eql?(@yield_level) or (not @yield_key.nil? and @yield_key == previous_key)
90
- end
91
-
92
- def yield_value?
93
- @yield_values and ((next_level).eql?(@yield_level) or (not @yield_key.nil? and @yield_key == @current_key))
94
- end
95
-
96
- def new_level(type)
97
- reset_current_key if array_level?(@current_level)
98
- set_aggregator_key
99
- @current_level += 1
100
- reset_current_level(type)
34
+ process_io
101
35
  end
102
36
 
103
- def reset_current_level(type)
104
- @aggregator[@current_level] = type
105
- end
106
-
107
- def set_aggregator_key
108
- @aggregator_keys[@current_level] = @current_key
109
- end
110
-
111
- def reset_current_key
112
- @current_key = nil
113
- end
37
+ def get_with_conditions(conditions, options = {})
38
+ @parser = Parser.new(@event_generator, symbolize_keys: options[:symbolize_keys])
114
39
 
115
- def array_level?(nesting_level)
116
- @aggregator[nesting_level].is_a?(Array)
117
- end
118
-
119
- def merge_up
120
- return if @current_level.zero?
121
-
122
- if array_level?(previous_level)
123
- @aggregator[previous_level] << @aggregator[@current_level]
124
- else
125
- @aggregator[previous_level][previous_key] = @aggregator[@current_level]
40
+ parser.get(conditions) do |obj|
41
+ yield obj
126
42
  end
127
43
 
128
- @aggregator.delete(@current_level)
44
+ process_io
129
45
  end
130
46
 
131
- def previous_level
132
- @current_level - 1
47
+ def aggregator
48
+ parser.aggregator
133
49
  end
134
50
 
135
- def next_level
136
- @current_level + 1
137
- end
51
+ private
138
52
 
139
- def previous_key
140
- @aggregator_keys[previous_level]
53
+ def process_io
54
+ @file_io.each(@chunk_size) { |chunk| parser << chunk } if @file_io
141
55
  end
142
56
  end
143
57
  end
@@ -0,0 +1,51 @@
1
+ require_relative 'aggregator'
2
+ require_relative 'callbacks'
3
+
4
+ module Json
5
+ module Streamer
6
+ class Parser
7
+ def initialize(event_generator, options = {})
8
+ @event_generator = event_generator
9
+ @symbolize_keys = options[:symbolize_keys]
10
+
11
+ @aggregator = Aggregator.new
12
+ @event_consumer = Callbacks.new(@aggregator)
13
+ end
14
+
15
+ def get(conditions)
16
+ @event_generator.start_object { @event_consumer.start_object }
17
+ @event_generator.start_array { @event_consumer.start_array }
18
+
19
+ @event_generator.key do |k|
20
+ @event_consumer.key(k, @symbolize_keys)
21
+ end
22
+
23
+ @event_generator.value do |v|
24
+ @event_consumer.value(v) do |value|
25
+ yield value if conditions.yield_value.call(aggregator: @aggregator, value: value)
26
+ end
27
+ end
28
+
29
+ @event_generator.end_object do
30
+ @event_consumer.end_object do |object|
31
+ yield object if conditions.yield_object.call(aggregator: @aggregator, object: object)
32
+ end
33
+ end
34
+
35
+ @event_generator.end_array do
36
+ @event_consumer.end_array do |array|
37
+ yield array if conditions.yield_array.call(aggregator: @aggregator, array: array)
38
+ end
39
+ end
40
+ end
41
+
42
+ def <<(data)
43
+ @event_generator << data
44
+ end
45
+
46
+ def aggregator
47
+ @aggregator.get
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,5 +1,5 @@
1
1
  module Json
2
2
  module Streamer
3
- VERSION = "1.3.0"
3
+ VERSION = "2.0.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json-streamer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thisismydesign
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-09-16 00:00:00.000000000 Z
11
+ date: 2017-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,48 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: guard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: guard-bundler
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: guard-rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
97
139
  - !ruby/object:Gem::Dependency
98
140
  name: json-stream
99
141
  requirement: !ruby/object:Gem::Requirement
@@ -121,6 +163,7 @@ files:
121
163
  - ".travis.yml"
122
164
  - CODE_OF_CONDUCT.md
123
165
  - Gemfile
166
+ - Guardfile
124
167
  - LICENSE.txt
125
168
  - README.md
126
169
  - Rakefile
@@ -128,7 +171,11 @@ files:
128
171
  - bin/setup
129
172
  - json-streamer.gemspec
130
173
  - lib/json/streamer.rb
174
+ - lib/json/streamer/aggregator.rb
175
+ - lib/json/streamer/callbacks.rb
176
+ - lib/json/streamer/conditions.rb
131
177
  - lib/json/streamer/json_streamer.rb
178
+ - lib/json/streamer/parser.rb
132
179
  - lib/json/streamer/version.rb
133
180
  homepage: https://github.com/thisismydesign/json-streamer
134
181
  licenses: