feedx 0.7.1 → 0.7.2

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
  SHA256:
3
- metadata.gz: 2769dc5b097e9d5c17d467070cd3fa638ab7573f7e3cd310843c5c023a84aa5c
4
- data.tar.gz: 6edf76ff62393e0ea29aa7a3b8c74fb66d2a0328ddcf4a256f6fda297806f6f3
3
+ metadata.gz: c294d0811dec65d1fce8a44fe2e18dbe08ac0bffee8d56c6b1d96bdac63e9366
4
+ data.tar.gz: e6172cbf1f728569624a928dcf2a22c3a9a87824ed6cfd8fddaf834d1e526d8e
5
5
  SHA512:
6
- metadata.gz: 27c60344dfd2fab31e8c38f0a2368440e43ed5ab698b3df16dfb7820625f6d6646bdfc81211ce3c9c15ec9ffd44a12cea8b33fbaa357bc5cc664d1bf7d0599ad
7
- data.tar.gz: db9073b92564898bb535e725f09c32e76c07f32a050da2ad7bf7d513c4fdf8e6402c8733dc1de53238e528bc2986c18e2635ae0e16e006052b6827de350fcd13
6
+ metadata.gz: 64f69d35aa2cdca873e223b0f959a404dacebe03b07a8ca77796a10d5d6dfc06b4f7b9e7ee3bbfcfa03c23656399038324088a19d8e643636d0d3f3408af7b4b
7
+ data.tar.gz: fb2e106e250693c5756edecf6d98df7115f8e8b33f4fca01ade0c3c568eb756215e6efa967d36099fdb055e4f77a26f68aebc207d9f62bb82c4748fb193d8ec7
@@ -1,3 +1,6 @@
1
1
  require: rubocop-performance
2
2
  inherit_from:
3
3
  - https://gitlab.com/bsm/misc/raw/master/rubocop/default.yml
4
+
5
+ AllCops:
6
+ TargetRubyVersion: "2.4"
@@ -15,6 +15,11 @@ matrix:
15
15
  - 2.4
16
16
  before_install:
17
17
  - gem install bundler
18
+ - language: go
19
+ go:
20
+ - 1.12.x
21
+ env:
22
+ - GO111MODULE=on
18
23
  - language: go
19
24
  go:
20
25
  - 1.11.x
