diff options
author | tjpcc <tjp@ctrl-c.club> | 2023-08-26 09:22:25 -0600 |
---|---|---|
committer | tjpcc <tjp@ctrl-c.club> | 2023-08-26 09:26:23 -0600 |
commit | 4a5dad998cf97b879e88c8dc1ce025432dfc90cb (patch) | |
tree | 4af7a00d7f54dcba3067130f0fac2d6aa06a67d1 | |
parent | 343cdecabd46e2b505f3f92c8281753df1ee0fee (diff) |
AutoAtom: middleware that supports adding .atom to any gemtext pathv1.2.0
-rw-r--r-- | gemini/gemtext/sub.go | 49 | ||||
-rw-r--r-- | gemini/gemtext/sub_test.go | 75 |
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") + } +} |