all repos — flounder @ 42f4c5b308770026b34f5012bf5115c5e1b01046

A small site builder for the Gemini protocol

gemfeed.go (view raw)

  1// Parses Gemfeed according to the companion spec:
  2// gemini://gemini.circumlunar.space/docs/companion/subscription.gmi
  3package main
  4
  5import (
  6	"bufio"
  7	"github.com/gorilla/feeds"
  8	"io/ioutil"
  9	"net/url"
 10	"os"
 11	"path"
 12	"path/filepath"
 13	"sort"
 14	"strings"
 15	"time"
 16)
 17
 18type Gemfeed struct {
 19	Title   string
 20	Creator string
 21	Url     *url.URL
 22	Entries []FeedEntry
 23}
 24
 25func (gf *Gemfeed) toAtomFeed() string {
 26	feed := feeds.Feed{
 27		Title:  gf.Title,
 28		Author: &feeds.Author{Name: gf.Creator},
 29		Link:   &feeds.Link{Href: gf.Url.String()},
 30	}
 31	feed.Items = []*feeds.Item{}
 32	for _, fe := range gf.Entries {
 33		feed.Items = append(feed.Items, &feeds.Item{
 34			Title:   fe.Title,
 35			Link:    &feeds.Link{Href: fe.Url.String()}, // Rel=alternate?
 36			Created: fe.Date,                            // Updated not created?
 37			Content: fe.Content,
 38		})
 39	}
 40	res, _ := feed.ToAtom()
 41	return res
 42}
 43
 44type FeedEntry struct {
 45	Title      string
 46	Url        *url.URL
 47	Date       time.Time
 48	DateString string
 49	Feed       *Gemfeed
 50	File       string // TODO refactor
 51	Content    string
 52}
 53
 54func urlFromPath(fullPath string) url.URL {
 55	creator := getCreator(fullPath)
 56	baseUrl := url.URL{}
 57	baseUrl.Host = creator + "." + c.Host
 58	baseUrl.Path = getLocalPath(fullPath)
 59	return baseUrl
 60}
 61
 62// Non-standard extension
 63// Requires yyyy-mm-dd formatted files
 64func generateFeedFromUser(user string) *Gemfeed {
 65	gemlogFolderPath := path.Join(c.FilesDirectory, user, GemlogFolder)
 66	// NOTE: assumes sanitized input
 67	u := urlFromPath(gemlogFolderPath)
 68	feed := Gemfeed{
 69		Title:   strings.Title(user) + "'s Gemlog",
 70		Creator: user,
 71		Url:     &u,
 72	}
 73	err := filepath.Walk(gemlogFolderPath, func(thepath string, info os.FileInfo, err error) error {
 74		base := path.Base(thepath)
 75		if len(base) >= 10 {
 76			entry := FeedEntry{}
 77			date, err := time.Parse("2006-01-02", base[:10])
 78			if err != nil {
 79				return nil
 80			}
 81			entry.Date = date
 82			entry.DateString = base[:10]
 83			entry.Feed = &feed
 84			f, err := os.Open(thepath)
 85			if err != nil {
 86				return nil
 87			}
 88			defer f.Close()
 89			scanner := bufio.NewScanner(f)
 90			for scanner.Scan() {
 91				// skip blank lines
 92				if scanner.Text() == "" {
 93					continue
 94				}
 95				line := scanner.Text()
 96				if strings.HasPrefix(line, "#") {
 97					entry.Title = strings.Trim(line, "# \t")
 98				} else {
 99					var title string
100					if len(line) > 50 {
101						title = line[:50]
102					} else {
103						title = line
104					}
105					entry.Title = "[" + title + "...]"
106				}
107				break
108			}
109			content, err := ioutil.ReadFile(thepath)
110			if err != nil {
111				return nil
112			}
113			entry.Content = string(content)
114			entry.File = getLocalPath(thepath)
115			u := urlFromPath(thepath)
116			entry.Url = &u
117			feed.Entries = append(feed.Entries, entry)
118		}
119		return nil
120	})
121	if err != nil {
122		return nil
123	}
124	// Reverse chronological sort
125	sort.Slice(feed.Entries, func(i, j int) bool {
126		return feed.Entries[i].Date.After(feed.Entries[j].Date)
127	})
128	return &feed
129}