mirror of
https://codeberg.org/librarian/feeds
synced 2024-12-06 19:16:24 +01:00
initial commit, general organization and likely broken atom implementation
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
Syndication (feed) generator library for golang.
|
||||
|
||||
Usage:
|
||||
|
||||
```golang
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package syndicate
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Generates Atom feed as XML
|
||||
|
||||
const ns = "http://www.w3.org/2005/Atom"
|
||||
|
||||
type atomSummary struct {
|
||||
S string `xml:",chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
|
||||
type atomEntry struct {
|
||||
XMLName xml.Name `xml:"entry"`
|
||||
Title string `xml:"title"`
|
||||
Link *atomLink
|
||||
Updated string `xml:"updated"`
|
||||
Id string `xml:"id"`
|
||||
Summary *atomSummary `xml:"summary"`
|
||||
}
|
||||
|
||||
type atomLink struct {
|
||||
XMLName xml.Name `xml:"link"`
|
||||
Href string `xml:"href,attr"`
|
||||
Rel string `xml:"rel,attr"`
|
||||
}
|
||||
|
||||
type atomFeed struct {
|
||||
XMLName xml.Name `xml:"feed"`
|
||||
Ns string `xml:"xmlns,attr"`
|
||||
Title string `xml:"title"`
|
||||
Link *atomLink
|
||||
Id string `xml:"id"`
|
||||
Updated string `xml:"updated"`
|
||||
Entries []*atomEntry
|
||||
}
|
||||
|
||||
type Atom struct {
|
||||
*Feed
|
||||
}
|
||||
|
||||
func newAtomEntry(i *Item) *atomEntry {
|
||||
id := i.Id
|
||||
// assume the description is html
|
||||
s := &atomSummary{i.Description, "html"}
|
||||
|
||||
// try to get a single timestamp, since we only have one in atom
|
||||
ts := i.Updated
|
||||
if ts.IsZero() {
|
||||
ts = i.Created
|
||||
}
|
||||
// <id>tag:blog.kowalczyk.info,2012-09-11:/item/1.html</id>
|
||||
if len(id) == 0 {
|
||||
// if there's no id set, try to create one, either from data or just a uuid
|
||||
if len(i.Link.Href) > 0 && (!i.Created.IsZero() || !i.Updated.IsZero()) {
|
||||
dateStr := ts.Format("2006-01-02")
|
||||
host, path := i.Link.Href, "/invalid.html"
|
||||
if url, err := url.Parse(i.Link.Href); err == nil {
|
||||
host, path = url.Host, url.Path
|
||||
}
|
||||
id = fmt.Sprintf("tag:%s,%s:%s", host, dateStr, path)
|
||||
} else {
|
||||
id = "urn:uuid:" + NewUUID().String()
|
||||
}
|
||||
}
|
||||
x := &atomEntry{
|
||||
Title: i.Title,
|
||||
Link: &atomLink{Href: i.Link.Href, Rel: i.Link.Rel},
|
||||
Summary: s,
|
||||
Id: id,
|
||||
Updated: i.Updated.Format(time.RFC3339)}
|
||||
return x
|
||||
}
|
||||
|
||||
func (a *Atom) FeedXml() interface{} {
|
||||
feed := &atomFeed{
|
||||
Ns: ns,
|
||||
Title: a.Title,
|
||||
Link: &atomLink{Href: a.Link.Href, Rel: a.Link.Rel},
|
||||
Id: a.Link.Href,
|
||||
Updated: a.Updated.Format(time.RFC3339)}
|
||||
for _, e := range a.Items {
|
||||
feed.Entries = append(feed.Entries, newAtomEntry(e))
|
||||
}
|
||||
|
||||
return feed
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package syndicate
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Link struct {
|
||||
Href string
|
||||
Rel string
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
Name string
|
||||
Email string
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Title string
|
||||
Link *Link
|
||||
Author *Author
|
||||
Description string // used as description in rss, summary in atom
|
||||
Id string // used as guid in rss, id in atom
|
||||
Updated time.Time
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
type Feed struct {
|
||||
Title string
|
||||
Link *Link
|
||||
Author *Author
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
Description string
|
||||
Subtitle string
|
||||
Id string
|
||||
Items []*Item
|
||||
}
|
||||
|
||||
func (f *Feed) Add(item *Item) {
|
||||
f.Items = append(f.Items, item)
|
||||
}
|
||||
|
||||
type XmlFeed interface {
|
||||
FeedXml() interface{}
|
||||
}
|
||||
|
||||
func ToXML(feed XmlFeed) (string, error) {
|
||||
x := feed.FeedXml()
|
||||
data, err := xml.MarshalIndent(x, " ", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s := xml.Header[:len(xml.Header)-1] + string(data)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (f *Feed) ToAtom() (string, error) {
|
||||
a := &Atom{f}
|
||||
return ToXML(a)
|
||||
}
|
||||
|
||||
func (f *Feed) ToRss(version ...float64) (string, error) {
|
||||
vers := 2.0
|
||||
if len(version) > 0 {
|
||||
vers = version[0]
|
||||
}
|
||||
/*
|
||||
r := &RssFeed{f}
|
||||
return ToXML(r)
|
||||
*/
|
||||
return fmt.Sprint(vers), nil
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package syndicate
|
||||
@@ -0,0 +1,26 @@
|
||||
package syndicate
|
||||
|
||||
// relevant bits from https://github.com/abneptis/GoUUID/blob/master/uuid.go
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type UUID [16]byte
|
||||
|
||||
func NewUUID() *UUID {
|
||||
u := &UUID{}
|
||||
_, err := rand.Read(u[:16])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
u[8] = (u[8] | 0x80) & 0xBf
|
||||
u[6] = (u[6] | 0x40) & 0x4f
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *UUID) String() string {
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:])
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package syndicate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
s := NewUUID()
|
||||
s2 := NewUUID()
|
||||
if len(s) != 16 {
|
||||
t.Errorf("Expecting len of 16, got %d\n", len(s))
|
||||
}
|
||||
if len(s.String()) != 36 {
|
||||
t.Errorf("Expecting uuid hex string len of 36, got %d\n", len(s.String()))
|
||||
}
|
||||
if s == s2 {
|
||||
t.Errorf("Expecting different UUIDs to be different, but they are the same.\n")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user