From 4e6bcdd4bbbefc155bdf3fada8464d1a0621af22 Mon Sep 17 00:00:00 2001 From: Jason Moiron Date: Tue, 15 Jan 2013 17:27:35 -0500 Subject: [PATCH] initial commit, general organization and likely broken atom implementation --- README.md | 8 +++++ atom.go | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++ doc.go | 5 +++ feed.go | 74 +++++++++++++++++++++++++++++++++++++++++ feed_test.go | 1 + rss.go | 1 + uuid.go | 26 +++++++++++++++ uuid_test.go | 19 +++++++++++ 8 files changed, 227 insertions(+) create mode 100644 README.md create mode 100644 atom.go create mode 100644 doc.go create mode 100644 feed.go create mode 100644 feed_test.go create mode 100644 rss.go create mode 100644 uuid.go create mode 100644 uuid_test.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..f5884f6 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +Syndication (feed) generator library for golang. + +Usage: + +```golang + +``` + diff --git a/atom.go b/atom.go new file mode 100644 index 0000000..5f7d45b --- /dev/null +++ b/atom.go @@ -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 + } + // tag:blog.kowalczyk.info,2012-09-11:/item/1.html + 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 +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..21c7aa6 --- /dev/null +++ b/doc.go @@ -0,0 +1,5 @@ +/* +Create feed output in RSS or Atom. + +*/ +package syndicate diff --git a/feed.go b/feed.go new file mode 100644 index 0000000..4a5de98 --- /dev/null +++ b/feed.go @@ -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 +} diff --git a/feed_test.go b/feed_test.go new file mode 100644 index 0000000..3f8c901 --- /dev/null +++ b/feed_test.go @@ -0,0 +1 @@ +package syndicate diff --git a/rss.go b/rss.go new file mode 100644 index 0000000..3f8c901 --- /dev/null +++ b/rss.go @@ -0,0 +1 @@ +package syndicate diff --git a/uuid.go b/uuid.go new file mode 100644 index 0000000..6e204e2 --- /dev/null +++ b/uuid.go @@ -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:]) +} diff --git a/uuid_test.go b/uuid_test.go new file mode 100644 index 0000000..b53dee4 --- /dev/null +++ b/uuid_test.go @@ -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") + } +}