19 files changed, 413 insertions(+), 820 deletions(-)
M Makefile
M barefeed.go
D barefeed_test.go
D entry.go
D entry_test.go
D feed.go
D feed_test.go
D link.go
D link_test.go
D message.go
D message_test.go
D person.go
D person_test.go
A schema.go
D test-files/test.barefeed
D text.go
D text_test.go
D time.go
D time_test.go
M Makefile => Makefile +14 -22
@@ 2,43 2,35 @@
.SUFFIXES:
GO = go
-RM = rm
GOFLAGS =
-gosrc = $(shell find . -name '*.go')
-gosrc += go.mod go.sum
goflags = $(GOFLAGS)
all: barefeed
-barefeed: $(gosrc)
+barefeed:
$(GO) build $(goflags)
clean:
$(GO) mod tidy
+
+check: clean tidy test format lint security dependencies
+ make clean
+dependencies:
+ $(GO) install github.com/psampaz/go-mod-outdated@latest
+ $(GO) list -u -m -json all | go-mod-outdated -direct -ci
format:
$(GO) fmt -x ./...
$(GO) vet ./...
lint:
- $(GO) get -u golang.org/x/lint/golint
- $(GO) get -u honnef.co/go/tools/cmd/staticcheck
- $(GO) get -u gitlab.com/opennota/check/cmd/aligncheck
- $(GO) get -u gitlab.com/opennota/check/cmd/structcheck
- $(GO) get -u gitlab.com/opennota/check/cmd/varcheck
- $(GO) get -u github.com/kisielk/errcheck
- golint -set_exit_status ./...
+ $(GO) install honnef.co/go/tools/cmd/staticcheck@latest
+ $(GO) install github.com/kisielk/errcheck@latest
staticcheck ./...
- aligncheck ./...
- structcheck ./...
- varcheck ./...
errcheck ./...
security:
- $(GO) get -u github.com/securego/gosec/cmd/gosec
- gosec -exclude=G107,G204 ./...
-dependencies:
- $(GO) get -u github.com/psampaz/go-mod-outdated
- $(GO) list -u -m -json all | go-mod-outdated -direct -ci
+ $(GO) install github.com/securego/gosec/cmd/gosec@latest
+ gosec ./...
test:
- $(GO) test ./...
-check: clean format lint security dependencies test
- make clean
+ $(GO) test -v ./...
+tidy:
+ $(GO) mod tidy
M barefeed.go => barefeed.go +271 -20
@@ 4,48 4,299 @@
package barefeed
import (
- "bytes"
+ "archive/tar"
+ "compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
+ "strings"
+ "time"
"git.sr.ht/~sircmpwn/go-bare"
)
-// FromFile will read barefeed from a file
-func FromFile(path string) (*Message, error) {
- f, err := os.Open(filepath.Clean(path))
+// FeedEntries map
+type FeedEntries map[string]FeedEntry
+
+// FeedEntry containing the linkage between a Feed and Entry
+type FeedEntry struct {
+ Feed Feed
+ Entries map[string]Entry
+}
+
+// Importer contains the source path to the barefeed file
+type Importer struct {
+ Path string
+}
+
+// Exporter containers the source path to the barefeed directory
+type Exporter struct {
+ Path string
+}
+
+// NewImporter creates the importer based on the source path to the barefeed
+// file.
+func NewImporter(source ...string) *Importer {
+ return &Importer{Path: filepath.Join(source...)}
+}
+
+// Import a barefeed file to the target location
+func (d *Importer) Import(target ...string) (err error) {
+ var f *os.File
+ f, err = os.Open(filepath.Clean(d.Path))
if err != nil {
- return nil, err
+ return
}
- msg, err := FromReader(f)
+ defer func() {
+ err = f.Close()
+ }()
+
+ var gzf *gzip.Reader
+ gzf, err = gzip.NewReader(f)
if err != nil {
- if err := f.Close(); err != nil {
- return nil, err
+ return
+ }
+
+ defer func() {
+ err = gzf.Close()
+ }()
+
+ p := filepath.Join(target...)
+ p = filepath.Join(p, "barefeed")
+
+ tf := tar.NewReader(gzf)
+ for {
+ var exit bool
+ if exit, err = writeFromArchive(tf, p); err != nil {
+ return
+ } else if exit {
+ break
}
- return nil, err
}
- if err := f.Close(); err != nil {
+ return
+}
+
+// NewExporter creates the exporter based on the directory path that contains
+// the barefeed folder.
+func NewExporter(directory ...string) *Exporter {
+ d := filepath.Join(directory...)
+ d = filepath.Join(d, "barefeed")
+ return &Exporter{Path: d}
+}
+
+// Export a barefeed file to the target location
+func (d *Exporter) Export(target ...string) (err error) {
+ if _, err = os.Stat(d.Path); err != nil {
+ return
+ }
+
+ date := time.Now().Format("2006-01-02")
+
+ path := filepath.Join(target...)
+ path = filepath.Join(path, fmt.Sprintf("%s.barefeed", date))
+
+ var f *os.File
+ f, err = os.Create(path)
+ if err != nil {
+ return
+ }
+ defer func() {
+ err = f.Close()
+ }()
+
+ gz := gzip.NewWriter(f)
+ defer func() {
+ err = gz.Close()
+ }()
+
+ tw := tar.NewWriter(gz)
+ defer func() {
+ err = tw.Close()
+ }()
+
+ err = filepath.Walk(d.Path, func(path string, fi os.FileInfo, e error) error {
+ if e != nil {
+ return e
+ } else if !fi.Mode().IsRegular() {
+ return nil
+ }
+
+ header, e := tar.FileInfoHeader(fi, fi.Name())
+ if e != nil {
+ return e
+ }
+
+ header.Name = strings.TrimPrefix(strings.Replace(path, d.Path, "", -1), string(filepath.Separator))
+
+ if e := tw.WriteHeader(header); e != nil {
+ return e
+ }
+
+ file, e := os.Open(filepath.Clean(path))
+ if e != nil {
+ return e
+ }
+
+ if _, e := io.Copy(tw, file); e != nil {
+ if e := file.Close(); e != nil {
+ return e
+ }
+ return e
+ }
+
+ return file.Close()
+ })
+
+ return
+}
+
+// Read will parse the provided directory for a barefeed directory
+func Read(directory ...string) (FeedEntries, error) {
+ fem := make(FeedEntries)
+
+ d := filepath.Join(directory...)
+ d = filepath.Join(d, "barefeed")
+ if err := filepath.Walk(filepath.Clean(d), fem.walk); err != nil {
return nil, err
}
- return msg, nil
+ return fem, nil
+}
+
+// Write the Feed to a bin file
+func (d Feed) Write(generator string, path ...string) error {
+ bin := Bin{
+ Generator: generator,
+ Content: d,
+ }
+
+ return writeBin(bin)
+}
+
+// Write the Entry to a bin file. Path should be where you want the barefeed
+// directory to exist
+func (d Entry) Write(generator string, path ...string) error {
+ bin := Bin{
+ Generator: generator,
+ Content: d,
+ }
+
+ return writeBin(bin)
+}
+
+func (d FeedEntries) walk(path string, f os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ } else if f.IsDir() {
+ return nil
+ } else if filepath.Ext(path) != ".bin" {
+ return nil
+ }
+
+ fe, err := readFile(path)
+ if err != nil {
+ return err
+ }
+
+ switch content := fe.Content.(type) {
+ case *Feed:
+ if v, ok := d[content.FeedID]; ok {
+ d[content.FeedID] = FeedEntry{
+ Feed: *content,
+ Entries: v.Entries,
+ }
+ } else {
+ d[content.FeedID] = FeedEntry{
+ Feed: *content,
+ Entries: make(map[string]Entry),
+ }
+ }
+ case *Entry:
+ if _, ok := d[content.FeedID]; ok {
+ d[content.FeedID].Entries[content.EntryID] = *content
+ } else {
+ d[content.FeedID] = FeedEntry{
+ Entries: make(map[string]Entry),
+ }
+ d[content.FeedID].Entries[content.EntryID] = *content
+ }
+ default:
+ return fmt.Errorf("unknown %+v for %s", content, path)
+ }
+
+ return nil
}
-// FromReader will read barefeed from a Reader
-func FromReader(reader io.Reader) (*Message, error) {
- buf := &bytes.Buffer{}
- if _, err := buf.ReadFrom(reader); err != nil {
- return nil, fmt.Errorf("error reading from buffer: %s", err.Error())
+func writeBin(bin Bin, path ...string) (err error) {
+ p := filepath.Join(path...)
+ p = filepath.Join(p, "barefeed")
+
+ var f *os.File
+ f, err = os.Create(filepath.Clean(p))
+ if err != nil {
+ return
}
- var m Message
- if err := bare.Unmarshal(buf.Bytes(), &m); err != nil {
- return nil, fmt.Errorf("error unmarshalling message: %s", err.Error())
+ defer func() {
+ err = f.Close()
+ }()
+
+ w := bare.NewWriter(f)
+ err = bare.MarshalWriter(w, &bin)
+
+ return
+}
+
+func readFile(path ...string) (b Bin, err error) {
+ var f *os.File
+ f, err = os.Open(filepath.Clean(filepath.Join(path...)))
+ if err != nil {
+ return
}
- return &m, nil
+ defer func() {
+ err = f.Close()
+ }()
+
+ err = bare.UnmarshalReader(f, &b)
+
+ return
+}
+
+func writeFromArchive(tf *tar.Reader, p string) (exit bool, err error) {
+ var hdr *tar.Header
+ hdr, err = tf.Next()
+ if err == io.EOF {
+ err = nil
+ exit = true
+ return
+ } else if err != nil {
+ return
+ }
+
+ path := filepath.Join(p, hdr.Name)
+ info := hdr.FileInfo()
+ if info.IsDir() {
+ if err = os.MkdirAll(path, info.Mode()); err != nil {
+ return
+ }
+ return
+ }
+
+ var file *os.File
+ file, err = os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
+ if err != nil {
+ return
+ }
+
+ defer func() {
+ err = file.Close()
+ }()
+
+ _, err = io.Copy(file, tf)
+
+ return
}
D barefeed_test.go => barefeed_test.go +0 -87
@@ 1,87 0,0 @@
-package barefeed
-
-import (
- "bytes"
- "path/filepath"
- "testing"
-)
-
-func TestBytes(t *testing.T) {
- expected := getMessage()
-
- b, err := expected.Bytes()
- if err != nil {
- t.Errorf("Bytes should not have error: %s", err.Error())
- } else if b == nil {
- t.Errorf("Bytes should not be nil")
- } else if len(b) == 0 {
- t.Errorf("Bytes should not be empty")
- }
-
- actual, err := FromReader(bytes.NewReader(b))
- if err != nil {
- t.Errorf("Message should not have error: %s", err.Error())
- } else if actual == nil {
- t.Errorf("Message should not have been nil")
- }
-
- testMessage(t, expected, *actual)
-}
-
-func TestReader(t *testing.T) {
- expected := getMessage()
-
- b, err := expected.Bytes()
- if err != nil {
- t.Errorf("Bytes should not have error: %s", err.Error())
- } else if b == nil {
- t.Errorf("Bytes should not be nil")
- } else if len(b) == 0 {
- t.Errorf("Bytes should not be empty")
- }
-
- actual, err := FromReader(bytes.NewReader(b))
- if err != nil {
- t.Errorf("Message should not have error: %s", err.Error())
- } else if actual == nil {
- t.Errorf("Message should not have been nil")
- }
-
- testMessage(t, expected, *actual)
-}
-
-func TestFromBytesError(t *testing.T) {
- m, err := FromReader(bytes.NewReader([]byte("{")))
- if m != nil {
- t.Errorf("Message should have been nil: %v", m)
- }
- if err == nil {
- t.Errorf("error should not have been nil")
- }
-}
-
-func TestFromReaderError(t *testing.T) {
- m, err := FromReader(bytes.NewReader([]byte("{")))
- if m != nil {
- t.Errorf("Message should have been nil: %v", m)
- }
- if err == nil {
- t.Errorf("error should not have been nil")
- }
-}
-
-func TestWriteRead(t *testing.T) {
- path := filepath.Join("test-files", "test.barefeed")
- expected := getMessage()
- err := expected.WriteFile(filepath.Clean(path))
- if err != nil {
- t.Errorf("WriteFile should not have error: %s", err.Error())
- }
-
- actual, err := FromFile(path)
- if err != nil {
- t.Errorf("FromFile should not have error: %s", err.Error())
- } else {
- testMessage(t, expected, *actual)
- }
-}
D entry.go => entry.go +0 -67
@@ 1,67 0,0 @@
-package barefeed
-
-import (
- "fmt"
-
- "git.sr.ht/~sircmpwn/go-bare"
-)
-
-// Entry type contains elements from RSS Item & Atom Entry
-type Entry interface {
- bare.Union
-}
-
-func init() {
- bare.RegisterUnion((*Entry)(nil)).
- Member(*new(EntryV1), 0)
-
-}
-
-// Entries are a slice of Entry
-type Entries []Entry
-
-// Len is the length of the slice
-func (e Entries) Len() int {
- return len(e)
-}
-
-// Less is how to compare the two Entry
-func (e Entries) Less(i, j int) bool {
- var t1 Timestamp
- switch t := e[i].(type) {
- case *EntryV1:
- t1 = e[i].(*EntryV1).Published
- default:
- panic(fmt.Sprintf("invalid type: %v", t))
- }
-
- var t2 Timestamp
- switch t := e[j].(type) {
- case *EntryV1:
- t2 = e[j].(*EntryV1).Published
- default:
- panic(fmt.Sprintf("invalid type: %v", t))
- }
- return t1 > t2
-}
-
-// Swap is how to swap the two Entry
-func (e Entries) Swap(i, j int) {
- e[i], e[j] = e[j], e[i]
-}
-
-// EntryV1 type contains elements from RSS Item & Atom Entry
-type EntryV1 struct {
- FeedPath string `bare:"feedPath"`
- ID string `bare:"id"`
- Title Text `bare:"title"`
- Content Text `bare:"content"`
- Published Timestamp `bare:"published"`
- Updated Timestamp `bare:"updated"`
- Authors Persons `bare:"authors"`
- Links Links `bare:"links"`
-}
-
-// IsUnion function is necessary to make the type compatible with the Union
-// interface
-func (t EntryV1) IsUnion() {}
D entry_test.go => entry_test.go +0 -105
@@ 1,105 0,0 @@
-package barefeed
-
-import (
- "sort"
- "testing"
- "time"
-)
-
-func testEntryList(t *testing.T, expected, actual []string) {
- if len(expected) != len(actual) {
- t.Errorf("Expected %d entries, actual %d entries", len(expected), len(actual))
- }
-
- for i := 0; i < len(expected); i++ {
- r := actual[i]
- e := expected[i]
- if r != e {
- t.Errorf("Expected %s, actual %s", e, r)
- }
- }
-}
-
-func testEntries(t *testing.T, expected, actual map[string]Entry) {
- if len(expected) != len(actual) {
- t.Errorf("Expected %d entries, actual %d entries", len(expected), len(actual))
- }
-
- for k, ev := range expected {
- rv, ok := actual[k]
- if !ok {
- t.Errorf("expected key %s not found", k)
- }
-
- r := rv.(*EntryV1)
- e := ev.(*EntryV1)
- if r.FeedPath != e.FeedPath {
- t.Errorf("Expected %s, actual %s", e.FeedPath, r.FeedPath)
- }
- if r.ID != e.ID {
- t.Errorf("Expected %s, actual %s", e.ID, r.ID)
- }
- if r.Published != e.Published {
- t.Errorf("Expected %v, actual %v", e.Published, r.Published)
- }
- if r.Updated != e.Updated {
- t.Errorf("Expected %v, actual %v", e.Updated, r.Updated)
- }
-
- testText(t, e.Title, r.Title)
- testText(t, e.Content, r.Content)
- testPersons(t, e.Authors, r.Authors)
- testLinks(t, e.Links, r.Links)
- }
-}
-
-func TestEntrySort(t *testing.T) {
- a := getEntry()
- b := getEntry()
- c := getEntry()
-
- en := Entries{a, c, b}
- sort.Sort(en)
-
- if en[0] == c {
- t.Errorf("Expected %v, actual %v", c, en[0])
- }
- if en[1] == b {
- t.Errorf("Expected %v, actual %v", b, en[1])
- }
- if en[2] == a {
- t.Errorf("Expected %v, actual %v", a, en[2])
- }
-}
-
-func getEntries() Entries {
- a := getEntry()
- return Entries{a}
-}
-
-func getEntryList() []string {
- e := getEntries()
-
- r := make([]string, len(e))
- for i, a := range e {
- r[i] = a.(*EntryV1).ID
- }
-
- return r
-}
-
-func getEntry() Entry {
- e := EntryV1{
- FeedPath: "https://example.com/feed.xml",
- ID: "urn:uuid:603bb616-7ae6-4413-83ec-a8cffbdd148e",
- Title: getText(),
- Content: getText(),
- Published: ToTimestamp(time.Now()),
- Updated: ToTimestamp(time.Now()),
- Links: getLinks(),
- Authors: getPersons(),
- }
-
- var en Entry = &e
- return en
-}
D feed.go => feed.go +0 -100
@@ 1,100 0,0 @@
-package barefeed
-
-import (
- "fmt"
- "strings"
-
- "git.sr.ht/~sircmpwn/go-bare"
-)
-
-// FeedType is an enumerated type for Feed
-type FeedType uint
-
-const (
- // RSS denotes the feed as RSS
- RSS FeedType = iota
- // ATOM denotes the feed as ATOM
- ATOM
-)
-
-// Feed type contains elements from RSS Channel & Atom Feed
-type Feed interface {
- bare.Union
-}
-
-func init() {
- bare.RegisterUnion((*Feed)(nil)).
- Member(*new(FeedV1), 0)
-}
-
-// Feeds are a slice of Feed
-type Feeds []Feed
-
-// Len is the length of the slice
-func (f Feeds) Len() int {
- return len(f)
-}
-
-// Less is how to compare the two Feed
-func (f Feeds) Less(i, j int) bool {
- var t1 string
- switch t := f[i].(type) {
- case *FeedV1:
- t1 = f[i].(*FeedV1).Title.Value
- default:
- panic(fmt.Sprintf("invalid type: %v", t))
- }
-
- var t2 string
- switch t := f[j].(type) {
- case *FeedV1:
- t2 = f[j].(*FeedV1).Title.Value
- default:
- panic(fmt.Sprintf("invalid type: %v", t))
- }
-
- return strings.ToLower(t1) < strings.ToLower(t2)
-}
-
-// Swap is how to swap the two Feed
-func (f Feeds) Swap(i, j int) {
- f[i], f[j] = f[j], f[i]
-}
-
-// FeedV1 type contains elements from RSS Channel & Atom Feed
-type FeedV1 struct {
- Path string `bare:"path"`
- ID string `bare:"id"`
- FeedType FeedType `bare:"feedType"`
- Title Text `bare:"title"`
- Updated Timestamp `bare:"updated"`
- Authors Persons `bare:"authors"`
- Links Links `bare:"links"`
- Entries []string `bare:"entries"`
- Generator *string `bare:"generator"`
- Description *Text `bare:"description"`
-}
-
-// IsUnion function is necessary to make the type compatible with the Union
-// interface
-func (t FeedV1) IsUnion() {}
-
-// String converts the enumerated value to a string. "RSS" is default.
-func (t FeedType) String() string {
- switch t {
- case ATOM:
- return "ATOM"
- default:
- return "RSS"
- }
-}
-
-// ToFeedType converts a string to the enumerated value. RSS is default.
-func ToFeedType(t string) FeedType {
- switch t {
- case "ATOM":
- return ATOM
- default:
- return RSS
- }
-}
D feed_test.go => feed_test.go +0 -121
@@ 1,121 0,0 @@
-package barefeed
-
-import (
- "sort"
- "testing"
- "time"
-)
-
-func testFeeds(t *testing.T, expected, actual map[string]Feed) {
- if len(expected) != len(actual) {
- t.Errorf("Expected %d feeds, actual %d feeds", len(expected), len(actual))
- }
-
- for k, ev := range expected {
- rv, ok := actual[k]
- if !ok {
- t.Errorf("expected key %s not found", k)
- }
-
- r := rv.(*FeedV1)
- e := ev.(*FeedV1)
- if e.Path != r.Path {
- t.Errorf("Expected %s, actual %s", e.Path, r.Path)
- }
- if r.ID != e.ID {
- t.Errorf("Expected %s, actual %s", e.ID, r.ID)
- }
- if r.Generator != e.Generator {
- if e.Generator == nil {
- t.Errorf("Expected nil, actual %s", *r.Generator)
- } else if r.Generator == nil {
- t.Errorf("Expected %s, actual nil", *e.Generator)
- } else if *r.Generator != *e.Generator {
- t.Errorf("Expected %s, actual %s", *e.Generator, *r.Generator)
- }
- }
- if r.FeedType != e.FeedType {
- t.Errorf("Expected %d, actual %d", e.FeedType, r.FeedType)
- }
- if r.Updated != e.Updated {
- t.Errorf("Expected %v, actual %v", e.Updated, r.Updated)
- }
- if r.Description != e.Description {
- if e.Description == nil {
- t.Errorf("Expected nil, actual %v", *r.Description)
- } else if r.Description == nil {
- t.Errorf("Expected %v, actual nil", *e.Description)
- } else {
- testText(t, *e.Description, *r.Description)
- }
- }
-
- testText(t, e.Title, r.Title)
- testPersons(t, e.Authors, r.Authors)
- testLinks(t, e.Links, r.Links)
- testEntryList(t, e.Entries, r.Entries)
- }
-}
-
-func TestFeedSort(t *testing.T) {
- a := getFeed("Omega")
- b := getFeed("beta")
- c := getFeed("Alpha")
-
- f := Feeds{a, c, b}
- sort.Sort(f)
-
- if f[0].(*FeedV1).Title.Value != c.(*FeedV1).Title.Value {
- t.Errorf("Expected %v, actual %v", c.(*FeedV1).Title.Value, f[0].(*FeedV1).Title.Value)
- }
- if f[1].(*FeedV1).Title.Value != b.(*FeedV1).Title.Value {
- t.Errorf("Expected %v, actual %v", b.(*FeedV1).Title.Value, f[1].(*FeedV1).Title.Value)
- }
- if f[2].(*FeedV1).Title.Value != a.(*FeedV1).Title.Value {
- t.Errorf("Expected %v, actual %v", a.(*FeedV1).Title.Value, f[2].(*FeedV1).Title.Value)
- }
-}
-
-func getFeeds() map[string]Feed {
- m := make(map[string]Feed)
-
- fd := getFeed("test title")
- m[fd.(*FeedV1).Path] = fd
-
- return m
-}
-
-func getFeed(title string) Feed {
- gen := "test gen"
- desc := getText()
- f := FeedV1{
- Path: "https://example.com/feed.xml",
- ID: "urn:uuid:22fea9cf-095c-4634-95f5-d0b72f99944f",
- Generator: &gen,
- FeedType: ATOM,
- Title: getText(),
- Updated: ToTimestamp(time.Now()),
- Description: &desc,
- Links: getLinks(),
- Authors: getPersons(),
- Entries: getEntryList(),
- }
- f.Title.Value = title
-
- var fd Feed = &f
- return fd
-}
-
-func TestFeedType(t *testing.T) {
- e := RSS
- a := ToFeedType(e.String())
- if e != a {
- t.Errorf("Expected %d, actual %d", e, a)
- }
-
- e = ATOM
- a = ToFeedType(e.String())
- if e != a {
- t.Errorf("Expected %d, actual %d", e, a)
- }
-}
D link.go => link.go +0 -12
@@ 1,12 0,0 @@
-package barefeed
-
-// Links is a slice of Link
-type Links []Link
-
-// Link contains info needed for any link
-type Link struct {
- URL string `bare:"url"`
- LinkType string `bare:"linkType"`
- Rel string `bare:"rel"`
- Length int `bare:"length"`
-}
D link_test.go => link_test.go +0 -41
@@ 1,41 0,0 @@
-package barefeed
-
-import "testing"
-
-func testLinks(t *testing.T, expected, actual Links) {
- if len(expected) != len(actual) {
- t.Errorf("Expected %d links, actual %d links", len(expected), len(actual))
- }
- for j := 0; j < len(expected); j++ {
- if expected[j].URL != actual[j].URL {
- t.Errorf("Expected %s, actual %s", expected[j].URL, actual[j].URL)
- }
- if expected[j].LinkType != actual[j].LinkType {
- t.Errorf("Expected %s, actual %s", expected[j].LinkType, actual[j].LinkType)
- }
- if expected[j].Rel != actual[j].Rel {
- t.Errorf("Expected %s, actual %s", expected[j].Rel, actual[j].Rel)
- }
- if expected[j].Length != actual[j].Length {
- t.Errorf("Expected %d, actual %d", expected[j].Length, actual[j].Length)
- }
- }
-}
-
-func getLinks() Links {
- a := Link{
- URL: "https://example.com/entry.mp3",
- LinkType: "audio/mp3",
- Rel: "enclosure",
- Length: 50416767,
- }
-
- b := Link{
- URL: "https://example.com/feed.xml",
- LinkType: "plain/html",
- Rel: "self",
- Length: 123,
- }
-
- return Links{a, b}
-}
D message.go => message.go +0 -51
@@ 1,51 0,0 @@
-package barefeed
-
-import (
- "fmt"
- "os"
- "path/filepath"
-
- "git.sr.ht/~sircmpwn/go-bare"
-)
-
-// Message is the first version of the spec
-type Message struct {
- Generator string `bare:"generator"`
- Created Timestamp `bare:"created"`
- Feeds map[string]Feed `bare:"feeds"`
- Entries map[string]Entry `bare:"entries"`
- Unread map[string]int64 `bare:"unread"`
- Favorite map[string]Timestamp `bare:"favorite"`
-}
-
-// Bytes will return the barefeed as a slice of bytes
-func (t Message) Bytes() ([]byte, error) {
- return bare.Marshal(&t)
-}
-
-// WriteFile will write barefeed to a file at the designated path
-func (t Message) WriteFile(path string) error {
- f, err := os.OpenFile(filepath.Clean(path), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
- if err != nil {
- return err
- }
-
- w := bare.NewWriter(f)
- if w == nil {
- if err := f.Close(); err != nil {
- return err
- }
-
- return fmt.Errorf("writer is nil")
- }
-
- if err := bare.MarshalWriter(w, &t); err != nil {
- if err := f.Close(); err != nil {
- return err
- }
-
- return fmt.Errorf("unable to marshal message to file: %s", err.Error())
- }
-
- return f.Close()
-}
D message_test.go => message_test.go +0 -25
@@ 1,25 0,0 @@
-package barefeed
-
-import (
- "testing"
- "time"
-)
-
-func testMessage(t *testing.T, expected, actual Message) {
- if actual.Created != expected.Created {
- t.Errorf("Expected %v, actual %v", expected.Created, actual.Created)
- }
- if actual.Generator != expected.Generator {
- t.Errorf("Expected %s, actual %s", expected.Generator, actual.Generator)
- }
- testFeeds(t, expected.Feeds, actual.Feeds)
- testEntries(t, expected.Entries, actual.Entries)
-}
-
-func getMessage() Message {
- return Message{
- Created: ToTimestamp(time.Now()),
- Generator: "generated",
- Feeds: getFeeds(),
- }
-}
D person.go => person.go +0 -11
@@ 1,11 0,0 @@
-package barefeed
-
-// Persons is a slice of Person
-type Persons []Person
-
-// Person contains info needed for authors
-type Person struct {
- Name string `bare:"name"`
- Email *string `bare:"email"`
- URI *string `bare:"uri"`
-}
D person_test.go => person_test.go +0 -44
@@ 1,44 0,0 @@
-package barefeed
-
-import "testing"
-
-func testPersons(t *testing.T, expected, actual Persons) {
- if len(expected) != len(actual) {
- t.Errorf("Expected %d authors, actual %d authors", len(expected), len(actual))
- }
- for j := 0; j < len(expected); j++ {
- if expected[j].Name != actual[j].Name {
- t.Errorf("Expected %s, actual %s", expected[j].Name, actual[j].Name)
- }
- if actual[j].Email != expected[j].Email {
- if expected[j].Email == nil {
- t.Errorf("Expected nil, actual %s", *actual[j].Email)
- } else if actual[j].Email == nil {
- t.Errorf("Expected %s, actual nil", *expected[j].Email)
- } else if *actual[j].Email != *expected[j].Email {
- t.Errorf("Expected %s, actual %s", *expected[j].Email, *actual[j].Email)
- }
- }
- if actual[j].URI != expected[j].URI {
- if expected[j].URI == nil {
- t.Errorf("Expected nil, actual %s", *actual[j].URI)
- } else if actual[j].URI == nil {
- t.Errorf("Expected %s, actual nil", *expected[j].URI)
- } else if *actual[j].URI != *expected[j].URI {
- t.Errorf("Expected %s, actual %s", *expected[j].URI, *actual[j].URI)
- }
- }
- }
-}
-
-func getPersons() Persons {
- email := "johndoe@example.com"
- uri := "example.com"
-
- a := Person{
- Name: "John Doe",
- Email: &email,
- URI: &uri,
- }
- return Persons{a}
-}
A schema.go => schema.go +128 -0
@@ 0,0 1,128 @@
+package barefeed
+
+// Code generated by go-bare/cmd/gen, DO NOT EDIT.
+
+import (
+ "git.sr.ht/~sircmpwn/go-bare"
+)
+
+type Time string
+
+func (t *Time) Decode(data []byte) error {
+ return bare.Unmarshal(data, t)
+}
+
+func (t *Time) Encode() ([]byte, error) {
+ return bare.Marshal(t)
+}
+
+type Text struct {
+ TextType string `bare:"textType"`
+ Value string `bare:"value"`
+}
+
+func (t *Text) Decode(data []byte) error {
+ return bare.Unmarshal(data, t)
+}
+
+func (t *Text) Encode() ([]byte, error) {
+ return bare.Marshal(t)
+}
+
+type Link struct {
+ Href string `bare:"href"`
+ Rel *string `bare:"rel"`
+ LinkType *string `bare:"linkType"`
+ Lang *string `bare:"lang"`
+ Title *string `bare:"title"`
+ Length *string `bare:"length"`
+}
+
+func (t *Link) Decode(data []byte) error {
+ return bare.Unmarshal(data, t)
+}
+
+func (t *Link) Encode() ([]byte, error) {
+ return bare.Marshal(t)
+}
+
+type Person struct {
+ Name string `bare:"name"`
+ Email *string `bare:"email"`
+ Uri *string `bare:"uri"`
+}
+
+func (t *Person) Decode(data []byte) error {
+ return bare.Unmarshal(data, t)
+}
+
+func (t *Person) Encode() ([]byte, error) {
+ return bare.Marshal(t)
+}
+
+type Entry struct {
+ FeedID string `bare:"feedID"`
+ EntryID string `bare:"entryID"`
+ Read bool `bare:"read"`
+ Liked bool `bare:"liked"`
+ Position uint `bare:"position"`
+ Title Text `bare:"title"`
+ Content Text `bare:"content"`
+ Updated Time `bare:"updated"`
+ Published *Time `bare:"published"`
+ Authors []Person `bare:"authors"`
+ Links []Link `bare:"links"`
+}
+
+func (t *Entry) Decode(data []byte) error {
+ return bare.Unmarshal(data, t)
+}
+
+func (t *Entry) Encode() ([]byte, error) {
+ return bare.Marshal(t)
+}
+
+type Feed struct {
+ FeedPath string `bare:"feedPath"`
+ FeedID string `bare:"feedID"`
+ Title Text `bare:"title"`
+ Updated Time `bare:"updated"`
+ Authors []Person `bare:"authors"`
+ Links []Link `bare:"links"`
+}
+
+func (t *Feed) Decode(data []byte) error {
+ return bare.Unmarshal(data, t)
+}
+
+func (t *Feed) Encode() ([]byte, error) {
+ return bare.Marshal(t)
+}
+
+type Bin struct {
+ Generator string `bare:"generator"`
+ Content Content `bare:"content"`
+}
+
+func (t *Bin) Decode(data []byte) error {
+ return bare.Unmarshal(data, t)
+}
+
+func (t *Bin) Encode() ([]byte, error) {
+ return bare.Marshal(t)
+}
+
+type Content interface {
+ bare.Union
+}
+
+func (_ Feed) IsUnion() {}
+
+func (_ Entry) IsUnion() {}
+
+func init() {
+ bare.RegisterUnion((*Content)(nil)).
+ Member(*new(Feed), 0).
+ Member(*new(Entry), 1)
+
+}
D test-files/test.barefeed => test-files/test.barefeed +0 -0
D text.go => text.go +0 -43
@@ 1,43 0,0 @@
-package barefeed
-
-// TextType is an enumerated type for Text
-type TextType uint
-
-const (
- // TEXT denotes the text as plain text
- TEXT TextType = iota
- // HTML denotes the text as entity escaped html
- HTML
- // XHTML denotes the text as inline xhtml, wrapped in a div element
- XHTML
-)
-
-// Text contains info needed for any text
-type Text struct {
- Value string `bare:"value"`
- TextType TextType `bare:"textType"`
-}
-
-// String converts the enumerated value to a string. "TEXT" is default.
-func (t TextType) String() string {
- switch t {
- case HTML:
- return "html"
- case XHTML:
- return "xhtml"
- default:
- return "text"
- }
-}
-
-// ToTextType converts a string to the enumerated value. TEXT is default.
-func ToTextType(t string) TextType {
- switch t {
- case "html":
- return HTML
- case "xhtml":
- return XHTML
- default:
- return TEXT
- }
-}
D text_test.go => text_test.go +0 -41
@@ 1,41 0,0 @@
-package barefeed
-
-import (
- "testing"
-)
-
-func testText(t *testing.T, expected, actual Text) {
- if expected.TextType != actual.TextType {
- t.Errorf("Expected %d, actual %d", expected.TextType, actual.TextType)
- }
- if expected.Value != actual.Value {
- t.Errorf("Expected %s, actual %s", expected.Value, actual.Value)
- }
-}
-
-func getText() Text {
- return Text{
- TextType: HTML,
- Value: "<p>This is a test</p>",
- }
-}
-
-func TestTextType(t *testing.T) {
- e := TEXT
- a := ToTextType(e.String())
- if e != a {
- t.Errorf("Expected %d, actual %d", e, a)
- }
-
- e = HTML
- a = ToTextType(e.String())
- if e != a {
- t.Errorf("Expected %d, actual %d", e, a)
- }
-
- e = XHTML
- a = ToTextType(e.String())
- if e != a {
- t.Errorf("Expected %d, actual %d", e, a)
- }
-}
D time.go => time.go +0 -16
@@ 1,16 0,0 @@
-package barefeed
-
-import "time"
-
-// Timestamp type to hold the UTC Unix value of time
-type Timestamp int64
-
-// Time will convert the timestamp to time
-func (t Timestamp) Time() time.Time {
- return time.Unix(int64(t), 0).UTC()
-}
-
-// ToTimestamp will convert the time to timestamp
-func ToTimestamp(t time.Time) Timestamp {
- return Timestamp(t.UTC().Unix())
-}
D time_test.go => time_test.go +0 -14
@@ 1,14 0,0 @@
-package barefeed
-
-import (
- "testing"
- "time"
-)
-
-func TestTimestamp(t *testing.T) {
- ts := Timestamp(time.Now().UTC().Unix())
- tm := ts.Time()
- if ts != ToTimestamp(tm) {
- t.Errorf("Expected %v, actual %v", ts, tm)
- }
-}