diff options
-rw-r--r-- | cmd/blog/blog.go | 93 | ||||
-rw-r--r-- | pkg/atom/atom.go | 57 | ||||
-rw-r--r-- | template/root.tmpl | 3 |
3 files changed, 149 insertions, 4 deletions
diff --git a/cmd/blog/blog.go b/cmd/blog/blog.go index cddb5a2..ee3491d 100644 --- a/cmd/blog/blog.go +++ b/cmd/blog/blog.go @@ -8,6 +8,7 @@ package main import ( "bytes" + "encoding/xml" "html/template" "log" "net/http" @@ -16,14 +17,18 @@ import ( "path/filepath" "sort" "strings" + "time" + "code.google.com/p/go.blog/pkg/atom" _ "code.google.com/p/go.talks/pkg/playground" "code.google.com/p/go.talks/pkg/present" ) const ( - baseURL = "http://blog.golang.org" - homeArticles = 5 // number of articles to display on the home page + hostname = "blog.golang.org" + baseURL = "http://" + hostname + homeArticles = 5 // number of articles to display on the home page + feedArticles = 10 // number of articles to include in Atom feed ) // Doc represents an article, adorned with presentation data: @@ -47,7 +52,8 @@ type Server struct { template struct { home, index, article, doc *template.Template } - content http.Handler + atomFeed []byte // pre-rendered Atom feed + content http.Handler } // NewServer constructs a new Server, serving articles from the specified @@ -90,6 +96,11 @@ func NewServer(pathPrefix, contentPath, templatePath string) (*Server, error) { return nil, err } + err = s.renderAtomFeed() + if err != nil { + return nil, err + } + // Set up content file server. s.content = http.StripPrefix(pathPrefix, http.FileServer(http.Dir(contentPath))) @@ -226,6 +237,78 @@ func (s *Server) loadDocs(root string) error { return nil } +// renderAtomFeed generates an XML Atom feed and stores it in the Server's +// atomFeed field. +func (s *Server) renderAtomFeed() error { + var updated time.Time + if len(s.docs) > 1 { + updated = s.docs[0].Time + } + feed := atom.Feed{ + Title: "The Go Programming Language Blog", + ID: "tag:" + hostname + ",2013:" + hostname, + Updated: atom.Time(updated), + Link: []atom.Link{{ + Rel: "self", + Href: baseURL + path.Join(s.pathPrefix, "/feed.atom"), + }}, + } + for i, doc := range s.docs { + if i >= feedArticles { + break + } + e := &atom.Entry{ + Title: doc.Title, + ID: feed.ID + doc.Path, + Link: []atom.Link{{ + Rel: "alternate", + Href: baseURL + doc.Path, + }}, + Published: atom.Time(doc.Time), + Updated: atom.Time(doc.Time), + Summary: &atom.Text{ + Type: "html", + Body: summary(doc), + }, + Content: &atom.Text{ + Type: "html", + Body: string(doc.HTML), + }, + Author: &atom.Person{ + Name: authors(doc.Authors), + }, + } + feed.Entry = append(feed.Entry, e) + } + data, err := xml.Marshal(&feed) + if err != nil { + return err + } + s.atomFeed = data + return nil +} + +// summary returns the first paragraph of text from the provided Doc. +func summary(d *Doc) string { + if len(d.Sections) == 0 { + return "" + } + for _, elem := range d.Sections[0].Elem { + text, ok := elem.(present.Text) + if !ok || text.Pre { + // skip everything but non-text elements + continue + } + var buf bytes.Buffer + for _, s := range text.Lines { + buf.WriteString(string(present.Style(s))) + buf.WriteByte('\n') + } + return buf.String() + } + return "" +} + // rootData encapsulates data destined for the root template. type rootData struct { Doc *Doc @@ -253,6 +336,10 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { case "index": d.Data = s.docs t = s.template.index + case "feed.atom": + w.Header().Set("Content-type", "application/atom+xml") + w.Write(s.atomFeed) + return default: doc, ok := s.docPaths[p] if !ok { diff --git a/pkg/atom/atom.go b/pkg/atom/atom.go new file mode 100644 index 0000000..bc114dd --- /dev/null +++ b/pkg/atom/atom.go @@ -0,0 +1,57 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Adapted from encoding/xml/read_test.go. + +// Package atom defines XML data structures for an Atom feed. +package atom + +import ( + "encoding/xml" + "time" +) + +type Feed struct { + XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"` + Title string `xml:"title"` + ID string `xml:"id"` + Link []Link `xml:"link"` + Updated TimeStr `xml:"updated"` + Author *Person `xml:"author"` + Entry []*Entry `xml:"entry"` +} + +type Entry struct { + Title string `xml:"title"` + ID string `xml:"id"` + Link []Link `xml:"link"` + Published TimeStr `xml:"published"` + Updated TimeStr `xml:"updated"` + Author *Person `xml:"author"` + Summary *Text `xml:"summary"` + Content *Text `xml:"content"` +} + +type Link struct { + Rel string `xml:"rel,attr"` + Href string `xml:"href,attr"` +} + +type Person struct { + Name string `xml:"name"` + URI string `xml:"uri,omitempty"` + Email string `xml:"email,omitempty"` + InnerXML string `xml:",innerxml"` +} + +type Text struct { + Type string `xml:"type,attr"` + Body string `xml:",chardata"` +} + +type TimeStr string + +func Time(t time.Time) TimeStr { + return TimeStr(t.Format("2006-01-02T15:04:05-07:00")) +} diff --git a/template/root.tmpl b/template/root.tmpl index 4348690..5b2f80e 100644 --- a/template/root.tmpl +++ b/template/root.tmpl @@ -4,9 +4,10 @@ <!DOCTYPE html> <html> <head> -<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>{{template "title" .}}</title> <link type="text/css" rel="stylesheet" href="/static/style.css"> + <link rel="alternate" type="application/atom+xml" title="blog.golang.org - Atom Feed" href="http://blog.golang.org/feed.atom" /> </head> <body> |