feedx 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +13 -4
- data/Gemfile.lock +5 -5
- data/Makefile +7 -0
- data/compression.go +63 -0
- data/compression_test.go +66 -0
- data/consumer.go +206 -0
- data/consumer_test.go +70 -0
- data/feedx.gemspec +1 -1
- data/feedx.go +3 -0
- data/feedx_test.go +27 -0
- data/format.go +122 -0
- data/format_test.go +80 -0
- data/go.mod +13 -0
- data/go.sum +111 -0
- metadata +14 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d1c12376b32188b646923935967905bb97ab8c86dde28236a0341ee878eb631
|
4
|
+
data.tar.gz: 3c76d4356a0f6aafcad0e8ea37a2159f8170f5d1cea4072753e3c9947afd7613
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d55b8ee3620301c3ffc5c95d2895d00f54ee562250c4aa8236ed93fd99e552cd67c126feab4a17b063415518e46b113ef918491b19e37043b5d36fa9e09c2808
|
7
|
+
data.tar.gz: b8eee064c4e12e462097087431cdd9b435720eead2d7524c9cc7d44cb4a7dde410756f443b71cd5b24261d3b06c95f1d3975ddca6b6ff69692e0e9a77a742446
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
feedx (0.3.
|
4
|
+
feedx (0.3.2)
|
5
5
|
bfs (>= 0.3.4)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
10
|
ast (2.4.0)
|
11
|
-
bfs (0.3.
|
11
|
+
bfs (0.3.6)
|
12
12
|
diff-lcs (1.3)
|
13
13
|
google-protobuf (3.6.1)
|
14
14
|
jaro_winkler (1.5.1)
|
@@ -19,7 +19,7 @@ GEM
|
|
19
19
|
google-protobuf
|
20
20
|
powerpack (0.1.2)
|
21
21
|
rainbow (3.0.0)
|
22
|
-
rake (12.3.
|
22
|
+
rake (12.3.2)
|
23
23
|
rspec (3.8.0)
|
24
24
|
rspec-core (~> 3.8.0)
|
25
25
|
rspec-expectations (~> 3.8.0)
|
@@ -33,7 +33,7 @@ GEM
|
|
33
33
|
diff-lcs (>= 1.2.0, < 2.0)
|
34
34
|
rspec-support (~> 3.8.0)
|
35
35
|
rspec-support (3.8.0)
|
36
|
-
rubocop (0.
|
36
|
+
rubocop (0.61.1)
|
37
37
|
jaro_winkler (~> 1.5.1)
|
38
38
|
parallel (~> 1.10)
|
39
39
|
parser (>= 2.5, != 2.5.1.1)
|
@@ -56,4 +56,4 @@ DEPENDENCIES
|
|
56
56
|
rubocop
|
57
57
|
|
58
58
|
BUNDLED WITH
|
59
|
-
1.
|
59
|
+
1.17.2
|
data/Makefile
ADDED
data/compression.go
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
package feedx
|
2
|
+
|
3
|
+
import (
|
4
|
+
"compress/gzip"
|
5
|
+
"io"
|
6
|
+
"path"
|
7
|
+
)
|
8
|
+
|
9
|
+
// Compression represents the data compression.
|
10
|
+
type Compression interface {
|
11
|
+
// NewReader wraps a reader.
|
12
|
+
NewReader(io.Reader) (io.ReadCloser, error)
|
13
|
+
// NewWriter wraps a writer.
|
14
|
+
NewWriter(io.Writer) (io.WriteCloser, error)
|
15
|
+
}
|
16
|
+
|
17
|
+
// DetectCompression detects the compression type from a URL path or file name.
|
18
|
+
func DetectCompression(name string) Compression {
|
19
|
+
if name != "" {
|
20
|
+
ext := path.Ext(path.Base(name))
|
21
|
+
if ext != "" && ext[0] == '.' && ext[len(ext)-1] == 'z' {
|
22
|
+
return GZipCompression
|
23
|
+
}
|
24
|
+
}
|
25
|
+
return NoCompression
|
26
|
+
}
|
27
|
+
|
28
|
+
// --------------------------------------------------------------------
|
29
|
+
|
30
|
+
// NoCompression is just a pass-through without compression.
|
31
|
+
var NoCompression = noCompression{}
|
32
|
+
|
33
|
+
type noCompression struct{}
|
34
|
+
|
35
|
+
func (noCompression) NewReader(r io.Reader) (io.ReadCloser, error) {
|
36
|
+
return noCompressionWrapper{Reader: r}, nil
|
37
|
+
}
|
38
|
+
|
39
|
+
func (noCompression) NewWriter(w io.Writer) (io.WriteCloser, error) {
|
40
|
+
return noCompressionWrapper{Writer: w}, nil
|
41
|
+
}
|
42
|
+
|
43
|
+
type noCompressionWrapper struct {
|
44
|
+
io.Reader
|
45
|
+
io.Writer
|
46
|
+
}
|
47
|
+
|
48
|
+
func (noCompressionWrapper) Close() error { return nil }
|
49
|
+
|
50
|
+
// --------------------------------------------------------------------
|
51
|
+
|
52
|
+
// GZipCompression supports gzip compression format.
|
53
|
+
var GZipCompression = gzipCompression{}
|
54
|
+
|
55
|
+
type gzipCompression struct{}
|
56
|
+
|
57
|
+
func (gzipCompression) NewReader(r io.Reader) (io.ReadCloser, error) {
|
58
|
+
return gzip.NewReader(r)
|
59
|
+
}
|
60
|
+
|
61
|
+
func (gzipCompression) NewWriter(w io.Writer) (io.WriteCloser, error) {
|
62
|
+
return gzip.NewWriter(w), nil
|
63
|
+
}
|
data/compression_test.go
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
package feedx_test
|
2
|
+
|
3
|
+
import (
|
4
|
+
"bytes"
|
5
|
+
|
6
|
+
"github.com/bsm/feedx"
|
7
|
+
. "github.com/onsi/ginkgo"
|
8
|
+
. "github.com/onsi/gomega"
|
9
|
+
)
|
10
|
+
|
11
|
+
var _ = Describe("Compression", func() {
|
12
|
+
var data = bytes.Repeat([]byte("wxyz"), 1024)
|
13
|
+
|
14
|
+
runSharedTest := func(subject feedx.Compression) {
|
15
|
+
buf := new(bytes.Buffer)
|
16
|
+
|
17
|
+
w, err := subject.NewWriter(buf)
|
18
|
+
Expect(err).NotTo(HaveOccurred())
|
19
|
+
defer w.Close()
|
20
|
+
|
21
|
+
Expect(w.Write(data)).To(Equal(4096))
|
22
|
+
Expect(w.Write(data)).To(Equal(4096))
|
23
|
+
Expect(w.Close()).To(Succeed())
|
24
|
+
|
25
|
+
r, err := subject.NewReader(buf)
|
26
|
+
Expect(err).NotTo(HaveOccurred())
|
27
|
+
defer r.Close()
|
28
|
+
|
29
|
+
p := make([]byte, 20)
|
30
|
+
Expect(r.Read(p)).To(Equal(20))
|
31
|
+
Expect(string(p)).To(Equal("wxyzwxyzwxyzwxyzwxyz"))
|
32
|
+
Expect(r.Close()).To(Succeed())
|
33
|
+
}
|
34
|
+
|
35
|
+
It("should detect the format", func() {
|
36
|
+
Expect(feedx.DetectCompression("/path/to/file.json")).To(Equal(feedx.NoCompression))
|
37
|
+
Expect(feedx.DetectCompression("/path/to/file.json.gz")).To(Equal(feedx.GZipCompression))
|
38
|
+
Expect(feedx.DetectCompression("/path/to/file.jsonz")).To(Equal(feedx.GZipCompression))
|
39
|
+
|
40
|
+
Expect(feedx.DetectCompression("/path/to/file.pb")).To(Equal(feedx.NoCompression))
|
41
|
+
Expect(feedx.DetectCompression("/path/to/file.pb.gz")).To(Equal(feedx.GZipCompression))
|
42
|
+
Expect(feedx.DetectCompression("/path/to/file.pbz")).To(Equal(feedx.GZipCompression))
|
43
|
+
|
44
|
+
Expect(feedx.DetectCompression("")).To(Equal(feedx.NoCompression))
|
45
|
+
Expect(feedx.DetectCompression("/path/to/file")).To(Equal(feedx.NoCompression))
|
46
|
+
Expect(feedx.DetectCompression("/path/to/file.txt")).To(Equal(feedx.NoCompression))
|
47
|
+
})
|
48
|
+
|
49
|
+
Describe("NoCompression", func() {
|
50
|
+
var subject = feedx.NoCompression
|
51
|
+
var _ feedx.Compression = subject
|
52
|
+
|
53
|
+
It("should write/read", func() {
|
54
|
+
runSharedTest(subject)
|
55
|
+
})
|
56
|
+
})
|
57
|
+
|
58
|
+
Describe("GZipCompression", func() {
|
59
|
+
var subject = feedx.GZipCompression
|
60
|
+
var _ feedx.Compression = subject
|
61
|
+
|
62
|
+
It("should write/read", func() {
|
63
|
+
runSharedTest(subject)
|
64
|
+
})
|
65
|
+
})
|
66
|
+
})
|
data/consumer.go
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
package feedx
|
2
|
+
|
3
|
+
import (
|
4
|
+
"context"
|
5
|
+
"fmt"
|
6
|
+
"strconv"
|
7
|
+
"sync/atomic"
|
8
|
+
"time"
|
9
|
+
|
10
|
+
"github.com/bsm/bfs"
|
11
|
+
)
|
12
|
+
|
13
|
+
// ConsumerOptions configure the Puller instance.
|
14
|
+
type ConsumerOptions struct {
|
15
|
+
// The interval used by Puller to check the remote changes.
|
16
|
+
// Default: 1m
|
17
|
+
Interval time.Duration
|
18
|
+
|
19
|
+
// Format specifies the format
|
20
|
+
// Default: auto-detected from URL path.
|
21
|
+
Format Format
|
22
|
+
|
23
|
+
// Compression specifies the compression type.
|
24
|
+
// Default: auto-detected from URL path.
|
25
|
+
Compression Compression
|
26
|
+
|
27
|
+
// AfterSync callbacks are triggered after each sync, receiving
|
28
|
+
// the updated status and error (if occurred).
|
29
|
+
AfterSync func(updated bool, err error)
|
30
|
+
}
|
31
|
+
|
32
|
+
func (o *ConsumerOptions) norm(name string) error {
|
33
|
+
if o.Interval <= 0 {
|
34
|
+
o.Interval = time.Minute
|
35
|
+
}
|
36
|
+
if o.Format == nil {
|
37
|
+
o.Format = DetectFormat(name)
|
38
|
+
|
39
|
+
if o.Format == nil {
|
40
|
+
return fmt.Errorf("feedx: unable to detect format from %q", name)
|
41
|
+
}
|
42
|
+
}
|
43
|
+
if o.Compression == nil {
|
44
|
+
o.Compression = DetectCompression(name)
|
45
|
+
}
|
46
|
+
return nil
|
47
|
+
}
|
48
|
+
|
49
|
+
// ParseFunc is a data parse function.
|
50
|
+
type ParseFunc func(FormatDecoder) (data interface{}, size int64, err error)
|
51
|
+
|
52
|
+
// Consumer manages data retrieval from a remote feed.
|
53
|
+
// It queries the feed in regular intervals, continuously retrieving new updates.
|
54
|
+
type Consumer interface {
|
55
|
+
// Data returns the data as returned by ParseFunc on last sync.
|
56
|
+
Data() interface{}
|
57
|
+
// LastCheck returns time of last sync attempt.
|
58
|
+
LastCheck() time.Time
|
59
|
+
// LastModified returns time at which the remote feed was last modified.
|
60
|
+
LastModified() time.Time
|
61
|
+
// Size returns the size as returned by ParseFunc on last sync.
|
62
|
+
Size() int64
|
63
|
+
// Close stops the underlying sync process.
|
64
|
+
Close() error
|
65
|
+
}
|
66
|
+
|
67
|
+
// NewConsumer starts a new feed consumer.
|
68
|
+
func NewConsumer(ctx context.Context, srcURL string, opt *ConsumerOptions, parse ParseFunc) (Consumer, error) {
|
69
|
+
src, err := bfs.NewObject(ctx, srcURL)
|
70
|
+
if err != nil {
|
71
|
+
return nil, err
|
72
|
+
}
|
73
|
+
|
74
|
+
var o ConsumerOptions
|
75
|
+
if opt != nil {
|
76
|
+
o = *opt
|
77
|
+
}
|
78
|
+
if err := o.norm(src.Name()); err != nil {
|
79
|
+
_ = src.Close()
|
80
|
+
return nil, err
|
81
|
+
}
|
82
|
+
|
83
|
+
ctx, stop := context.WithCancel(ctx)
|
84
|
+
f := &consumer{
|
85
|
+
src: src,
|
86
|
+
opt: o,
|
87
|
+
ctx: ctx,
|
88
|
+
stop: stop,
|
89
|
+
parse: parse,
|
90
|
+
}
|
91
|
+
|
92
|
+
// run initial sync
|
93
|
+
if _, err := f.sync(true); err != nil {
|
94
|
+
_ = f.Close()
|
95
|
+
return nil, err
|
96
|
+
}
|
97
|
+
|
98
|
+
// start continuous loop
|
99
|
+
go f.loop()
|
100
|
+
|
101
|
+
return f, nil
|
102
|
+
}
|
103
|
+
|
104
|
+
type consumer struct {
|
105
|
+
src *bfs.Object
|
106
|
+
opt ConsumerOptions
|
107
|
+
ctx context.Context
|
108
|
+
stop context.CancelFunc
|
109
|
+
|
110
|
+
parse ParseFunc
|
111
|
+
|
112
|
+
size, lastModMs int64
|
113
|
+
data, lastCheck atomic.Value
|
114
|
+
}
|
115
|
+
|
116
|
+
// Data implements Feed interface.
|
117
|
+
func (f *consumer) Data() interface{} {
|
118
|
+
return f.data.Load()
|
119
|
+
}
|
120
|
+
|
121
|
+
// Size implements Feed interface.
|
122
|
+
func (f *consumer) Size() int64 {
|
123
|
+
return atomic.LoadInt64(&f.size)
|
124
|
+
}
|
125
|
+
|
126
|
+
// LastCheck implements Feed interface.
|
127
|
+
func (f *consumer) LastCheck() time.Time {
|
128
|
+
return f.lastCheck.Load().(time.Time)
|
129
|
+
}
|
130
|
+
|
131
|
+
// LastModified implements Feed interface.
|
132
|
+
func (f *consumer) LastModified() time.Time {
|
133
|
+
msec := atomic.LoadInt64(&f.lastModMs)
|
134
|
+
return time.Unix(msec/1000, msec%1000*1e6)
|
135
|
+
}
|
136
|
+
|
137
|
+
// Close implements Feed interface.
|
138
|
+
func (f *consumer) Close() error {
|
139
|
+
f.stop()
|
140
|
+
return f.src.Close()
|
141
|
+
}
|
142
|
+
|
143
|
+
func (f *consumer) sync(force bool) (bool, error) {
|
144
|
+
f.lastCheck.Store(time.Now())
|
145
|
+
|
146
|
+
info, err := f.src.Head(f.ctx)
|
147
|
+
if err != nil {
|
148
|
+
return false, err
|
149
|
+
}
|
150
|
+
|
151
|
+
// calculate last modified time
|
152
|
+
msec, _ := strconv.ParseInt(info.Metadata[lastModifiedMetaKey], 10, 64)
|
153
|
+
|
154
|
+
// skip update if not forced or modified
|
155
|
+
if msec == atomic.LoadInt64(&f.lastModMs) && !force {
|
156
|
+
return false, nil
|
157
|
+
}
|
158
|
+
|
159
|
+
// open remote for reading
|
160
|
+
r, err := f.src.Open(f.ctx)
|
161
|
+
if err != nil {
|
162
|
+
return false, err
|
163
|
+
}
|
164
|
+
defer r.Close()
|
165
|
+
|
166
|
+
// wrap in compressed reader
|
167
|
+
c, err := f.opt.Compression.NewReader(r)
|
168
|
+
if err != nil {
|
169
|
+
return false, err
|
170
|
+
}
|
171
|
+
defer c.Close()
|
172
|
+
|
173
|
+
// open decoder
|
174
|
+
d, err := f.opt.Format.NewDecoder(c)
|
175
|
+
if err != nil {
|
176
|
+
return false, err
|
177
|
+
}
|
178
|
+
defer f.Close()
|
179
|
+
|
180
|
+
// parse feed
|
181
|
+
data, size, err := f.parse(d)
|
182
|
+
if err != nil {
|
183
|
+
return false, err
|
184
|
+
}
|
185
|
+
|
186
|
+
// update stores
|
187
|
+
f.data.Store(data)
|
188
|
+
atomic.StoreInt64(&f.size, size)
|
189
|
+
atomic.StoreInt64(&f.lastModMs, msec)
|
190
|
+
return true, nil
|
191
|
+
}
|
192
|
+
|
193
|
+
func (f *consumer) loop() {
|
194
|
+
ticker := time.NewTicker(f.opt.Interval)
|
195
|
+
defer ticker.Stop()
|
196
|
+
|
197
|
+
for {
|
198
|
+
select {
|
199
|
+
case <-f.ctx.Done():
|
200
|
+
return
|
201
|
+
case <-ticker.C:
|
202
|
+
updated, err := f.sync(false)
|
203
|
+
f.opt.AfterSync(updated, err)
|
204
|
+
}
|
205
|
+
}
|
206
|
+
}
|
data/consumer_test.go
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
package feedx_test
|
2
|
+
|
3
|
+
import (
|
4
|
+
"context"
|
5
|
+
"io"
|
6
|
+
"time"
|
7
|
+
|
8
|
+
"github.com/bsm/bfs"
|
9
|
+
"github.com/bsm/feedx"
|
10
|
+
tbp "github.com/golang/protobuf/proto/proto3_proto"
|
11
|
+
. "github.com/onsi/ginkgo"
|
12
|
+
. "github.com/onsi/gomega"
|
13
|
+
)
|
14
|
+
|
15
|
+
var _ = Describe("Consumer", func() {
|
16
|
+
ctx := context.Background()
|
17
|
+
msg := &tbp.Message{
|
18
|
+
Name: "Joe",
|
19
|
+
TrueScotsman: true,
|
20
|
+
Hilarity: tbp.Message_BILL_BAILEY,
|
21
|
+
}
|
22
|
+
pfn := func(dec feedx.FormatDecoder) (interface{}, int64, error) {
|
23
|
+
var msgs []*tbp.Message
|
24
|
+
for {
|
25
|
+
msg := new(tbp.Message)
|
26
|
+
if err := dec.Decode(msg); err == io.EOF {
|
27
|
+
break
|
28
|
+
} else if err != nil {
|
29
|
+
return nil, 0, err
|
30
|
+
}
|
31
|
+
msgs = append(msgs, msg)
|
32
|
+
}
|
33
|
+
return msgs, int64(len(msgs)), nil
|
34
|
+
}
|
35
|
+
|
36
|
+
BeforeEach(func() {
|
37
|
+
memStore = bfs.NewInMem()
|
38
|
+
w, err := memStore.Create(ctx, "path/to/file.jsonz", &bfs.WriteOptions{
|
39
|
+
Metadata: map[string]string{"x-feedx-pusher-last-modified": "1544477788899"},
|
40
|
+
})
|
41
|
+
Expect(err).NotTo(HaveOccurred())
|
42
|
+
defer w.Close()
|
43
|
+
|
44
|
+
c, err := feedx.GZipCompression.NewWriter(w)
|
45
|
+
Expect(err).NotTo(HaveOccurred())
|
46
|
+
defer c.Close()
|
47
|
+
|
48
|
+
f, err := feedx.JSONFormat.NewEncoder(c)
|
49
|
+
Expect(err).NotTo(HaveOccurred())
|
50
|
+
defer f.Close()
|
51
|
+
|
52
|
+
Expect(f.Encode(msg)).To(Succeed())
|
53
|
+
Expect(f.Encode(msg)).To(Succeed())
|
54
|
+
Expect(f.Close()).To(Succeed())
|
55
|
+
Expect(c.Close()).To(Succeed())
|
56
|
+
Expect(w.Close()).To(Succeed())
|
57
|
+
})
|
58
|
+
|
59
|
+
It("should sync and retrieve feeds from remote", func() {
|
60
|
+
subject, err := feedx.NewConsumer(ctx, "mem:///path/to/file.jsonz", nil, pfn)
|
61
|
+
Expect(err).NotTo(HaveOccurred())
|
62
|
+
defer subject.Close()
|
63
|
+
|
64
|
+
Expect(subject.LastCheck()).To(BeTemporally("~", time.Now(), time.Second))
|
65
|
+
Expect(subject.LastModified()).To(BeTemporally("~", time.Unix(1544477788, 0), time.Second))
|
66
|
+
Expect(subject.Size()).To(Equal(int64(2)))
|
67
|
+
Expect(subject.Data()).To(Equal([]*tbp.Message{msg, msg}))
|
68
|
+
Expect(subject.Close()).To(Succeed())
|
69
|
+
})
|
70
|
+
})
|
data/feedx.gemspec
CHANGED
data/feedx.go
ADDED
data/feedx_test.go
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
package feedx_test
|
2
|
+
|
3
|
+
import (
|
4
|
+
"context"
|
5
|
+
"net/url"
|
6
|
+
"testing"
|
7
|
+
|
8
|
+
"github.com/bsm/bfs"
|
9
|
+
. "github.com/onsi/ginkgo"
|
10
|
+
. "github.com/onsi/gomega"
|
11
|
+
)
|
12
|
+
|
13
|
+
// ------------------------------------------------------------------------
|
14
|
+
|
15
|
+
var memStore *bfs.InMem
|
16
|
+
|
17
|
+
func init() {
|
18
|
+
memStore = bfs.NewInMem()
|
19
|
+
bfs.Register("mem", func(_ context.Context, u *url.URL) (bfs.Bucket, error) {
|
20
|
+
return memStore, nil
|
21
|
+
})
|
22
|
+
}
|
23
|
+
|
24
|
+
func TestSuite(t *testing.T) {
|
25
|
+
RegisterFailHandler(Fail)
|
26
|
+
RunSpecs(t, "feedx")
|
27
|
+
}
|
data/format.go
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
package feedx
|
2
|
+
|
3
|
+
import (
|
4
|
+
"encoding/json"
|
5
|
+
"fmt"
|
6
|
+
"io"
|
7
|
+
"path"
|
8
|
+
|
9
|
+
"github.com/golang/protobuf/proto"
|
10
|
+
|
11
|
+
pbio "github.com/gogo/protobuf/io"
|
12
|
+
)
|
13
|
+
|
14
|
+
// Format represents the data format.
|
15
|
+
type Format interface {
|
16
|
+
// NewDecoder wraps a decoder around a reader.
|
17
|
+
NewDecoder(io.Reader) (FormatDecoder, error)
|
18
|
+
// NewEncoder wraps an encoder around a writer.
|
19
|
+
NewEncoder(io.Writer) (FormatEncoder, error)
|
20
|
+
}
|
21
|
+
|
22
|
+
// DetectFormat detects the data format from a URL path or file name.
|
23
|
+
// May return nil.
|
24
|
+
func DetectFormat(name string) Format {
|
25
|
+
ext := path.Ext(path.Base(name))
|
26
|
+
switch ext {
|
27
|
+
case ".json":
|
28
|
+
return JSONFormat
|
29
|
+
case ".pb", ".proto", ".protobuf":
|
30
|
+
return ProtobufFormat
|
31
|
+
default:
|
32
|
+
if name != "" && ext != "" && ext[0] == '.' {
|
33
|
+
if ext[len(ext)-1] == 'z' {
|
34
|
+
return DetectFormat(name[0 : len(name)-1])
|
35
|
+
}
|
36
|
+
return DetectFormat(name[0 : len(name)-len(ext)])
|
37
|
+
}
|
38
|
+
}
|
39
|
+
return nil
|
40
|
+
}
|
41
|
+
|
42
|
+
// FormatDecoder methods
|
43
|
+
type FormatDecoder interface {
|
44
|
+
// Decode decodes the next message into an interface.
|
45
|
+
Decode(v interface{}) error
|
46
|
+
|
47
|
+
io.Closer
|
48
|
+
}
|
49
|
+
|
50
|
+
// FormatEncoder methods
|
51
|
+
type FormatEncoder interface {
|
52
|
+
// Encode encodes the value to the stream.
|
53
|
+
Encode(v interface{}) error
|
54
|
+
|
55
|
+
io.Closer
|
56
|
+
}
|
57
|
+
|
58
|
+
// --------------------------------------------------------------------
|
59
|
+
|
60
|
+
// JSONFormat provides a Format implemention for JSON.
|
61
|
+
var JSONFormat = jsonFormat{}
|
62
|
+
|
63
|
+
type jsonFormat struct{}
|
64
|
+
|
65
|
+
// NewDecoder implements Format.
|
66
|
+
func (jsonFormat) NewDecoder(r io.Reader) (FormatDecoder, error) {
|
67
|
+
return jsonDecoderWrapper{Decoder: json.NewDecoder(r)}, nil
|
68
|
+
}
|
69
|
+
|
70
|
+
// NewEncoder implements Format.
|
71
|
+
func (jsonFormat) NewEncoder(w io.Writer) (FormatEncoder, error) {
|
72
|
+
return jsonEncoderWrapper{Encoder: json.NewEncoder(w)}, nil
|
73
|
+
}
|
74
|
+
|
75
|
+
type jsonDecoderWrapper struct{ *json.Decoder }
|
76
|
+
|
77
|
+
func (jsonDecoderWrapper) Close() error { return nil }
|
78
|
+
|
79
|
+
type jsonEncoderWrapper struct{ *json.Encoder }
|
80
|
+
|
81
|
+
func (jsonEncoderWrapper) Close() error { return nil }
|
82
|
+
|
83
|
+
// --------------------------------------------------------------------
|
84
|
+
|
85
|
+
const protobufMaxMessageSize = 20 * 1024 * 1024 // 20MB
|
86
|
+
|
87
|
+
// ProtobufFormat provides a Format implemention for Protobuf.
|
88
|
+
var ProtobufFormat = protobufFormat{}
|
89
|
+
|
90
|
+
type protobufFormat struct{}
|
91
|
+
|
92
|
+
// NewDecoder implements Format.
|
93
|
+
func (protobufFormat) NewDecoder(r io.Reader) (FormatDecoder, error) {
|
94
|
+
rc := pbio.NewDelimitedReader(r, protobufMaxMessageSize)
|
95
|
+
return protobufDecoderWrapper{ReadCloser: rc}, nil
|
96
|
+
}
|
97
|
+
|
98
|
+
// NewEncoder implements Format.
|
99
|
+
func (protobufFormat) NewEncoder(w io.Writer) (FormatEncoder, error) {
|
100
|
+
wc := pbio.NewDelimitedWriter(w)
|
101
|
+
return protobufEncoderWrapper{WriteCloser: wc}, nil
|
102
|
+
}
|
103
|
+
|
104
|
+
type protobufDecoderWrapper struct{ pbio.ReadCloser }
|
105
|
+
|
106
|
+
func (w protobufDecoderWrapper) Decode(v interface{}) error {
|
107
|
+
msg, ok := v.(proto.Message)
|
108
|
+
if !ok {
|
109
|
+
return fmt.Errorf("feedx: value %v is not a proto.Message", v)
|
110
|
+
}
|
111
|
+
return w.ReadCloser.ReadMsg(msg)
|
112
|
+
}
|
113
|
+
|
114
|
+
type protobufEncoderWrapper struct{ pbio.WriteCloser }
|
115
|
+
|
116
|
+
func (w protobufEncoderWrapper) Encode(v interface{}) error {
|
117
|
+
msg, ok := v.(proto.Message)
|
118
|
+
if !ok {
|
119
|
+
return fmt.Errorf("feedx: value %v is not a proto.Message", v)
|
120
|
+
}
|
121
|
+
return w.WriteMsg(msg)
|
122
|
+
}
|
data/format_test.go
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
package feedx_test
|
2
|
+
|
3
|
+
import (
|
4
|
+
"bytes"
|
5
|
+
"io"
|
6
|
+
|
7
|
+
"github.com/bsm/feedx"
|
8
|
+
tbp "github.com/golang/protobuf/proto/proto3_proto"
|
9
|
+
. "github.com/onsi/ginkgo"
|
10
|
+
. "github.com/onsi/gomega"
|
11
|
+
)
|
12
|
+
|
13
|
+
var _ = Describe("Format", func() {
|
14
|
+
msg := &tbp.Message{
|
15
|
+
Name: "Joe",
|
16
|
+
TrueScotsman: true,
|
17
|
+
Hilarity: tbp.Message_BILL_BAILEY,
|
18
|
+
}
|
19
|
+
|
20
|
+
runSharedTest := func(subject feedx.Format) {
|
21
|
+
buf := new(bytes.Buffer)
|
22
|
+
|
23
|
+
enc, err := subject.NewEncoder(buf)
|
24
|
+
Expect(err).NotTo(HaveOccurred())
|
25
|
+
defer enc.Close()
|
26
|
+
|
27
|
+
Expect(enc.Encode(msg)).To(Succeed())
|
28
|
+
Expect(enc.Encode(msg)).To(Succeed())
|
29
|
+
Expect(enc.Close()).To(Succeed())
|
30
|
+
|
31
|
+
dec, err := subject.NewDecoder(buf)
|
32
|
+
Expect(err).NotTo(HaveOccurred())
|
33
|
+
defer dec.Close()
|
34
|
+
|
35
|
+
v1 := new(tbp.Message)
|
36
|
+
Expect(dec.Decode(v1)).To(Succeed())
|
37
|
+
Expect(v1.Name).To(Equal("Joe"))
|
38
|
+
|
39
|
+
v2 := new(tbp.Message)
|
40
|
+
Expect(dec.Decode(v2)).To(Succeed())
|
41
|
+
Expect(v2.Name).To(Equal("Joe"))
|
42
|
+
|
43
|
+
v3 := new(tbp.Message)
|
44
|
+
Expect(dec.Decode(v3)).To(MatchError(io.EOF))
|
45
|
+
|
46
|
+
Expect(dec.Close()).To(Succeed())
|
47
|
+
}
|
48
|
+
|
49
|
+
It("should detect the format", func() {
|
50
|
+
Expect(feedx.DetectFormat("/path/to/file.json")).To(Equal(feedx.JSONFormat))
|
51
|
+
Expect(feedx.DetectFormat("/path/to/file.json.gz")).To(Equal(feedx.JSONFormat))
|
52
|
+
Expect(feedx.DetectFormat("/path/to/file.jsonz")).To(Equal(feedx.JSONFormat))
|
53
|
+
|
54
|
+
Expect(feedx.DetectFormat("/path/to/file.pb")).To(Equal(feedx.ProtobufFormat))
|
55
|
+
Expect(feedx.DetectFormat("/path/to/file.pb.gz")).To(Equal(feedx.ProtobufFormat))
|
56
|
+
Expect(feedx.DetectFormat("/path/to/file.pbz")).To(Equal(feedx.ProtobufFormat))
|
57
|
+
|
58
|
+
Expect(feedx.DetectFormat("")).To(BeNil())
|
59
|
+
Expect(feedx.DetectFormat("/path/to/file")).To(BeNil())
|
60
|
+
Expect(feedx.DetectFormat("/path/to/file.txt")).To(BeNil())
|
61
|
+
})
|
62
|
+
|
63
|
+
Describe("JSONFormat", func() {
|
64
|
+
var subject = feedx.JSONFormat
|
65
|
+
var _ feedx.Format = subject
|
66
|
+
|
67
|
+
It("should encode/decode", func() {
|
68
|
+
runSharedTest(subject)
|
69
|
+
})
|
70
|
+
})
|
71
|
+
|
72
|
+
Describe("ProtobufFormat", func() {
|
73
|
+
var subject = feedx.ProtobufFormat
|
74
|
+
var _ feedx.Format = subject
|
75
|
+
|
76
|
+
It("should encode/decode", func() {
|
77
|
+
runSharedTest(subject)
|
78
|
+
})
|
79
|
+
})
|
80
|
+
})
|
data/go.mod
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module github.com/bsm/feedx
|
2
|
+
|
3
|
+
require (
|
4
|
+
github.com/bsm/bfs v0.4.0
|
5
|
+
github.com/gogo/protobuf v1.2.0
|
6
|
+
github.com/golang/protobuf v1.2.0
|
7
|
+
github.com/kr/pretty v0.1.0 // indirect
|
8
|
+
github.com/onsi/ginkgo v1.7.0
|
9
|
+
github.com/onsi/gomega v1.4.3
|
10
|
+
golang.org/x/net v0.0.0-20181213202711-891ebc4b82d6 // indirect
|
11
|
+
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 // indirect
|
12
|
+
gopkg.in/yaml.v2 v2.2.2 // indirect
|
13
|
+
)
|
data/go.sum
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
2
|
+
cloud.google.com/go v0.32.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
3
|
+
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
4
|
+
git.apache.org/thrift.git v0.0.0-20181106172052-f7d43ce0aa58/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
5
|
+
github.com/aws/aws-sdk-go v1.15.71/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
|
6
|
+
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
7
|
+
github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ=
|
8
|
+
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
9
|
+
github.com/bsm/bfs v0.4.0 h1:+fT6iDNt1ytYVbSA1PCJFQez8j+DuO53D38wy39ol1g=
|
10
|
+
github.com/bsm/bfs v0.4.0/go.mod h1:b96pNJ2nox+3JhdQL2oaVrzSsTpd1pEL+GgQuWjxIO0=
|
11
|
+
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
12
|
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
13
|
+
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
14
|
+
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
15
|
+
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
16
|
+
github.com/go-ini/ini v1.39.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
17
|
+
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
|
18
|
+
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
19
|
+
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
20
|
+
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
21
|
+
github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
22
|
+
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
23
|
+
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
24
|
+
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
25
|
+
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
26
|
+
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
27
|
+
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
28
|
+
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
29
|
+
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
30
|
+
github.com/grpc-ecosystem/grpc-gateway v1.5.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
31
|
+
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
32
|
+
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
33
|
+
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
34
|
+
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
35
|
+
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
36
|
+
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
37
|
+
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
38
|
+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
39
|
+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
40
|
+
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
41
|
+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
42
|
+
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
43
|
+
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
44
|
+
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
45
|
+
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
46
|
+
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
47
|
+
github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
|
48
|
+
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
49
|
+
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
50
|
+
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
51
|
+
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
52
|
+
github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
53
|
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
54
|
+
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
55
|
+
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
56
|
+
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
57
|
+
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
58
|
+
github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
59
|
+
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
60
|
+
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
61
|
+
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
62
|
+
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
63
|
+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
64
|
+
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
65
|
+
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
66
|
+
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
67
|
+
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
68
|
+
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
69
|
+
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
70
|
+
golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg=
|
71
|
+
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
72
|
+
golang.org/x/net v0.0.0-20181213202711-891ebc4b82d6 h1:gT0Y6H7hbVPUtvtk0YGxMXPgN+p8fYlqWkgJeUCZcaQ=
|
73
|
+
golang.org/x/net v0.0.0-20181213202711-891ebc4b82d6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
74
|
+
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
75
|
+
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
76
|
+
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
77
|
+
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
78
|
+
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
79
|
+
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
80
|
+
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
81
|
+
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 h1:YoY1wS6JYVRpIfFngRf2HHo9R9dAne3xbkGOQ5rJXjU=
|
82
|
+
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
83
|
+
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM=
|
84
|
+
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
85
|
+
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
86
|
+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
87
|
+
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
88
|
+
golang.org/x/tools v0.0.0-20181107225058-a28dfb48e06b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
89
|
+
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
90
|
+
google.golang.org/api v0.0.0-20181108001712-cfbc873f6b93/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
91
|
+
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
92
|
+
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
93
|
+
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
94
|
+
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
95
|
+
google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
96
|
+
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
97
|
+
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
98
|
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
99
|
+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
100
|
+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
101
|
+
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
102
|
+
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
103
|
+
gopkg.in/ini.v1 v1.39.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
104
|
+
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
105
|
+
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
106
|
+
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
107
|
+
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
108
|
+
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
109
|
+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
110
|
+
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
111
|
+
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
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.3.
|
4
|
+
version: 0.3.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: 2018-
|
11
|
+
date: 2018-12-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bfs
|
@@ -108,9 +108,20 @@ files:
|
|
108
108
|
- Gemfile
|
109
109
|
- Gemfile.lock
|
110
110
|
- LICENSE
|
111
|
+
- Makefile
|
111
112
|
- README.md
|
112
113
|
- Rakefile
|
114
|
+
- compression.go
|
115
|
+
- compression_test.go
|
116
|
+
- consumer.go
|
117
|
+
- consumer_test.go
|
113
118
|
- feedx.gemspec
|
119
|
+
- feedx.go
|
120
|
+
- feedx_test.go
|
121
|
+
- format.go
|
122
|
+
- format_test.go
|
123
|
+
- go.mod
|
124
|
+
- go.sum
|
114
125
|
- lib/feedx.rb
|
115
126
|
- lib/feedx/compression.rb
|
116
127
|
- lib/feedx/compression/abstract.rb
|
@@ -149,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
160
|
version: '0'
|
150
161
|
requirements: []
|
151
162
|
rubyforge_project:
|
152
|
-
rubygems_version: 2.7.
|
163
|
+
rubygems_version: 2.7.7
|
153
164
|
signing_key:
|
154
165
|
specification_version: 4
|
155
166
|
summary: Exchange data between components via feeds
|