summaryrefslogtreecommitdiff
path: root/gemini/gemtext/sub.go
blob: a99bed21a17d6d1e5f7c3a316f092d28ec44011d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package gemtext

import (
	"bytes"
	"html/template"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type gemSub struct {
	ID       template.URL
	Title    string
	Subtitle string
	Updated  string

	Entries []gemSubEntry
}

type gemSubEntry struct {
	ID      template.URL
	Updated string
	Title   string
}

var linkElemRE = regexp.MustCompile(`(\d{4})-([0-1]\d)-([0-3]\d)`)

func parseGemSub(doc Document, location *url.URL) *gemSub {
	sub := &gemSub{ID: template.URL(location.String())}
	updated := time.Time{}

	for i, line := range doc {
		switch line.Type() {
		case LineTypeHeading1:
			if sub.Title != "" {
				continue
			}

			sub.Title = line.(HeadingLine).Body()

			for { // skip any empty lines
				i += 1
				if i >= len(doc) || strings.TrimPrefix(doc[i].String(), "\r") != "\n" {
					break
				}
			}
			if i < len(doc) && doc[i].Type() == LineTypeHeading2 {
				sub.Subtitle = doc[i].(HeadingLine).Body()
			}
		case LineTypeLink:
			label := line.(LinkLine).Label()
			if len(label) < 10 {
				continue
			}
			match := linkElemRE.FindStringSubmatch(label[:10])
			if match == nil {
				continue
			}

			year, err := strconv.Atoi(match[1])
			if err != nil {
				continue
			}
			month, err := strconv.Atoi(match[2])
			if err != nil || month > 12 {
				continue
			}
			day, err := strconv.Atoi(match[3])
			if err != nil || day > 31 {
				continue
			}

			entryUpdated := time.Date(year, time.Month(month), day, 12, 0, 0, 0, time.UTC)
			entryTitle := strings.TrimLeft(strings.TrimPrefix(strings.TrimLeft(label[10:], " \t"), "-"), " \t")

			sub.Entries = append(sub.Entries, gemSubEntry{
				ID:      template.URL(line.(LinkLine).URL()),
				Updated: entryUpdated.Format(time.RFC3339),
				Title:   entryTitle,
			})

			if entryUpdated.After(updated) {
				updated = entryUpdated
				sub.Updated = updated.Format(time.RFC3339)
			}
		}
	}

	return sub
}

func GemsubToAtom(doc Document, location url.URL) string {
	buf := &bytes.Buffer{}
	if err := atomTmpl.Execute(buf, parseGemSub(doc, &location)); err != nil {
		panic(err)
	}
	return `<?xml version="1.0" encoding="utf-8"?>` + "\n" + buf.String()
}



var atomTmpl = template.Must(template.New("atom").Parse(`
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>{{.ID}}</id>
  <link href="{{.ID}}"/>
  <title>{{.Title}}</title>
  {{- if .Subtitle }}
  <subtitle>{{.Subtitle}}</subtitle>
  {{- end }}
  <updated>{{.Updated}}</updated>
{{- range .Entries }}
  <entry>
    <id>{{.ID}}</id>
    <link rel="alternate" href="{{.ID}}"/>
    <title>{{.Title}}</title>
    <updated>{{.Updated}}</updated>
  </entry>
{{- end }}
</feed>
`[1:]))