flounder

A simple gemini site builder
git clone git://git.alexwennerberg.com/flounder
Log | Files | Refs | README | LICENSE

gemfeed.go (2936B) - raw


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