diff options
author | tjpcc <tjp@ctrl-c.club> | 2023-08-31 20:16:16 -0600 |
---|---|---|
committer | tjpcc <tjp@ctrl-c.club> | 2023-08-31 20:16:16 -0600 |
commit | 0fe9ec0c90bfe82f637f1897e47fec07f90805ec (patch) | |
tree | c483fd2b4834a9ba281c92220bcf8adb9e13fb46 /gemini/gemtext/sub.go | |
parent | d3d5d0df7fcc353318feadb57e1775f10778d505 (diff) |
move gemtext->atom conversion into package atomconv like the other converters
Diffstat (limited to 'gemini/gemtext/sub.go')
-rw-r--r-- | gemini/gemtext/sub.go | 179 |
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:])) |