// Package feed writes an RSS 2.0 feed for a slice of posts. The feed is
// intentionally minimal -- no atom, no enclosures -- because TILs are text.
package feed
import (
"encoding/xml"
"net/url"
"os"
"strings"
"time"
"mercemay.top/src/tilstream/internal/render"
)
type channel struct {
XMLName xml.Name `xml:"channel"`
Title string `xml:"title"`
Link string `xml:"link"`
Description string `xml:"description"`
Language string `xml:"language"`
LastBuildDate string `xml:"lastBuildDate"`
Items []item `xml:"item"`
}
type item struct {
Title string `xml:"title"`
Link string `xml:"link"`
GUID guid `xml:"guid"`
PubDate string `xml:"pubDate"`
Description string `xml:"description"`
Categories []string `xml:"category,omitempty"`
}
type guid struct {
Value string `xml:",chardata"`
IsPermaLink bool `xml:"isPermaLink,attr"`
}
type rss struct {
XMLName xml.Name `xml:"rss"`
Version string `xml:"version,attr"`
Channel channel `xml:"channel"`
}
// Write renders posts as RSS 2.0 at dest. baseURL is used to build absolute
// item links; trailing slash is tolerated either way.
func Write(dest string, posts []render.Post, baseURL string) error {
base := strings.TrimSuffix(baseURL, "/")
ch := channel{
Title: "TIL",
Link: base + "/",
Description: "Today I Learned notes",
Language: "en-us",
LastBuildDate: time.Now().UTC().Format(time.RFC1123Z),
}
for _, p := range posts {
link, err := url.JoinPath(base, p.Slug+".html")
if err != nil {
return err
}
// GUID mixes slug + first category so re-tagging forces a re-fetch
// in readers that key by guid. See commit d9c4f01.
cat := ""
if len(p.Tags) > 0 {
cat = ":" + p.Tags[0]
}
ch.Items = append(ch.Items, item{
Title: p.Title,
Link: link,
GUID: guid{Value: p.Slug + cat, IsPermaLink: false},
PubDate: p.Date.UTC().Format(time.RFC1123Z),
Description: render.Summary(p.Body),
Categories: p.Tags,
})
}
doc := rss{Version: "2.0", Channel: ch}
out, err := xml.MarshalIndent(doc, "", " ")
if err != nil {
return err
}
f, err := os.Create(dest)
if err != nil {
return err
}
defer f.Close()
if _, err := f.WriteString(xml.Header); err != nil {
return err
}
_, err = f.Write(out)
return err
}