accounts/feed.go (2460 bytes)
1 package main 2 3 import ( 4 "encoding/xml" 5 "io" 6 "time" 7 ) 8 9 // The rss types give the RSS 2.0 shape. encoding/xml does the escaping. 10 type rss struct { 11 XMLName xml.Name `xml:"rss"` 12 Version string `xml:"version,attr"` 13 Channel channel `xml:"channel"` 14 } 15 16 type channel struct { 17 Title string `xml:"title"` 18 Link string `xml:"link"` 19 Description string `xml:"description"` 20 Language string `xml:"language"` 21 Items []item `xml:"item"` 22 } 23 24 type item struct { 25 Title string `xml:"title"` 26 Link string `xml:"link"` 27 GUID guid `xml:"guid"` 28 PubDate string `xml:"pubDate,omitempty"` 29 Categories []string `xml:"category"` 30 Description string `xml:"description"` 31 } 32 33 type guid struct { 34 IsPermaLink string `xml:"isPermaLink,attr"` 35 Value string `xml:",chardata"` 36 } 37 38 // feedSize is the number of items in a private feed. 39 const feedSize = 20 40 41 // buildFeed makes the private feed for a user who muted the tags in muted. 42 // body returns the HTML body of a post. 43 func buildFeed(baseURL string, posts []Post, muted map[string]bool, body func(slug string) string) channel { 44 ch := channel{ 45 Title: "Recently Written", 46 Link: baseURL + "/", 47 Description: "A site full of things which I have recently written.", 48 Language: "en-us", 49 } 50 for _, p := range posts { 51 if len(ch.Items) == feedSize { 52 break 53 } 54 if !wanted(p, muted) { 55 continue 56 } 57 url := baseURL + "/" + p.Slug + ".html" 58 ch.Items = append(ch.Items, item{ 59 Title: p.Title, 60 Link: url, 61 GUID: guid{IsPermaLink: "true", Value: url}, 62 PubDate: feedDate(p.Date), 63 Categories: p.Tags, 64 Description: body(p.Slug), 65 }) 66 } 67 return ch 68 } 69 70 // feedDate gives the RFC 822 date that RSS requires, in the format that 71 // build.sh uses. A date that does not parse gives an empty string, and the 72 // item then has no pubDate. 73 func feedDate(d string) string { 74 t, err := time.Parse("2006-01-02", d) 75 if err != nil { 76 return "" 77 } 78 return t.Format(time.RFC1123Z) 79 } 80 81 // writeFeed writes ch as an RSS 2.0 document. encoding/xml escapes the 82 // text and replaces each character that XML 1.0 does not permit. 83 func writeFeed(w io.Writer, ch channel) error { 84 if _, err := io.WriteString(w, xml.Header); err != nil { 85 return err 86 } 87 enc := xml.NewEncoder(w) 88 enc.Indent("", " ") 89 if err := enc.Encode(rss{Version: "2.0", Channel: ch}); err != nil { 90 return err 91 } 92 _, err := io.WriteString(w, "\n") 93 return err 94 }