summaryrefslogtreecommitdiff
path: root/gemini/gemtext
diff options
context:
space:
mode:
authortjpcc <tjp@ctrl-c.club>2023-08-25 14:52:36 -0600
committertjpcc <tjp@ctrl-c.club>2023-08-26 09:26:23 -0600
commit4c4dba9ba1e91ab44fcd21c50c6df62a19cfd9e1 (patch)
tree56e925d5453a0b72edbae5f3f55657d353daab0f /gemini/gemtext
parentff57f73c72dc75cc19e015a1b6e98c6203511c44 (diff)
gemtext -> atom converter
* add GemsubToAtom converter function * add Server.Handler method and implementations fixes #1
Diffstat (limited to 'gemini/gemtext')
-rw-r--r--gemini/gemtext/sub.go122
-rw-r--r--gemini/gemtext/sub_test.go60
2 files changed, 182 insertions, 0 deletions
diff --git a/gemini/gemtext/sub.go b/gemini/gemtext/sub.go
new file mode 100644
index 0000000..a99bed2
--- /dev/null
+++ b/gemini/gemtext/sub.go
@@ -0,0 +1,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:]))
diff --git a/gemini/gemtext/sub_test.go b/gemini/gemtext/sub_test.go
new file mode 100644
index 0000000..8bba682
--- /dev/null
+++ b/gemini/gemtext/sub_test.go
@@ -0,0 +1,60 @@
+package gemtext
+
+import (
+ "bytes"
+ "net/url"
+ "testing"
+)
+
+func TestGemsubToAtom(t *testing.T) {
+ tests := []struct {
+ url string
+ input string
+ output string
+ }{
+ {
+ url: "gemini://sombodys.site/a/page",
+ input: `
+# This is a gemlog page
+
+
+## with a subtitle after empty lines
+
+=> ./first-post.gmi 2023-08-25 - This is my first post
+`[1:],
+ output: `
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+ <id>gemini://sombodys.site/a/page</id>
+ <link href="gemini://sombodys.site/a/page"/>
+ <title>This is a gemlog page</title>
+ <subtitle>with a subtitle after empty lines</subtitle>
+ <updated>2023-08-25T12:00:00Z</updated>
+ <entry>
+ <id>./first-post.gmi</id>
+ <link rel="alternate" href="./first-post.gmi"/>
+ <title>This is my first post</title>
+ <updated>2023-08-25T12:00:00Z</updated>
+ </entry>
+</feed>
+`[1:],
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.url, func(t *testing.T) {
+ doc, err := Parse(bytes.NewBufferString(test.input))
+ if err != nil {
+ t.Fatal(err)
+ }
+ loc, err := url.Parse(test.url)
+ if err != nil {
+ t.Fatal(err)
+ }
+ xml := GemsubToAtom(doc, *loc)
+ if xml != test.output {
+ t.Fatal("mismatched output")
+ }
+ })
+ }
+}