@@ -1,17 +1,17 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- feedx (0.7.1)
5
- bfs (>= 0.3.4)
4
+ feedx (0.7.2)
5
+ bfs (>= 0.4.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  ast (2.4.0)
11
- bfs (0.3.7)
11
+ bfs (0.4.0)
12
12
  diff-lcs (1.3)
13
13
  google-protobuf (3.8.0)
14
- jaro_winkler (1.5.2)
14
+ jaro_winkler (1.5.3)
15
15
  parallel (1.17.0)
16
16
  parser (2.6.3.0)
17
17
  ast (~> 2.4.0)
@@ -23,12 +23,12 @@ GEM
23
23
  rspec-core (~> 3.8.0)
24
24
  rspec-expectations (~> 3.8.0)
25
25
  rspec-mocks (~> 3.8.0)
26
- rspec-core (3.8.0)
26
+ rspec-core (3.8.1)
27
27
  rspec-support (~> 3.8.0)
28
28
  rspec-expectations (3.8.4)
29
29
  diff-lcs (>= 1.2.0, < 2.0)
30
30
  rspec-support (~> 3.8.0)
31
- rspec-mocks (3.8.0)
31
+ rspec-mocks (3.8.1)
32
32
  diff-lcs (>= 1.2.0, < 2.0)
33
33
  rspec-support (~> 3.8.0)
34
34
  rspec-support (3.8.2)
@@ -39,8 +39,8 @@ GEM
39
39
  rainbow (>= 2.2.2, < 4.0)
40
40
  ruby-progressbar (~> 1.7)
41
41
  unicode-display_width (>= 1.4.0, < 1.7)
42
- rubocop-performance (1.3.0)
43
- rubocop (>= 0.68.0)
42
+ rubocop-performance (1.4.0)
43
+ rubocop (>= 0.71.0)
44
44
  ruby-progressbar (1.10.1)
45
45
  unicode-display_width (1.6.0)
46
46
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'feedx'
3
- s.version = '0.7.1'
3
+ s.version = '0.7.2'
4
4
  s.authors = ['Black Square Media Ltd']
5
5
  s.email = ['info@blacksquaremedia.com']
6
6
  s.summary = %(Exchange data between components via feeds)
@@ -11,9 +11,9 @@ Gem::Specification.new do |s|
11
11
  s.files = `git ls-files -z`.split("\x0").reject {|f| f.match(%r{^spec/}) }
12
12
  s.test_files = `git ls-files -z -- spec/*`.split("\x0")
13
13
  s.require_paths = ['lib']
14
- s.required_ruby_version = '>= 2.3'
14
+ s.required_ruby_version = '>= 2.4'
15
15
 
16
- s.add_dependency 'bfs', '>= 0.3.4'
16
+ s.add_dependency 'bfs', '>= 0.4.0'
17
17
 
18
18
  s.add_development_dependency 'bundler'
19
19
  s.add_development_dependency 'pbio'
@@ -1,7 +1,9 @@
1
1
  module Feedx
2
2
  META_LAST_MODIFIED = 'x-feedx-last-modified'.freeze
3
3
 
4
+ autoload :Cache, 'feedx/cache'
4
5
  autoload :Compression, 'feedx/compression'
6
+ autoload :Consumer, 'feedx/consumer'
5
7
  autoload :Format, 'feedx/format'
6
8
  autoload :Stream, 'feedx/stream'
7
9
  autoload :Producer, 'feedx/producer'
@@ -0,0 +1,7 @@
1
+ module Feedx
2
+ module Cache
3
+ autoload :Abstract, 'feedx/cache/abstract'
4
+ autoload :Memory, 'feedx/cache/memory'
5
+ autoload :Value, 'feedx/cache/value'
6
+ end
7
+ end
@@ -0,0 +1,35 @@
1
+ class Feedx::Cache::Abstract
2
+ # Clears cache.
3
+ def clear
4
+ raise 'Not implemented'
5
+ end
6
+
7
+ # Read reads a key.
8
+ def read(_key, **_opts)
9
+ raise 'Not implemented'
10
+ end
11
+
12
+ # Write writes a key/value pair.
13
+ def write(_key, _value, **_opts)
14
+ raise 'Not implemented'
15
+ end
16
+
17
+ # Fetches data from the cache, using the given key.
18
+ # The optional block will be evaluated and the result stored in the cache
19
+ # in the event of a cache miss.
20
+ def fetch(key, **opts)
21
+ value = read(key, **opts)
22
+
23
+ if block_given?
24
+ value ||= yield
25
+ write(key, value, **opts) if value
26
+ end
27
+
28
+ value
29
+ end
30
+
31
+ # @return [Feedx::Abstract::Value] returns a wrapper around a single value.
32
+ def value(key)
33
+ Feedx::Cache::Value.new(self, key)
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ require 'monitor'
2
+
3
+ # Thread-safe in-memory cache. Use for testing only.
4
+ class Feedx::Cache::Memory < Feedx::Cache::Abstract
5
+ def initialize
6
+ @monitor = Monitor.new
7
+ @entries = {}
8
+ end
9
+
10
+ # Clear empties cache.
11
+ def clear
12
+ @monitor.synchronize do
13
+ @entries.clear
14
+ end
15
+ end
16
+
17
+ # Read reads a key.
18
+ def read(key, **)
19
+ @monitor.synchronize do
20
+ @entries[key]
21
+ end
22
+ end
23
+
24
+ # Write writes a key.
25
+ def write(key, value, **)
26
+ @monitor.synchronize do
27
+ @entries[key] = value.to_s
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ # A single value inside a cache.
2
+ class Feedx::Cache::Value
3
+ attr_reader :key
4
+
5
+ def initialize(cache, key)
6
+ @cache = cache
7
+ @key = key
8
+ end
9
+
10
+ # Read the key.
11
+ def read(**opts)
12
+ @cache.read(@key, **opts)
13
+ end
14
+
15
+ # Write a value.
16
+ def write(value, **opts)
17
+ @cache.write(@key, value, **opts)
18
+ end
19
+
20
+ # Fetches data. The optional block will be evaluated and the
21
+ # result stored in the cache under the key in the event of a cache miss.
22
+ def fetch(**opts, &block)
23
+ @cache.fetch(@key, **opts, &block)
24
+ end
25
+ end
@@ -0,0 +1,47 @@
1
+ require 'uri'
2
+ require 'bfs'
3
+ require 'feedx'
4
+
5
+ module Feedx
6
+ # Consumes an enumerates over a feed.
7
+ class Consumer
8
+ include Enumerable
9
+
10
+ # See constructor.
11
+ def self.each(url, klass, opts={}, &block)
12
+ new(url, klass, opts).each(&block)
13
+ end
14
+
15
+ # @param [String] url the destination URL.
16
+ # @param [Class] klass the record class.
17
+ # @param [Hash] opts options
18
+ # @option opts [Symbol,Class<Feedx::Format::Abstract>] :format custom formatter. Default: from file extension.
19
+ # @option opts [Hash] :format_options format decode options. Default: {}.
20
+ # @option opts [Symbol,Class<Feedx::Compression::Abstract>] :compress enable compression. Default: from file extension.
21
+ # @option opts [Feedx::Cache::Value] :cache cache value to store remote last modified time and consume conditionally.
22
+ def initialize(url, klass, opts={})
23
+ @klass = klass
24
+ @stream = Feedx::Stream.new(url, opts)
25
+ @fmt_opts = opts[:format_options] || {}
26
+ @cache = opts[:cache]
27
+ end
28
+
29
+ # @return [Boolean] returns true if performed.
30
+ def each(&block)
31
+ remote_rev = nil
32
+
33
+ if @cache
34
+ local_rev = @cache.read.to_i
35
+ remote_rev = @stream.blob.info.metadata[META_LAST_MODIFIED].to_i
36
+ return false if remote_rev.positive? && remote_rev <= local_rev
37
+ end
38
+
39
+ @stream.open do |fmt|
40
+ fmt.decode_each(@klass, **@fmt_opts, &block)
41
+ end
42
+ @cache.write(remote_rev) if @cache && remote_rev
43
+
44
+ true
45
+ end
46
+ end
47
+ end
@@ -9,9 +9,9 @@ class Feedx::Format::Abstract
9
9
 
10
10
  def decode_each(klass, **opts)
11
11
  if block_given?
12
- yield decode(klass, opts) until eof?
12
+ yield decode(klass, **opts) until eof?
13
13
  else
14
- Enumerator.new {|y| y << decode(klass, opts) until eof? }
14
+ Enumerator.new {|y| y << decode(klass, **opts) until eof? }
15
15
  end
16
16
  end
17
17
 
@@ -10,7 +10,7 @@ class Feedx::Format::JSON < Feedx::Format::Abstract
10
10
  obj
11
11
  end
12
12
 
13
- def encode(msg, **)
14
- @io.write msg.to_json << "\n"
13
+ def encode(msg, **opts)
14
+ @io.write msg.to_json(**opts) << "\n"
15
15
  end
16
16
  end
@@ -3,7 +3,7 @@ require 'bfs'
3
3
  require 'feedx'
4
4
 
5
5
  module Feedx
6
- # Produces a relation as am encoded stream to a remote location.
6
+ # Produces a relation as an encoded feed to a remote location.
7
7
  class Producer
8
8
  # See constructor.
9
9
  def self.perform(url, opts={}, &block)
@@ -14,6 +14,7 @@ module Feedx
14
14
  # @param [Hash] opts options
15
15
  # @option opts [Enumerable,ActiveRecord::Relation] :enum relation or enumerator to stream.
16
16
  # @option opts [Symbol,Class<Feedx::Format::Abstract>] :format custom formatter. Default: from file extension.
17
+ # @option opts [Hash] :format_options format encode options. Default: {}.
17
18
  # @option opts [Symbol,Class<Feedx::Compression::Abstract>] :compress enable compression. Default: from file extension.
18
19
  # @option opts [Time,Proc] :last_modified the last modified time, used to determine if a push is necessary.
19
20
  # @yield A block factory to generate the relation or enumerator.
@@ -22,24 +23,25 @@ module Feedx
22
23
  @enum = opts[:enum] || block
23
24
  raise ArgumentError, "#{self.class.name}.new expects an :enum option or a block factory" unless @enum
24
25
 
25
- @stream = Feedx::Stream.new(url, opts)
26
+ @stream = Feedx::Stream.new(url, opts)
26
27
  @last_mod = opts[:last_modified]
28
+ @fmt_opts = opts[:format_options] || {}
27
29
  end
28
30
 
29
31
  def perform
30
- enum = @enum.is_a?(Proc) ? @enum.call : @enum
31
- last_mod = @last_mod.is_a?(Proc) ? @last_mod.call(enum) : @last_mod
32
- current = (last_mod.to_f * 1000).floor
32
+ enum = @enum.is_a?(Proc) ? @enum.call : @enum
33
+ last_mod = @last_mod.is_a?(Proc) ? @last_mod.call(enum) : @last_mod
34
+ local_rev = last_mod.is_a?(Integer) ? last_mod : (last_mod.to_f * 1000).floor
33
35
 
34
36
  begin
35
- previous = @stream.blob.info.metadata[META_LAST_MODIFIED].to_i
36
- return -1 unless current > previous
37
+ remote_rev = @stream.blob.info.metadata[META_LAST_MODIFIED].to_i
38
+ return -1 unless local_rev > remote_rev
37
39
  rescue BFS::FileNotFound # rubocop:disable Lint/HandleExceptions
38
- end if current.positive?
40
+ end if local_rev.positive?
39
41
 
40
- @stream.create metadata: { META_LAST_MODIFIED => current.to_s } do |fmt|
42
+ @stream.create metadata: { META_LAST_MODIFIED => local_rev.to_s } do |fmt|
41
43
  iter = enum.respond_to?(:find_each) ? :find_each : :each
42
- enum.send(iter) {|rec| fmt.encode(rec) }
44
+ enum.send(iter) {|rec| fmt.encode(rec, **@fmt_opts) }
43
45
  end
44
46
  @stream.blob.info.size
45
47
  end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Feedx::Cache::Memory do
4
+ it 'should read/write' do
5
+ expect(subject.fetch('key')).to be_nil
6
+ expect(subject.fetch('key') { 'value' }).to eq('value')
7
+ expect(subject.fetch('key')).to eq('value')
8
+ expect(subject.fetch('key') { 'other' }).to eq('value')
9
+ expect(subject.fetch('key')).to eq('value')
10
+
11
+ subject.write('key', 'new-value')
12
+ expect(subject.read('key')).to eq('new-value')
13
+ expect(subject.fetch('key')).to eq('new-value')
14
+
15
+ subject.clear
16
+ expect(subject.fetch('key')).to be_nil
17
+ end
18
+
19
+ it 'should write strings' do
20
+ subject.write('key', 5)
21
+ expect(subject.read('key')).to eq('5')
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Feedx::Cache::Value do
4
+ subject do
5
+ described_class.new(Feedx::Cache::Memory.new, 'key')
6
+ end
7
+
8
+ it 'should read/write' do
9
+ expect(subject.fetch).to be_nil
10
+ expect(subject.fetch { 'value' }).to eq('value')
11
+ expect(subject.fetch).to eq('value')
12
+ expect(subject.fetch { 'other' }).to eq('value')
13
+ expect(subject.fetch).to eq('value')
14
+
15
+ subject.write('new-value')
16
+ expect(subject.read).to eq('new-value')
17
+ expect(subject.fetch).to eq('new-value')
18
+ end
19
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Feedx::Consumer do
4
+ let(:bucket) { BFS::Bucket::InMem.new }
5
+ let(:klass) { Feedx::TestCase::Model }
6
+ let(:cache) { Feedx::Cache::Memory.new.value('my-consumer') }
7
+ before { allow(BFS).to receive(:resolve).and_return(bucket) }
8
+
9
+ it 'should reject invalid inputs' do
10
+ expect do
11
+ described_class.each('mock:///dir/file.txt', klass) {}
12
+ end.to raise_error(/unable to detect format/)
13
+ end
14
+
15
+ it 'should consume feeds' do
16
+ url = mock_produce!
17
+ csm = described_class.new(url, klass)
18
+ expect(csm).to be_a(Enumerable)
19
+
20
+ cnt = csm.count do |rec|
21
+ expect(rec).to be_instance_of(klass)
22
+ true
23
+ end
24
+ expect(cnt).to eq(300)
25
+ end
26
+
27
+ it 'should perform conditionally' do
28
+ url = mock_produce! last_modified: Time.at(1515151515)
29
+ expect(described_class.new(url, klass, cache: cache).count).to eq(300)
30
+ expect(described_class.new(url, klass, cache: cache).count).to eq(0)
31
+
32
+ url = mock_produce!
33
+ expect(described_class.new(url, klass, cache: cache).count).to eq(300)
34
+ expect(described_class.new(url, klass, cache: cache).count).to eq(300)
35
+ end
36
+
37
+ private
38
+
39
+ def mock_produce!(opts={})
40
+ url = 'mock:///dir/file.json'
41
+ opts[:enum] ||= %w[x y z].map {|t| Feedx::TestCase::Model.new(t) } * 100
42
+ Feedx::Producer.perform url, opts
43
+ url
44
+ end
45
+ end
@@ -19,7 +19,7 @@ module Feedx
19
19
  @title = title
20
20
  end
21
21
 
22
- def to_pb
22
+ def to_pb(*)
23
23
  Feedx::TestCase::Message.new title: @title
24
24
  end
25
25
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feedx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Black Square Media Ltd
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-12 00:00:00.000000000 Z
11
+ date: 2019-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bfs
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.3.4
19
+ version: 0.4.0
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: 0.3.4
26
+ version: 0.4.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -137,10 +137,15 @@ files:
137
137
  - go.mod
138
138
  - go.sum
139
139
  - lib/feedx.rb
140
+ - lib/feedx/cache.rb
141
+ - lib/feedx/cache/abstract.rb
142
+ - lib/feedx/cache/memory.rb
143
+ - lib/feedx/cache/value.rb
140
144
  - lib/feedx/compression.rb
141
145
  - lib/feedx/compression/abstract.rb
142
146
  - lib/feedx/compression/gzip.rb
143
147
  - lib/feedx/compression/none.rb
148
+ - lib/feedx/consumer.rb
144
149
  - lib/feedx/format.rb
145
150
  - lib/feedx/format/abstract.rb
146
151
  - lib/feedx/format/json.rb
@@ -152,9 +157,12 @@ files:
152
157
  - producer_test.go
153
158
  - reader.go
154
159
  - reader_test.go
160
+ - spec/feedx/cache/memory_spec.rb
161
+ - spec/feedx/cache/value_spec.rb
155
162
  - spec/feedx/compression/gzip_spec.rb
156
163
  - spec/feedx/compression/none_spec.rb
157
164
  - spec/feedx/compression_spec.rb
165
+ - spec/feedx/consumer_spec.rb
158
166
  - spec/feedx/format/abstract_spec.rb
159
167
  - spec/feedx/format/json_spec.rb
160
168
  - spec/feedx/format/protobuf_spec.rb
@@ -176,7 +184,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
176
184
  requirements:
177
185
  - - ">="
178
186
  - !ruby/object:Gem::Version
179
- version: '2.3'
187
+ version: '2.4'
180
188
  required_rubygems_version: !ruby/object:Gem::Requirement
181
189
  requirements:
182
190
  - - ">="
@@ -188,9 +196,12 @@ signing_key:
188
196
  specification_version: 4
189
197
  summary: Exchange data between components via feeds
190
198
  test_files:
199
+ - spec/feedx/cache/memory_spec.rb
200
+ - spec/feedx/cache/value_spec.rb
191
201
  - spec/feedx/compression/gzip_spec.rb
192
202
  - spec/feedx/compression/none_spec.rb
193
203
  - spec/feedx/compression_spec.rb
204
+ - spec/feedx/consumer_spec.rb
194
205
  - spec/feedx/format/abstract_spec.rb
195
206
  - spec/feedx/format/json_spec.rb
196
207
  - spec/feedx/format/protobuf_spec.rb