summaryrefslogtreecommitdiff
path: root/gemini/gemtext/sub.go
diff options
context:
space:
mode:
Diffstat (limited to 'gemini/gemtext/sub.go')
-rw-r--r--gemini/gemtext/sub.go179
1 files changed, 0 insertions, 179 deletions
diff --git a/gemini/gemtext/sub.go b/gemini/gemtext/sub.go
deleted file mode 100644
index 269695f..0000000
--- a/gemini/gemtext/sub.go
+++ /dev/null
@@ -1,179 +0,0 @@
-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.
-//
-// It identifies feed fields and entries according to the specification at
-// gemini://gemini.circumlunar.space/docs/companion/subscription.gmi
-func GmisubToAtom(doc Document, location url.URL, out io.Writer) error {
- if _, err := out.Write([]byte(`<?xml version="1.0" encoding="utf-8"?>`)); err != nil {
- return err
- }
- if _, err := out.Write([]byte{'\n'}); err != nil {
- return err
- }
- if err := atomTmpl.Execute(out, parseGemSub(doc, &location)); err != nil {
- return err
- }
- 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 request.Scheme != "gemini" || !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
- Subtitle string
- Updated string
-
- Entries []gmiSubEntry
-}
-
-type gmiSubEntry 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) *gmiSub {
- sub := &gmiSub{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, gmiSubEntry{
- 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
-}
-
-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:]))