feedx 0.10.2 → 0.12.3
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 +4 -4
- data/.editorconfig +3 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +12 -2
- data/Gemfile +0 -2
- data/Gemfile.lock +50 -30
- data/Makefile +10 -5
- data/compression.go +18 -0
- data/compression_test.go +12 -0
- data/consumer_test.go +5 -4
- data/ext/parquet/decoder.go +170 -0
- data/ext/parquet/decoder_test.go +88 -0
- data/ext/parquet/go.mod +12 -0
- data/ext/parquet/go.sum +134 -0
- data/ext/parquet/parquet.go +78 -0
- data/ext/parquet/parquet_test.go +28 -0
- data/ext/parquet/reader.go +89 -0
- data/ext/parquet/testdata/alltypes_plain.parquet +0 -0
- data/ext/parquet/types.go +51 -0
- data/feedx.gemspec +3 -2
- data/feedx_test.go +8 -24
- data/format.go +50 -20
- data/format_test.go +8 -6
- data/go.mod +9 -11
- data/go.sum +76 -28
- data/internal/testdata/testdata.pb.go +223 -0
- data/internal/testdata/testdata.proto +15 -0
- data/lib/feedx/cache/abstract.rb +2 -2
- data/lib/feedx/cache/memory.rb +1 -0
- data/lib/feedx/compression.rb +11 -4
- data/lib/feedx/compression/abstract.rb +2 -2
- data/lib/feedx/compression/gzip.rb +14 -16
- data/lib/feedx/compression/none.rb +4 -4
- data/lib/feedx/consumer.rb +15 -9
- data/lib/feedx/format.rb +18 -9
- data/lib/feedx/format/abstract.rb +42 -13
- data/lib/feedx/format/json.rb +12 -8
- data/lib/feedx/format/parquet.rb +102 -0
- data/lib/feedx/format/protobuf.rb +16 -8
- data/lib/feedx/producer.rb +27 -22
- data/lib/feedx/stream.rb +36 -23
- data/producer_test.go +1 -2
- data/reader_test.go +6 -6
- data/spec/feedx/compression/gzip_spec.rb +2 -2
- data/spec/feedx/compression/none_spec.rb +2 -2
- data/spec/feedx/compression_spec.rb +9 -9
- data/spec/feedx/consumer_spec.rb +1 -1
- data/spec/feedx/format/abstract_spec.rb +11 -8
- data/spec/feedx/format/json_spec.rb +17 -16
- data/spec/feedx/format/parquet_spec.rb +30 -0
- data/spec/feedx/format/protobuf_spec.rb +12 -11
- data/spec/feedx/format_spec.rb +8 -8
- data/spec/feedx/producer_spec.rb +6 -0
- data/spec/feedx/stream_spec.rb +43 -6
- data/spec/spec_helper.rb +17 -1
- metadata +33 -5
data/lib/feedx/producer.rb
CHANGED
@@ -14,38 +14,43 @@ 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: {}.
|
18
17
|
# @option opts [Symbol,Class<Feedx::Compression::Abstract>] :compress enable compression. Default: from file extension.
|
19
18
|
# @option opts [Time,Proc] :last_modified the last modified time, used to determine if a push is necessary.
|
20
19
|
# @yield A block factory to generate the relation or enumerator.
|
21
20
|
# @yieldreturn [Enumerable,ActiveRecord::Relation] the relation or enumerator to stream.
|
22
|
-
def initialize(url, **opts, &block)
|
23
|
-
@enum =
|
21
|
+
def initialize(url, last_modified: nil, format_options: {}, enum: nil, **opts, &block)
|
22
|
+
@enum = enum || block
|
24
23
|
raise ArgumentError, "#{self.class.name}.new expects an :enum option or a block factory" unless @enum
|
25
24
|
|
26
|
-
@
|
27
|
-
@
|
28
|
-
@
|
25
|
+
@url = url
|
26
|
+
@opts = opts.merge(format_options)
|
27
|
+
@last_mod = last_modified
|
28
|
+
|
29
|
+
return if format_options.empty? || (defined?(Gem::Deprecate) && Gem::Deprecate.skip)
|
30
|
+
|
31
|
+
warn "WARNING: passing format_options is deprecated; pass the options inline instead (called from #{caller(2..2).first})."
|
29
32
|
end
|
30
33
|
|
31
34
|
def perform
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
35
|
+
Feedx::Stream.open(@url, **@opts) do |stream|
|
36
|
+
enum = @enum.is_a?(Proc) ? @enum.call : @enum
|
37
|
+
last_mod = @last_mod.is_a?(Proc) ? @last_mod.call(enum) : @last_mod
|
38
|
+
local_rev = last_mod.is_a?(Integer) ? last_mod : (last_mod.to_f * 1000).floor
|
39
|
+
|
40
|
+
begin
|
41
|
+
metadata = stream.blob.info.metadata
|
42
|
+
remote_rev = (metadata[META_LAST_MODIFIED] || metadata[META_LAST_MODIFIED_DC]).to_i
|
43
|
+
return -1 unless local_rev > remote_rev
|
44
|
+
rescue BFS::FileNotFound
|
45
|
+
nil
|
46
|
+
end if local_rev.positive?
|
47
|
+
|
48
|
+
stream.create metadata: { META_LAST_MODIFIED => local_rev.to_s } do |fmt|
|
49
|
+
iter = enum.respond_to?(:find_each) ? :find_each : :each
|
50
|
+
enum.send(iter) {|rec| fmt.encode(rec, **@opts) }
|
51
|
+
end
|
52
|
+
stream.blob.info.size
|
47
53
|
end
|
48
|
-
@stream.blob.info.size
|
49
54
|
end
|
50
55
|
end
|
51
56
|
end
|
data/lib/feedx/stream.rb
CHANGED
@@ -6,25 +6,40 @@ module Feedx
|
|
6
6
|
class Stream
|
7
7
|
attr_reader :blob
|
8
8
|
|
9
|
+
# Behaves like new, but accepts an optional block.
|
10
|
+
# If a block is given, streams are automatically closed after the block is yielded.
|
11
|
+
def self.open(url, **opts)
|
12
|
+
stream = new(url, **opts)
|
13
|
+
return stream unless block_given?
|
14
|
+
|
15
|
+
begin
|
16
|
+
yield stream
|
17
|
+
ensure
|
18
|
+
stream.close
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
9
22
|
# @param [String] url the blob URL.
|
10
23
|
# @param [Hash] opts options
|
11
24
|
# @option opts [Symbol,Class<Feedx::Format::Abstract>] :format custom formatter. Default: from file extension.
|
12
25
|
# @option opts [Symbol,Class<Feedx::Compression::Abstract>] :compress enable compression. Default: from file extension.
|
13
|
-
def initialize(url, **opts)
|
26
|
+
def initialize(url, format: nil, compress: nil, **opts)
|
14
27
|
@blob = BFS::Blob.new(url)
|
15
|
-
@format = detect_format(
|
16
|
-
@compress = detect_compress(
|
28
|
+
@format = detect_format(format)
|
29
|
+
@compress = detect_compress(compress)
|
30
|
+
@opts = opts
|
31
|
+
|
32
|
+
BFS.defer(self, :close)
|
17
33
|
end
|
18
34
|
|
19
35
|
# Opens the remote for reading.
|
20
36
|
# @param [Hash] opts BFS::Blob#open options
|
21
37
|
# @yield A block over a formatted stream.
|
22
38
|
# @yieldparam [Feedx::Format::Abstract] formatted input stream.
|
23
|
-
def open(**opts)
|
39
|
+
def open(**opts, &block)
|
24
40
|
@blob.open(**opts) do |io|
|
25
|
-
@compress.reader(io) do |cio|
|
26
|
-
|
27
|
-
yield fmt
|
41
|
+
@compress.reader(io, **@opts) do |cio|
|
42
|
+
@format.decoder(cio, **@opts, &block)
|
28
43
|
end
|
29
44
|
end
|
30
45
|
end
|
@@ -33,28 +48,29 @@ module Feedx
|
|
33
48
|
# @param [Hash] opts BFS::Blob#create options
|
34
49
|
# @yield A block over a formatted stream.
|
35
50
|
# @yieldparam [Feedx::Format::Abstract] formatted output stream.
|
36
|
-
def create(**opts)
|
51
|
+
def create(**opts, &block)
|
37
52
|
@blob.create(**opts) do |io|
|
38
|
-
@compress.writer(io) do |cio|
|
39
|
-
|
40
|
-
yield fmt
|
53
|
+
@compress.writer(io, **@opts) do |cio|
|
54
|
+
@format.encoder(cio, **@opts, &block)
|
41
55
|
end
|
42
56
|
end
|
43
57
|
end
|
44
58
|
|
59
|
+
# Closes the underlying connection.
|
60
|
+
def close
|
61
|
+
@blob.close
|
62
|
+
end
|
63
|
+
|
45
64
|
private
|
46
65
|
|
47
66
|
def detect_format(val)
|
48
67
|
case val
|
49
68
|
when nil
|
50
69
|
Feedx::Format.detect(@blob.path)
|
51
|
-
when
|
52
|
-
parent = Feedx::Format::Abstract
|
53
|
-
raise ArgumentError, "Class #{val} must extend #{parent}" unless val < parent
|
54
|
-
|
55
|
-
val
|
56
|
-
else
|
70
|
+
when String, Symbol
|
57
71
|
Feedx::Format.resolve(val)
|
72
|
+
else
|
73
|
+
Feedx::Format.validate!(val)
|
58
74
|
end
|
59
75
|
end
|
60
76
|
|
@@ -62,13 +78,10 @@ module Feedx
|
|
62
78
|
case val
|
63
79
|
when nil
|
64
80
|
Feedx::Compression.detect(@blob.path)
|
65
|
-
when
|
66
|
-
parent = Feedx::Compression::Abstract
|
67
|
-
raise ArgumentError, "Class #{val} must extend #{parent}" unless val < parent
|
68
|
-
|
69
|
-
val
|
70
|
-
else
|
81
|
+
when String, Symbol
|
71
82
|
Feedx::Compression.resolve(val)
|
83
|
+
else
|
84
|
+
Feedx::Compression.validate!(val)
|
72
85
|
end
|
73
86
|
end
|
74
87
|
end
|
data/producer_test.go
CHANGED
data/reader_test.go
CHANGED
@@ -5,9 +5,9 @@ import (
|
|
5
5
|
"io"
|
6
6
|
"io/ioutil"
|
7
7
|
|
8
|
-
"github.com/bsm/feedx"
|
9
|
-
|
10
8
|
"github.com/bsm/bfs"
|
9
|
+
"github.com/bsm/feedx"
|
10
|
+
"github.com/bsm/feedx/internal/testdata"
|
11
11
|
. "github.com/onsi/ginkgo"
|
12
12
|
. "github.com/onsi/gomega"
|
13
13
|
)
|
@@ -38,18 +38,18 @@ var _ = Describe("Reader", func() {
|
|
38
38
|
})
|
39
39
|
|
40
40
|
It("should decode", func() {
|
41
|
-
var msgs []MockMessage
|
41
|
+
var msgs []*testdata.MockMessage
|
42
42
|
for {
|
43
|
-
var msg MockMessage
|
43
|
+
var msg testdata.MockMessage
|
44
44
|
err := subject.Decode(&msg)
|
45
45
|
if err == io.EOF {
|
46
46
|
break
|
47
47
|
}
|
48
48
|
Expect(err).NotTo(HaveOccurred())
|
49
|
-
msgs = append(msgs, msg)
|
49
|
+
msgs = append(msgs, &msg)
|
50
50
|
}
|
51
51
|
|
52
|
-
Expect(msgs).To(
|
52
|
+
Expect(msgs).To(ConsistOf(seed(), seed(), seed()))
|
53
53
|
Expect(subject.NumRead()).To(Equal(3))
|
54
54
|
})
|
55
55
|
})
|
@@ -3,13 +3,13 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe Feedx::Compression::Gzip do
|
4
4
|
it 'should wrap readers/writers' do
|
5
5
|
wio = StringIO.new
|
6
|
-
|
6
|
+
subject.writer(wio) {|w| w.write 'xyz' * 1000 }
|
7
7
|
expect(wio.size).to be_within(20).of(40)
|
8
8
|
expect(wio.string.encoding).to eq(Encoding::BINARY)
|
9
9
|
|
10
10
|
data = ''
|
11
11
|
StringIO.open(wio.string) do |rio|
|
12
|
-
|
12
|
+
subject.reader(rio) {|z| data = z.read }
|
13
13
|
end
|
14
14
|
expect(data.size).to eq(3000)
|
15
15
|
expect(data.encoding).to eq(Encoding.default_external)
|
@@ -3,12 +3,12 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe Feedx::Compression::None do
|
4
4
|
it 'should wrap readers/writers' do
|
5
5
|
wio = StringIO.new
|
6
|
-
|
6
|
+
subject.writer(wio) {|w| w.write 'xyz' * 1000 }
|
7
7
|
expect(wio.size).to eq(3000)
|
8
8
|
|
9
9
|
data = ''
|
10
10
|
StringIO.open(wio.string) do |rio|
|
11
|
-
|
11
|
+
subject.reader(rio) {|z| data = z.read }
|
12
12
|
end
|
13
13
|
expect(data.size).to eq(3000)
|
14
14
|
end
|
@@ -2,18 +2,18 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe Feedx::Compression do
|
4
4
|
it 'should resolve' do
|
5
|
-
expect(described_class.resolve(:gzip)).to
|
6
|
-
expect(described_class.resolve(:gz)).to
|
7
|
-
expect(described_class.resolve(nil)).to
|
5
|
+
expect(described_class.resolve(:gzip)).to be_instance_of(described_class::Gzip)
|
6
|
+
expect(described_class.resolve(:gz)).to be_instance_of(described_class::Gzip)
|
7
|
+
expect(described_class.resolve(nil)).to be_instance_of(described_class::None)
|
8
8
|
expect { described_class.resolve(:txt) }.to raise_error(/invalid compression txt/)
|
9
9
|
end
|
10
10
|
|
11
11
|
it 'should detect' do
|
12
|
-
expect(described_class.detect('path/to/file.jsonz')).to
|
13
|
-
expect(described_class.detect('path/to/file.json.gz')).to
|
14
|
-
expect(described_class.detect('path/to/file.json')).to
|
15
|
-
expect(described_class.detect('path/to/file.pbz')).to
|
16
|
-
expect(described_class.detect('path/to/file.pb.gz')).to
|
17
|
-
expect(described_class.detect('path/to/file.pb')).to
|
12
|
+
expect(described_class.detect('path/to/file.jsonz')).to be_instance_of(described_class::Gzip)
|
13
|
+
expect(described_class.detect('path/to/file.json.gz')).to be_instance_of(described_class::Gzip)
|
14
|
+
expect(described_class.detect('path/to/file.json')).to be_instance_of(described_class::None)
|
15
|
+
expect(described_class.detect('path/to/file.pbz')).to be_instance_of(described_class::Gzip)
|
16
|
+
expect(described_class.detect('path/to/file.pb.gz')).to be_instance_of(described_class::Gzip)
|
17
|
+
expect(described_class.detect('path/to/file.pb')).to be_instance_of(described_class::None)
|
18
18
|
end
|
19
19
|
end
|
data/spec/feedx/consumer_spec.rb
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Feedx::Format::Abstract do
|
4
|
-
subject { Feedx::Format::JSON.new
|
4
|
+
subject { Feedx::Format::JSON.new }
|
5
5
|
let(:wio) { StringIO.new }
|
6
|
+
let(:rio) { StringIO.open(wio.string) }
|
6
7
|
|
7
8
|
it 'should decode each' do
|
8
|
-
subject.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
subject.encoder wio do |enc|
|
10
|
+
enc.encode(Feedx::TestCase::Model.new('X'))
|
11
|
+
enc.encode(Feedx::TestCase::Model.new('Y'))
|
12
|
+
enc.encode(Feedx::TestCase::Message.new(title: 'Z'))
|
13
|
+
end
|
14
|
+
|
15
|
+
subject.decoder rio do |dec|
|
16
|
+
acc = dec.decode_each(Feedx::TestCase::Model).to_a
|
17
|
+
expect(acc.map(&:title)).to eq(%w[X Y Z])
|
15
18
|
end
|
16
19
|
end
|
17
20
|
end
|
@@ -1,26 +1,27 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Feedx::Format::JSON do
|
4
|
-
subject { described_class.new(wio) }
|
5
4
|
let(:wio) { StringIO.new }
|
5
|
+
let(:rio) { StringIO.open(wio.string) }
|
6
6
|
|
7
7
|
it 'should encode/decode' do
|
8
|
-
subject.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
8
|
+
subject.encoder wio do |enc|
|
9
|
+
enc.encode(Feedx::TestCase::Model.new('X'))
|
10
|
+
enc.encode(Feedx::TestCase::Model.new('Y'))
|
11
|
+
enc.encode(Feedx::TestCase::Message.new(title: 'Z'))
|
12
|
+
end
|
13
|
+
expect(wio.string).to eq(<<~JSON)
|
14
|
+
{"title":"X","updated_at":"2018-01-05 11:25:15 UTC"}
|
15
|
+
{"title":"Y","updated_at":"2018-01-05 11:25:15 UTC"}
|
16
|
+
{"title":"Z"}
|
17
|
+
JSON
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
expect(
|
20
|
-
expect(
|
21
|
-
expect(
|
22
|
-
expect(
|
23
|
-
expect(fmt).to be_eof
|
19
|
+
subject.decoder rio do |dec|
|
20
|
+
expect(dec.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('X'))
|
21
|
+
expect(dec.decode(Feedx::TestCase::Model.new('O'))).to eq(Feedx::TestCase::Model.new('Y'))
|
22
|
+
expect(dec.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('Z'))
|
23
|
+
expect(dec.decode(Feedx::TestCase::Model)).to be_nil
|
24
|
+
expect(dec).to be_eof
|
24
25
|
end
|
25
26
|
end
|
26
27
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Feedx::Format::Parquet do
|
4
|
+
let(:wio) { StringIO.new }
|
5
|
+
let(:rio) { StringIO.open(wio.string) }
|
6
|
+
|
7
|
+
let(:schema) do
|
8
|
+
Arrow::Schema.new([
|
9
|
+
Arrow::Field.new('title', :string),
|
10
|
+
Arrow::Field.new('updated_at', type: :timestamp, unit: :second),
|
11
|
+
])
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should encode/decode' do
|
15
|
+
subject.encoder wio, schema: schema, batch_size: 2 do |enc|
|
16
|
+
enc.encode(Feedx::TestCase::Model.new('X'))
|
17
|
+
enc.encode(Feedx::TestCase::Model.new('Y'))
|
18
|
+
enc.encode(Feedx::TestCase::Model.new('Z'))
|
19
|
+
end
|
20
|
+
expect(wio.string.bytesize).to be_within(100).of(1100)
|
21
|
+
|
22
|
+
subject.decoder rio do |dec|
|
23
|
+
expect(dec.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('X'))
|
24
|
+
expect(dec.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('Y'))
|
25
|
+
expect(dec.decode(Feedx::TestCase::Model)).to eq(Feedx::TestCase::Model.new('Z'))
|
26
|
+
expect(dec.decode(Feedx::TestCase::Model)).to be_nil
|
27
|
+
expect(dec).to be_eof
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,22 +1,23 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Feedx::Format::Protobuf do
|
4
|
-
subject { described_class.new(wio) }
|
5
4
|
let(:wio) { StringIO.new }
|
5
|
+
let(:rio) { StringIO.open(wio.string) }
|
6
6
|
|
7
7
|
it 'should encode/decode' do
|
8
|
-
subject.
|
9
|
-
|
10
|
-
|
8
|
+
subject.encoder wio do |enc|
|
9
|
+
enc.encode(Feedx::TestCase::Model.new('X'))
|
10
|
+
enc.encode(Feedx::TestCase::Model.new('Y'))
|
11
|
+
enc.encode(Feedx::TestCase::Message.new(title: 'Z'))
|
12
|
+
end
|
11
13
|
expect(wio.string.bytes).to eq([3, 10, 1, 88] + [3, 10, 1, 89] + [3, 10, 1, 90])
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
expect(
|
16
|
-
expect(
|
17
|
-
expect(
|
18
|
-
expect(
|
19
|
-
expect(fmt).to be_eof
|
15
|
+
subject.decoder rio do |dec|
|
16
|
+
expect(dec.decode(Feedx::TestCase::Message)).to eq(Feedx::TestCase::Message.new(title: 'X'))
|
17
|
+
expect(dec.decode(Feedx::TestCase::Message)).to eq(Feedx::TestCase::Message.new(title: 'Y'))
|
18
|
+
expect(dec.decode(Feedx::TestCase::Message)).to eq(Feedx::TestCase::Message.new(title: 'Z'))
|
19
|
+
expect(dec.decode(Feedx::TestCase::Message)).to be_nil
|
20
|
+
expect(dec).to be_eof
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
data/spec/feedx/format_spec.rb
CHANGED
@@ -2,18 +2,18 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe Feedx::Format do
|
4
4
|
it 'should resolve' do
|
5
|
-
expect(described_class.resolve(:json)).to
|
6
|
-
expect(described_class.resolve(:pb)).to
|
5
|
+
expect(described_class.resolve(:json)).to be_instance_of(described_class::JSON)
|
6
|
+
expect(described_class.resolve(:pb)).to be_instance_of(described_class::Protobuf)
|
7
7
|
expect { described_class.resolve(:txt) }.to raise_error(/invalid format txt/)
|
8
8
|
end
|
9
9
|
|
10
10
|
it 'should detect' do
|
11
|
-
expect(described_class.detect('path/to/file.json')).to
|
12
|
-
expect(described_class.detect('path/to/file.jsonz')).to
|
13
|
-
expect(described_class.detect('path/to/file.json.gz')).to
|
14
|
-
expect(described_class.detect('path/to/file.pb')).to
|
15
|
-
expect(described_class.detect('path/to/file.pbz')).to
|
16
|
-
expect(described_class.detect('path/to/file.pb.z')).to
|
11
|
+
expect(described_class.detect('path/to/file.json')).to be_instance_of(described_class::JSON)
|
12
|
+
expect(described_class.detect('path/to/file.jsonz')).to be_instance_of(described_class::JSON)
|
13
|
+
expect(described_class.detect('path/to/file.json.gz')).to be_instance_of(described_class::JSON)
|
14
|
+
expect(described_class.detect('path/to/file.pb')).to be_instance_of(described_class::Protobuf)
|
15
|
+
expect(described_class.detect('path/to/file.pbz')).to be_instance_of(described_class::Protobuf)
|
16
|
+
expect(described_class.detect('path/to/file.pb.z')).to be_instance_of(described_class::Protobuf)
|
17
17
|
expect do
|
18
18
|
described_class.detect('path/to/file.txt')
|
19
19
|
end.to raise_error(/unable to detect format/)
|