Implement gemini atom feed

This commit is contained in:
Brian Picciano 2023-01-22 12:45:03 +01:00
parent 1cfdac5e8c
commit 5b5a043868
6 changed files with 109 additions and 51 deletions

View File

@ -37,8 +37,7 @@ type rendererGetPostSeriesNextPreviousRes struct {
type renderer struct { type renderer struct {
url *url.URL url *url.URL
postStore post.Store postStore post.Store
gmiPublicURL *url.URL preprocessFuncs post.PreprocessFunctions
httpPublicURL *url.URL
} }
func (r renderer) GetPosts(page, count int) (rendererGetPostsRes, error) { func (r renderer) GetPosts(page, count int) (rendererGetPostsRes, error) {
@ -91,39 +90,9 @@ func (r renderer) GetPostSeriesNextPrevious(p post.StoredPost) (rendererGetPostS
func (r renderer) PostBody(p post.StoredPost) (string, error) { func (r renderer) PostBody(p post.StoredPost) (string, error) {
preprocessFuncs := post.PreprocessFunctions{
BlogURL: func(path string) string {
return filepath.Join("/", r.gmiPublicURL.Path, path)
},
AssetURL: func(id string) string {
return filepath.Join("/assets", id)
},
PostURL: func(id string) string {
return filepath.Join("/posts", id)
},
StaticURL: func(path string) string {
httpPublicURL := *r.httpPublicURL
httpPublicURL.Path = filepath.Join(httpPublicURL.Path, "/static", path)
return httpPublicURL.String()
},
Image: func(args ...string) (string, error) {
var (
id = args[0]
descr = "Image"
)
if len(args) > 1 {
descr = args[1]
}
return fmt.Sprintf("=> %s %s", filepath.Join("/assets", id), descr), nil
},
}
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
if err := p.PreprocessBody(buf, preprocessFuncs); err != nil { if err := p.PreprocessBody(buf, r.preprocessFuncs); err != nil {
return "", fmt.Errorf("preprocessing post body: %w", err) return "", fmt.Errorf("preprocessing post body: %w", err)
} }
@ -159,8 +128,66 @@ func (r renderer) Add(a, b int) int { return a + b }
func (a *api) tplHandler() (gemini.Handler, error) { func (a *api) tplHandler() (gemini.Handler, error) {
blogURL := func(path string, abs bool) string {
path = filepath.Join(a.params.PublicURL.Path, path)
if !abs {
return path
}
u := *a.params.PublicURL
u.Path = path
return u.String()
}
preprocessFuncs := post.PreprocessFunctions{
BlogURL: func(path string) string {
return blogURL(path, false)
},
AssetURL: func(id string) string {
path := filepath.Join("assets", id)
return blogURL(path, false)
},
PostURL: func(id string) string {
path := filepath.Join("posts", id) + ".gmi"
return blogURL(path, false)
},
StaticURL: func(path string) string {
httpPublicURL := *a.params.HTTPPublicURL
httpPublicURL.Path = filepath.Join(httpPublicURL.Path, "/static", path)
return httpPublicURL.String()
},
Image: func(args ...string) (string, error) {
var (
id = args[0]
descr = "Image"
)
if len(args) > 1 {
descr = args[1]
}
path := blogURL(filepath.Join("assets", id), false)
return fmt.Sprintf("=> %s %s", path, descr), nil
},
}
allTpls := template.New("") allTpls := template.New("")
allTpls.Funcs(preprocessFuncs.ToFuncsMap())
allTpls.Funcs(template.FuncMap{
"BlogURLAbs": func(path string) string {
return blogURL(path, true)
},
"PostURLAbs": func(id string) string {
path := filepath.Join("posts", id) + ".gmi"
return blogURL(path, true)
},
})
err := fs.WalkDir(tplFS, "tpl", func(path string, d fs.DirEntry, err error) error { err := fs.WalkDir(tplFS, "tpl", func(path string, d fs.DirEntry, err error) error {
if err != nil { if err != nil {
@ -221,8 +248,7 @@ func (a *api) tplHandler() (gemini.Handler, error) {
err := tpl.Execute(buf, renderer{ err := tpl.Execute(buf, renderer{
url: r.URL, url: r.URL,
postStore: a.params.PostStore, postStore: a.params.PostStore,
gmiPublicURL: a.params.PublicURL, preprocessFuncs: preprocessFuncs,
httpPublicURL: a.params.HTTPPublicURL,
}) })
if err != nil { if err != nil {

28
src/gmi/tpl/feed.xml Normal file
View File

@ -0,0 +1,28 @@
{{ $getPostsRes := .GetPosts 0 15 -}}
{{ $posts := $getPostsRes.Posts -}}
<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">
<title>mediocregopher's lil web corner</title>
<id>{{ BlogURLAbs "/" }}</id>
{{ if gt (len $posts) 0 -}}
<updated>{{ (index $posts 0).PublishedAt.Format "2006-01-02T15:04:05Z07:00" }}</updated>
{{ end -}}
<link href="{{ BlogURLAbs "/" }}"></link>
<author>
<name>mediocregopher</name>
</author>
{{ range $posts -}}
<entry>
<title>{{ .Title }}</title>
<updated>{{ .PublishedAt.Format "2006-01-02T15:04:05Z07:00" }}</updated>
<id>{{ PostURLAbs .ID }}</id>
<link href="{{ PostURLAbs .ID }}" rel="alternate"></link>
{{ if .Description -}}
<summary type="html">{{ .Description }}</summary>
{{ end -}}
<author>
<name>mediocregopher</name>
</author>
</entry>
{{ end -}}
</feed>

View File

@ -9,7 +9,7 @@
{{ end -}} {{ end -}}
{{ range $getPostsRes.Posts -}} {{ range $getPostsRes.Posts -}}
=> /posts/{{ .ID }}.gmi {{ .PublishedAt.Format "2006-01-02" }} - {{ .Title }} => {{ PostURL .ID }} {{ .PublishedAt.Format "2006-01-02" }} - {{ .Title }}
{{ end -}} {{ end -}}

View File

@ -39,9 +39,8 @@ func (a *api) renderFeedHandler() http.Handler {
publicURL := a.params.PublicURL.String() publicURL := a.params.PublicURL.String()
feed := feeds.Feed{ feed := feeds.Feed{
Title: "Mediocre Blog", Title: "mediocregopher's lil web corner",
Link: &feeds.Link{Href: publicURL + "/"}, Link: &feeds.Link{Href: publicURL + "/"},
Description: "A mix of tech, art, travel, and who knows what else.",
Author: author, Author: author,
} }

View File

@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<title>mediocregopher's lil web corner</title>
<style>{{ StaticInlineCSS "new.css" }}</style> <style>{{ StaticInlineCSS "new.css" }}</style>
<style>{{ StaticInlineCSS "mediocre.css" }}</style> <style>{{ StaticInlineCSS "mediocre.css" }}</style>

View File

@ -37,6 +37,16 @@ type PreprocessFunctions struct {
Image func(args ...string) (string, error) Image func(args ...string) (string, error)
} }
func (funcs PreprocessFunctions) ToFuncsMap() template.FuncMap {
return template.FuncMap{
"BlogURL": funcs.BlogURL,
"AssetURL": funcs.AssetURL,
"PostURL": funcs.PostURL,
"StaticURL": funcs.StaticURL,
"Image": funcs.Image,
}
}
// PreprocessBody interprets the Post's Body as a text template which may use // PreprocessBody interprets the Post's Body as a text template which may use
// any of the functions found in PreprocessFunctions (all must be set). It // any of the functions found in PreprocessFunctions (all must be set). It
// executes the template and writes the result to the given writer. // executes the template and writes the result to the given writer.
@ -44,13 +54,7 @@ func (p Post) PreprocessBody(into io.Writer, funcs PreprocessFunctions) error {
tpl := template.New("") tpl := template.New("")
tpl.Funcs(template.FuncMap{ tpl.Funcs(funcs.ToFuncsMap())
"BlogURL": funcs.BlogURL,
"AssetURL": funcs.AssetURL,
"PostURL": funcs.PostURL,
"StaticURL": funcs.StaticURL,
"Image": funcs.Image,
})
tpl, err := tpl.Parse(p.Body) tpl, err := tpl.Parse(p.Body)