feedx 0.3.1 → 0.3.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 +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
|