summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortjpcc <tjp@ctrl-c.club>2023-08-26 09:22:25 -0600
committertjpcc <tjp@ctrl-c.club>2023-08-26 09:26:23 -0600
commit4a5dad998cf97b879e88c8dc1ce025432dfc90cb (patch)
tree4af7a00d7f54dcba3067130f0fac2d6aa06a67d1
parent343cdecabd46e2b505f3f92c8281753df1ee0fee (diff)
AutoAtom: middleware that supports adding .atom to any gemtext pathv1.2.0
-rw-r--r--gemini/gemtext/sub.go49
-rw-r--r--gemini/gemtext/sub_test.go75
2 files changed, 124 insertions, 0 deletions
diff --git a/gemini/gemtext/sub.go b/gemini/gemtext/sub.go
index 365d41b..5030291 100644
--- a/gemini/gemtext/sub.go
+++ b/gemini/gemtext/sub.go
@@ -2,13 +2,18 @@ package gemtext
import (
"bytes"
+ "context"
"html/template"
"io"
+ "mime"
"net/url"
"regexp"
"strconv"
"strings"
"time"
+
+ "tildegit.org/tjp/sliderule/gemini"
+ "tildegit.org/tjp/sliderule/internal/types"
)
// GmisubToAtom converts a gemini document to Atom format.
@@ -28,6 +33,50 @@ func GmisubToAtom(doc Document, location url.URL, out io.Writer) error {
return nil
}
+// AutoAtom is a middleware which builds atom feeds for any gemtext pages.
+//
+// It looks for requests ending with the '.atom' extension, passes through the request
+// with the extension clipped off, then if the response is in gemtext it converts it to
+// an Atom feed according to the gmisub spec at
+// gemini://gemini.circumlunar.space/docs/companion/subscription.gmi
+var AutoAtom = types.Middleware(func(h types.Handler) types.Handler {
+ return types.HandlerFunc(func(ctx context.Context, request *types.Request) *types.Response {
+ if !strings.HasSuffix(request.Path, ".atom") {
+ return h.Handle(ctx, request)
+ }
+
+ req := *request
+ u := *request.URL
+ u.Path = strings.TrimSuffix(u.Path, ".atom")
+ req.URL = &u
+
+ response := h.Handle(ctx, &req)
+ if response.Status != gemini.StatusSuccess {
+ return response
+ }
+
+ mtype, _, err := mime.ParseMediaType(response.Meta.(string))
+ if err != nil || mtype != "text/gemini" {
+ return response
+ }
+
+ defer func() {
+ _ = response.Close()
+ }()
+
+ doc, err := Parse(response.Body)
+ if err != nil {
+ return gemini.Failure(err)
+ }
+
+ buf := &bytes.Buffer{}
+ if err := GmisubToAtom(doc, *request.URL, buf); err != nil {
+ return gemini.Failure(err)
+ }
+ return gemini.Success("application/atom+xml; charset=utf-8", buf)
+ })
+})
+
type gmiSub struct {
ID template.URL
Title string
diff --git a/gemini/gemtext/sub_test.go b/gemini/gemtext/sub_test.go
index f080705..9e0fcc8 100644
--- a/gemini/gemtext/sub_test.go
+++ b/gemini/gemtext/sub_test.go
@@ -2,8 +2,15 @@ package gemtext
import (
"bytes"
+ "context"
+ "fmt"
+ "io"
"net/url"
"testing"
+
+ "tildegit.org/tjp/sliderule"
+ "tildegit.org/tjp/sliderule/gemini"
+ "tildegit.org/tjp/sliderule/internal/types"
)
func TestGemsubToAtom(t *testing.T) {
@@ -61,3 +68,71 @@ func TestGemsubToAtom(t *testing.T) {
})
}
}
+
+func TestAutoAtom(t *testing.T) {
+ rout := &sliderule.Router{}
+
+ rout.Route("/foo.gmi", types.HandlerFunc(func(ctx context.Context, request *types.Request) *types.Response {
+ return gemini.Success("text/gemini", bytes.NewBufferString(`
+# This is my gemini page
+
+## a subtitle
+
+=> ./first-post.gmi 2023-05-17 - My first post
+=> ./second-post.gmi 2023-06-02 second-ever post
+`[1:]))
+ }))
+
+ rout.Route("/bar.gmi", types.HandlerFunc(func(ctx context.Context, request *types.Request) *types.Response {
+ return gemini.Success("text/gemini", bytes.NewBufferString(`
+# Another homepage
+
+=> ./first-post.gmi 2023-05-17 - first post
+=> ./second-post.gmi 2023-06-02 second post
+`[1:]))
+ }))
+
+ h := AutoAtom(rout.Handler())
+
+ response := h.Handle(context.Background(), &types.Request{URL: &url.URL{
+ Scheme: "gemini",
+ Host: "127.0.0.1",
+ Path: "/foo.gmi.atom",
+ }})
+ if response.Status != gemini.StatusSuccess {
+ t.Fatal("bad response code")
+ }
+
+ result, err := io.ReadAll(response.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ target := `
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+ <id>gemini://127.0.0.1/foo.gmi.atom</id>
+ <link href="gemini://127.0.0.1/foo.gmi.atom"/>
+ <title>This is my gemini page</title>
+ <subtitle>a subtitle</subtitle>
+ <updated>2023-06-02T12:00:00Z</updated>
+ <entry>
+ <id>./first-post.gmi</id>
+ <link rel="alternate" href="./first-post.gmi"/>
+ <title>My first post</title>
+ <updated>2023-05-17T12:00:00Z</updated>
+ </entry>
+ <entry>
+ <id>./second-post.gmi</id>
+ <link rel="alternate" href="./second-post.gmi"/>
+ <title>second-ever post</title>
+ <updated>2023-06-02T12:00:00Z</updated>
+ </entry>
+</feed>
+`[1:]
+ if string(result) != target {
+ fmt.Println(target)
+ fmt.Println(string(result))
+ t.Fatal("response body")
+ }
+}