package app import ( "bytes" "crypto/sha1" "encoding/xml" "fmt" "io" "net/http" "time" ) type AtomFeed struct { XMLName xml.Name `xml:"feed"` XMLNS string `xml:"xmlns,attr"` Title string `xml:"title"` ID string `xml:"id"` Updated string `xml:"updated"` Subtitle string `xml:"subtitle,omitempty"` Entries []AtomEntry `xml:"entry"` } type AtomEntry struct { Title string `xml:"title"` ID string `xml:"id"` Updated string `xml:"updated"` Content AtomContent `xml:"content"` } type AtomContent struct { XMLName xml.Name `xml:"content"` Type string `xml:"type,attr,omitempty"` Value string `xml:",chardata"` } type FeedEntry struct { Title string ID string Content string ContentType string // "text" or "html", defaults to "text" Updated time.Time } type FeedBuilder struct{ f AtomFeed } func NewFeed(title, id string) *FeedBuilder { return &FeedBuilder{f: AtomFeed{ XMLNS: "http://www.w3.org/2005/Atom", Title: title, ID: id, Updated: time.Now().Format(time.RFC3339), }} } func (f *FeedBuilder) WithSubtitle(subtitle string) *FeedBuilder { f.f.Subtitle = subtitle return f } func (f *FeedBuilder) WithUpdated(updated time.Time) *FeedBuilder { if !updated.IsZero() { f.f.Updated = updated.Format(time.RFC3339) } return f } func (f *FeedBuilder) Add(entry FeedEntry) *FeedBuilder { if entry.Updated.IsZero() { entry.Updated = time.Now() } if entry.ID == "" { hash := sha1.Sum(fmt.Appendf(nil, "%s|%s|%s", entry.Title, entry.Content, entry.Updated.Format(time.RFC3339Nano))) entry.ID = fmt.Sprintf("urn:sha1:%x", hash) } contentType := entry.ContentType if contentType == "" { contentType = "text" } f.f.Entries = append(f.f.Entries, AtomEntry{ Title: entry.Title, ID: entry.ID, Updated: entry.Updated.Format(time.RFC3339), Content: AtomContent{ Type: contentType, Value: entry.Content, }, }) feedUpdated, err := time.Parse(time.RFC3339, f.f.Updated) if err != nil || entry.Updated.After(feedUpdated) { f.f.Updated = entry.Updated.Format(time.RFC3339) } return f } func (f *FeedBuilder) SetUpdated(updated time.Time) *FeedBuilder { if !updated.IsZero() { f.f.Updated = updated.Format(time.RFC3339) } return f } func (f *FeedBuilder) WriteTo(w io.Writer) error { enc := xml.NewEncoder(w) enc.Indent("", " ") return enc.Encode(f.f) } func (f *FeedBuilder) Bytes() ([]byte, error) { var buf bytes.Buffer if err := f.WriteTo(&buf); err != nil { return nil, err } return buf.Bytes(), nil } func (f *FeedBuilder) Render(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/atom+xml; charset=utf-8") return f.WriteTo(w) }