A fast and simple blog backend.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
mediocre-blog/src/gmi/tpl.go

134 lines
2.5 KiB

package gmi
import (
"bytes"
"context"
"embed"
"fmt"
"io"
"io/fs"
"net/url"
"strconv"
"strings"
"text/template"
"git.sr.ht/~adnano/go-gemini"
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
)
//go:embed tpl
var tplFS embed.FS
type rendererGetPostsRes struct {
Posts []post.StoredPost
HasMore bool
}
type renderer struct {
url *url.URL
postStore post.Store
}
func (r renderer) GetPosts(page, count int) (rendererGetPostsRes, error) {
posts, hasMore, err := r.postStore.Get(page, count)
return rendererGetPostsRes{posts, hasMore}, err
}
func (r renderer) GetPostByID(id string) (post.StoredPost, error) {
p, err := r.postStore.GetByID(id)
if err != nil {
return post.StoredPost{}, fmt.Errorf("fetching post %q: %w", id, err)
}
return p, nil
}
func (r renderer) GetQueryValue(key, def string) string {
v := r.url.Query().Get(key)
if v == "" {
v = def
}
return v
}
func (r renderer) GetQueryIntValue(key string, def int) (int, error) {
vStr := r.GetQueryValue(key, strconv.Itoa(def))
return strconv.Atoi(vStr)
}
func (r renderer) Add(a, b int) int { return a + b }
func (a *api) tplHandler() (gemini.Handler, error) {
allTpls := template.New("")
err := fs.WalkDir(tplFS, "tpl", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
body, err := fs.ReadFile(tplFS, path)
if err != nil {
panic(err)
}
name := strings.TrimPrefix(path, "tpl/")
allTpls, err = allTpls.New(name).Parse(string(body))
if err != nil {
return fmt.Errorf("parsing %q as template: %w", path, err)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("parsing templates: %w", err)
}
return gemini.HandlerFunc(func(
ctx context.Context,
rw gemini.ResponseWriter,
r *gemini.Request,
) {
if strings.HasSuffix(r.URL.Path, "/") {
r.URL.Path += "index.gmi"
}
tplPath := strings.TrimPrefix(r.URL.Path, "/")
ctx = mctx.Annotate(ctx,
"url", r.URL,
"tplPath", tplPath,
)
tpl := allTpls.Lookup(tplPath)
if tpl == nil {
a.params.Logger.WarnString(ctx, "page not found")
rw.WriteHeader(gemini.StatusNotFound, "Page not found, sorry!")
return
}
buf := new(bytes.Buffer)
err := tpl.Execute(buf, renderer{
url: r.URL,
postStore: a.params.PostStore,
})
if err != nil {
a.params.Logger.Error(ctx, "rendering error", err)
rw.WriteHeader(gemini.StatusTemporaryFailure, err.Error())
return
}
io.Copy(rw, buf)
}), nil
}