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:]))
|