feedx 0.12.0 → 0.12.6
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/.github/workflows/test.yml +60 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +14 -5
- data/Gemfile +0 -2
- data/Gemfile.lock +60 -49
- data/Makefile +6 -6
- data/README.md +1 -1
- data/compression.go +18 -0
- data/compression_test.go +17 -5
- data/consumer.go +12 -3
- data/consumer_test.go +50 -19
- data/ext/parquet/decoder.go +170 -0
- data/ext/parquet/decoder_test.go +88 -0
- data/ext/parquet/go.mod +10 -0
- data/ext/parquet/go.sum +154 -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 +5 -6
- data/feedx_ext_test.go +6 -0
- data/feedx_test.go +6 -6
- data/format.go +45 -15
- data/format_test.go +7 -5
- data/go.mod +10 -5
- data/go.sum +90 -25
- data/internal/testdata/testdata.pb.go +176 -77
- data/lib/feedx/cache/memory.rb +1 -0
- data/lib/feedx/consumer.rb +9 -6
- data/lib/feedx/format.rb +1 -1
- data/lib/feedx/producer.rb +20 -18
- data/lib/feedx/stream.rb +24 -8
- data/producer_test.go +4 -4
- data/reader_test.go +6 -5
- data/spec/feedx/cache/memory_spec.rb +2 -2
- data/spec/feedx/cache/value_spec.rb +1 -1
- data/spec/feedx/compression/gzip_spec.rb +1 -1
- data/spec/feedx/compression/none_spec.rb +1 -1
- data/spec/feedx/compression_spec.rb +2 -2
- data/spec/feedx/consumer_spec.rb +5 -4
- data/spec/feedx/format/abstract_spec.rb +2 -1
- data/spec/feedx/format/json_spec.rb +6 -6
- data/spec/feedx/format/parquet_spec.rb +1 -1
- data/spec/feedx/format/protobuf_spec.rb +1 -1
- data/spec/feedx/format_spec.rb +2 -2
- data/spec/feedx/producer_spec.rb +10 -9
- data/spec/feedx/stream_spec.rb +36 -18
- data/writer.go +1 -4
- data/writer_test.go +8 -8
- metadata +25 -23
- data/.travis.yml +0 -24
@@ -1,24 +1,29 @@
|
|
1
|
-
// Code generated by protoc-gen-
|
1
|
+
// Code generated by protoc-gen-go. DO NOT EDIT.
|
2
|
+
// versions:
|
3
|
+
// protoc-gen-go v1.24.0-devel
|
4
|
+
// protoc v3.11.3
|
2
5
|
// source: internal/testdata/testdata.proto
|
3
6
|
|
4
7
|
package testdata
|
5
8
|
|
6
9
|
import (
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
+
proto "github.com/golang/protobuf/proto"
|
11
|
+
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
12
|
+
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
13
|
+
reflect "reflect"
|
14
|
+
sync "sync"
|
10
15
|
)
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
const (
|
18
|
+
// Verify that this generated code is sufficiently up-to-date.
|
19
|
+
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
20
|
+
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
21
|
+
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
22
|
+
)
|
16
23
|
|
17
|
-
// This is a compile-time assertion
|
18
|
-
//
|
19
|
-
|
20
|
-
// proto package needs to be updated.
|
21
|
-
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
24
|
+
// This is a compile-time assertion that a sufficiently up-to-date version
|
25
|
+
// of the legacy proto package is being used.
|
26
|
+
const _ = proto.ProtoPackageIsVersion4
|
22
27
|
|
23
28
|
type MockEnum int32
|
24
29
|
|
@@ -27,98 +32,192 @@ const (
|
|
27
32
|
MockEnum_FIRST MockEnum = 3
|
28
33
|
)
|
29
34
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
35
|
+
// Enum value maps for MockEnum.
|
36
|
+
var (
|
37
|
+
MockEnum_name = map[int32]string{
|
38
|
+
0: "UNKNOWN",
|
39
|
+
3: "FIRST",
|
40
|
+
}
|
41
|
+
MockEnum_value = map[string]int32{
|
42
|
+
"UNKNOWN": 0,
|
43
|
+
"FIRST": 3,
|
44
|
+
}
|
45
|
+
)
|
34
46
|
|
35
|
-
|
36
|
-
|
37
|
-
|
47
|
+
func (x MockEnum) Enum() *MockEnum {
|
48
|
+
p := new(MockEnum)
|
49
|
+
*p = x
|
50
|
+
return p
|
38
51
|
}
|
39
52
|
|
40
53
|
func (x MockEnum) String() string {
|
41
|
-
return
|
54
|
+
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
42
55
|
}
|
43
56
|
|
44
|
-
func (MockEnum)
|
45
|
-
return
|
57
|
+
func (MockEnum) Descriptor() protoreflect.EnumDescriptor {
|
58
|
+
return file_internal_testdata_testdata_proto_enumTypes[0].Descriptor()
|
46
59
|
}
|
47
60
|
|
48
|
-
|
49
|
-
|
50
|
-
Enum MockEnum `protobuf:"varint,2,opt,name=enum,proto3,enum=feedx.internal.testdata.MockEnum" json:"enum,omitempty"`
|
51
|
-
Height uint32 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"`
|
52
|
-
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
53
|
-
XXX_unrecognized []byte `json:"-"`
|
54
|
-
XXX_sizecache int32 `json:"-"`
|
61
|
+
func (MockEnum) Type() protoreflect.EnumType {
|
62
|
+
return &file_internal_testdata_testdata_proto_enumTypes[0]
|
55
63
|
}
|
56
64
|
|
57
|
-
func (
|
58
|
-
|
59
|
-
func (*MockMessage) ProtoMessage() {}
|
60
|
-
func (*MockMessage) Descriptor() ([]byte, []int) {
|
61
|
-
return fileDescriptor_076a9f61cb4a1904, []int{0}
|
65
|
+
func (x MockEnum) Number() protoreflect.EnumNumber {
|
66
|
+
return protoreflect.EnumNumber(x)
|
62
67
|
}
|
63
|
-
|
64
|
-
|
68
|
+
|
69
|
+
// Deprecated: Use MockEnum.Descriptor instead.
|
70
|
+
func (MockEnum) EnumDescriptor() ([]byte, []int) {
|
71
|
+
return file_internal_testdata_testdata_proto_rawDescGZIP(), []int{0}
|
65
72
|
}
|
66
|
-
|
67
|
-
|
73
|
+
|
74
|
+
type MockMessage struct {
|
75
|
+
state protoimpl.MessageState
|
76
|
+
sizeCache protoimpl.SizeCache
|
77
|
+
unknownFields protoimpl.UnknownFields
|
78
|
+
|
79
|
+
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
80
|
+
Enum MockEnum `protobuf:"varint,2,opt,name=enum,proto3,enum=feedx.internal.testdata.MockEnum" json:"enum,omitempty"`
|
81
|
+
Height uint32 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"`
|
68
82
|
}
|
69
|
-
|
70
|
-
|
83
|
+
|
84
|
+
func (x *MockMessage) Reset() {
|
85
|
+
*x = MockMessage{}
|
86
|
+
if protoimpl.UnsafeEnabled {
|
87
|
+
mi := &file_internal_testdata_testdata_proto_msgTypes[0]
|
88
|
+
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
89
|
+
ms.StoreMessageInfo(mi)
|
90
|
+
}
|
71
91
|
}
|
72
|
-
|
73
|
-
|
92
|
+
|
93
|
+
func (x *MockMessage) String() string {
|
94
|
+
return protoimpl.X.MessageStringOf(x)
|
74
95
|
}
|
75
|
-
|
76
|
-
|
96
|
+
|
97
|
+
func (*MockMessage) ProtoMessage() {}
|
98
|
+
|
99
|
+
func (x *MockMessage) ProtoReflect() protoreflect.Message {
|
100
|
+
mi := &file_internal_testdata_testdata_proto_msgTypes[0]
|
101
|
+
if protoimpl.UnsafeEnabled && x != nil {
|
102
|
+
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
103
|
+
if ms.LoadMessageInfo() == nil {
|
104
|
+
ms.StoreMessageInfo(mi)
|
105
|
+
}
|
106
|
+
return ms
|
107
|
+
}
|
108
|
+
return mi.MessageOf(x)
|
77
109
|
}
|
78
110
|
|
79
|
-
|
111
|
+
// Deprecated: Use MockMessage.ProtoReflect.Descriptor instead.
|
112
|
+
func (*MockMessage) Descriptor() ([]byte, []int) {
|
113
|
+
return file_internal_testdata_testdata_proto_rawDescGZIP(), []int{0}
|
114
|
+
}
|
80
115
|
|
81
|
-
func (
|
82
|
-
if
|
83
|
-
return
|
116
|
+
func (x *MockMessage) GetName() string {
|
117
|
+
if x != nil {
|
118
|
+
return x.Name
|
84
119
|
}
|
85
120
|
return ""
|
86
121
|
}
|
87
122
|
|
88
|
-
func (
|
89
|
-
if
|
90
|
-
return
|
123
|
+
func (x *MockMessage) GetEnum() MockEnum {
|
124
|
+
if x != nil {
|
125
|
+
return x.Enum
|
91
126
|
}
|
92
127
|
return MockEnum_UNKNOWN
|
93
128
|
}
|
94
129
|
|
95
|
-
func (
|
96
|
-
if
|
97
|
-
return
|
130
|
+
func (x *MockMessage) GetHeight() uint32 {
|
131
|
+
if x != nil {
|
132
|
+
return x.Height
|
98
133
|
}
|
99
134
|
return 0
|
100
135
|
}
|
101
136
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
137
|
+
var File_internal_testdata_testdata_proto protoreflect.FileDescriptor
|
138
|
+
|
139
|
+
var file_internal_testdata_testdata_proto_rawDesc = []byte{
|
140
|
+
0x0a, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64,
|
141
|
+
0x61, 0x74, 0x61, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f,
|
142
|
+
0x74, 0x6f, 0x12, 0x17, 0x66, 0x65, 0x65, 0x64, 0x78, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
143
|
+
0x61, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x22, 0x70, 0x0a, 0x0b, 0x4d,
|
144
|
+
0x6f, 0x63, 0x6b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
|
145
|
+
0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35,
|
146
|
+
0x0a, 0x04, 0x65, 0x6e, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x66,
|
147
|
+
0x65, 0x65, 0x64, 0x78, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x74, 0x65,
|
148
|
+
0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4d, 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x75, 0x6d, 0x52,
|
149
|
+
0x04, 0x65, 0x6e, 0x75, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18,
|
150
|
+
0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x2a, 0x22, 0x0a,
|
151
|
+
0x08, 0x4d, 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b,
|
152
|
+
0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x49, 0x52, 0x53, 0x54, 0x10,
|
153
|
+
0x03, 0x42, 0x24, 0x5a, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
154
|
+
0x66, 0x65, 0x65, 0x64, 0x78, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74,
|
155
|
+
0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
156
|
+
}
|
157
|
+
|
158
|
+
var (
|
159
|
+
file_internal_testdata_testdata_proto_rawDescOnce sync.Once
|
160
|
+
file_internal_testdata_testdata_proto_rawDescData = file_internal_testdata_testdata_proto_rawDesc
|
161
|
+
)
|
162
|
+
|
163
|
+
func file_internal_testdata_testdata_proto_rawDescGZIP() []byte {
|
164
|
+
file_internal_testdata_testdata_proto_rawDescOnce.Do(func() {
|
165
|
+
file_internal_testdata_testdata_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_testdata_testdata_proto_rawDescData)
|
166
|
+
})
|
167
|
+
return file_internal_testdata_testdata_proto_rawDescData
|
168
|
+
}
|
169
|
+
|
170
|
+
var file_internal_testdata_testdata_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
171
|
+
var file_internal_testdata_testdata_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
172
|
+
var file_internal_testdata_testdata_proto_goTypes = []interface{}{
|
173
|
+
(MockEnum)(0), // 0: feedx.internal.testdata.MockEnum
|
174
|
+
(*MockMessage)(nil), // 1: feedx.internal.testdata.MockMessage
|
175
|
+
}
|
176
|
+
var file_internal_testdata_testdata_proto_depIdxs = []int32{
|
177
|
+
0, // 0: feedx.internal.testdata.MockMessage.enum:type_name -> feedx.internal.testdata.MockEnum
|
178
|
+
1, // [1:1] is the sub-list for method output_type
|
179
|
+
1, // [1:1] is the sub-list for method input_type
|
180
|
+
1, // [1:1] is the sub-list for extension type_name
|
181
|
+
1, // [1:1] is the sub-list for extension extendee
|
182
|
+
0, // [0:1] is the sub-list for field type_name
|
183
|
+
}
|
184
|
+
|
185
|
+
func init() { file_internal_testdata_testdata_proto_init() }
|
186
|
+
func file_internal_testdata_testdata_proto_init() {
|
187
|
+
if File_internal_testdata_testdata_proto != nil {
|
188
|
+
return
|
189
|
+
}
|
190
|
+
if !protoimpl.UnsafeEnabled {
|
191
|
+
file_internal_testdata_testdata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
192
|
+
switch v := v.(*MockMessage); i {
|
193
|
+
case 0:
|
194
|
+
return &v.state
|
195
|
+
case 1:
|
196
|
+
return &v.sizeCache
|
197
|
+
case 2:
|
198
|
+
return &v.unknownFields
|
199
|
+
default:
|
200
|
+
return nil
|
201
|
+
}
|
202
|
+
}
|
203
|
+
}
|
204
|
+
type x struct{}
|
205
|
+
out := protoimpl.TypeBuilder{
|
206
|
+
File: protoimpl.DescBuilder{
|
207
|
+
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
208
|
+
RawDescriptor: file_internal_testdata_testdata_proto_rawDesc,
|
209
|
+
NumEnums: 1,
|
210
|
+
NumMessages: 1,
|
211
|
+
NumExtensions: 0,
|
212
|
+
NumServices: 0,
|
213
|
+
},
|
214
|
+
GoTypes: file_internal_testdata_testdata_proto_goTypes,
|
215
|
+
DependencyIndexes: file_internal_testdata_testdata_proto_depIdxs,
|
216
|
+
EnumInfos: file_internal_testdata_testdata_proto_enumTypes,
|
217
|
+
MessageInfos: file_internal_testdata_testdata_proto_msgTypes,
|
218
|
+
}.Build()
|
219
|
+
File_internal_testdata_testdata_proto = out.File
|
220
|
+
file_internal_testdata_testdata_proto_rawDesc = nil
|
221
|
+
file_internal_testdata_testdata_proto_goTypes = nil
|
222
|
+
file_internal_testdata_testdata_proto_depIdxs = nil
|
124
223
|
}
|
data/lib/feedx/cache/memory.rb
CHANGED
data/lib/feedx/consumer.rb
CHANGED
@@ -19,10 +19,10 @@ module Feedx
|
|
19
19
|
# @option opts [Symbol,Class<Feedx::Compression::Abstract>] :compress enable compression. Default: from file extension.
|
20
20
|
# @option opts [Feedx::Cache::Value] :cache cache value to store remote last modified time and consume conditionally.
|
21
21
|
def initialize(url, klass, format_options: {}, cache: nil, **opts)
|
22
|
-
@klass
|
23
|
-
@
|
24
|
-
@
|
25
|
-
@
|
22
|
+
@klass = klass
|
23
|
+
@url = url
|
24
|
+
@opts = opts.merge(format_options)
|
25
|
+
@cache = cache
|
26
26
|
|
27
27
|
return if format_options.empty? || (defined?(Gem::Deprecate) && Gem::Deprecate.skip)
|
28
28
|
|
@@ -31,21 +31,24 @@ module Feedx
|
|
31
31
|
|
32
32
|
# @return [Boolean] returns true if performed.
|
33
33
|
def each(&block)
|
34
|
+
stream = Feedx::Stream.new(@url, **@opts)
|
34
35
|
remote_rev = nil
|
35
36
|
|
36
37
|
if @cache
|
37
|
-
metadata =
|
38
|
+
metadata = stream.blob.info.metadata
|
38
39
|
local_rev = @cache.read.to_i
|
39
40
|
remote_rev = (metadata[META_LAST_MODIFIED] || metadata[META_LAST_MODIFIED_DC]).to_i
|
40
41
|
return false if remote_rev.positive? && remote_rev <= local_rev
|
41
42
|
end
|
42
43
|
|
43
|
-
|
44
|
+
stream.open do |fmt|
|
44
45
|
fmt.decode_each(@klass, **@opts, &block)
|
45
46
|
end
|
46
47
|
@cache.write(remote_rev) if @cache && remote_rev
|
47
48
|
|
48
49
|
true
|
50
|
+
ensure
|
51
|
+
stream&.close
|
49
52
|
end
|
50
53
|
end
|
51
54
|
end
|
data/lib/feedx/format.rb
CHANGED
@@ -27,7 +27,7 @@ module Feedx
|
|
27
27
|
ext = File.extname(base)
|
28
28
|
raise ArgumentError, 'unable to detect format' if ext.empty?
|
29
29
|
|
30
|
-
kind = _resolve(ext[1
|
30
|
+
kind = _resolve(ext[1..]) || _resolve(ext[1..-2])
|
31
31
|
return kind if kind
|
32
32
|
|
33
33
|
base = base[0..-ext.size - 1]
|
data/lib/feedx/producer.rb
CHANGED
@@ -22,9 +22,9 @@ module Feedx
|
|
22
22
|
@enum = enum || block
|
23
23
|
raise ArgumentError, "#{self.class.name}.new expects an :enum option or a block factory" unless @enum
|
24
24
|
|
25
|
-
@
|
25
|
+
@url = url
|
26
|
+
@opts = opts.merge(format_options)
|
26
27
|
@last_mod = last_modified
|
27
|
-
@opts = opts.merge(format_options)
|
28
28
|
|
29
29
|
return if format_options.empty? || (defined?(Gem::Deprecate) && Gem::Deprecate.skip)
|
30
30
|
|
@@ -32,23 +32,25 @@ module Feedx
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def perform
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
50
53
|
end
|
51
|
-
@stream.blob.info.size
|
52
54
|
end
|
53
55
|
end
|
54
56
|
end
|
data/lib/feedx/stream.rb
CHANGED
@@ -6,6 +6,19 @@ 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.
|
@@ -15,18 +28,18 @@ module Feedx
|
|
15
28
|
@format = detect_format(format)
|
16
29
|
@compress = detect_compress(compress)
|
17
30
|
@opts = opts
|
31
|
+
|
32
|
+
BFS.defer(self, :close)
|
18
33
|
end
|
19
34
|
|
20
35
|
# Opens the remote for reading.
|
21
36
|
# @param [Hash] opts BFS::Blob#open options
|
22
37
|
# @yield A block over a formatted stream.
|
23
38
|
# @yieldparam [Feedx::Format::Abstract] formatted input stream.
|
24
|
-
def open(**opts)
|
39
|
+
def open(**opts, &block)
|
25
40
|
@blob.open(**opts) do |io|
|
26
41
|
@compress.reader(io, **@opts) do |cio|
|
27
|
-
@format.decoder(cio, **@opts)
|
28
|
-
yield fmt
|
29
|
-
end
|
42
|
+
@format.decoder(cio, **@opts, &block)
|
30
43
|
end
|
31
44
|
end
|
32
45
|
end
|
@@ -35,16 +48,19 @@ module Feedx
|
|
35
48
|
# @param [Hash] opts BFS::Blob#create options
|
36
49
|
# @yield A block over a formatted stream.
|
37
50
|
# @yieldparam [Feedx::Format::Abstract] formatted output stream.
|
38
|
-
def create(**opts)
|
51
|
+
def create(**opts, &block)
|
39
52
|
@blob.create(**opts) do |io|
|
40
53
|
@compress.writer(io, **@opts) do |cio|
|
41
|
-
@format.encoder(cio, **@opts)
|
42
|
-
yield fmt
|
43
|
-
end
|
54
|
+
@format.encoder(cio, **@opts, &block)
|
44
55
|
end
|
45
56
|
end
|
46
57
|
end
|
47
58
|
|
59
|
+
# Closes the underlying connection.
|
60
|
+
def close
|
61
|
+
@blob.close
|
62
|
+
end
|
63
|
+
|
48
64
|
private
|
49
65
|
|
50
66
|
def detect_format(val)
|