feedx 0.7.1 → 0.7.2

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