package gmi import ( "bytes" "context" "embed" "errors" "fmt" "io" "io/fs" "mime" "path" "strings" "text/template" "dev.mediocregopher.com/mediocre-blog.git/src/post" "dev.mediocregopher.com/mediocre-blog.git/src/render" "dev.mediocregopher.com/mediocre-go-lib.git/mctx" "git.sr.ht/~adnano/go-gemini" ) type ctxKey string const ( ctxKeyTplPath ctxKey = "tplPath" ) func withTplPath(ctx context.Context, path string) context.Context { return context.WithValue(ctx, ctxKeyTplPath, path) } //go:embed tpl var tplFS embed.FS func (a *api) tplHandler() (gemini.Handler, error) { var ( postPreprocessFuncs = postPreprocessFuncs{a.urlBuilder} 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, ) { tplPath, _ := ctx.Value(ctxKeyTplPath).(string) if tplPath == "" { tplPath = r.URL.Path } tplPath = strings.TrimPrefix(tplPath, "/") mimeType := mime.TypeByExtension(path.Ext(tplPath)) ctx = mctx.Annotate(ctx, "url", r.URL, "tplPath", tplPath, "mimeType", mimeType, ) tpl := allTpls.Lookup(tplPath) if tpl == nil { rw.WriteHeader(gemini.StatusNotFound, "Page not found, sorry!") return } if mimeType != "" { rw.SetMediaType(mimeType) } buf := new(bytes.Buffer) err := tpl.Execute(buf, render.NewMethods( ctx, r.URL, a.urlBuilder, a.params.HTTPGeminiGatewayURL, a.params.PostStore, nil, // asset.Store, not supported by gemini endpoint nil, // post.DraftStore, not supported by gemini endpoint postPreprocessFuncs, )) if errors.Is(err, post.ErrPostNotFound) { a.params.Logger.Warn(ctx, "post not found", err) rw.WriteHeader(gemini.StatusNotFound, "Post not found") } else if err != nil { a.params.Logger.Error(ctx, "rendering error", err) rw.WriteHeader(gemini.StatusTemporaryFailure, err.Error()) return } io.Copy(rw, buf) }), nil }