summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Picciano <mediocregopher@gmail.com>2023-01-21 17:37:22 +0100
committerBrian Picciano <mediocregopher@gmail.com>2023-01-21 17:37:22 +0100
commitffdd9520b9803e141582ba647050682659075760 (patch)
treed19769360077d7b4a4cce58a0e70679014fd5979
parent293655452cfc6a106c55384e839f9c07d340b954 (diff)
Implement preprocessing of post bodies for gemini
-rw-r--r--src/gmi/tpl.go40
-rw-r--r--src/gmi/tpl/posts/post.gmi2
-rw-r--r--src/http/posts.go109
-rw-r--r--src/post/preprocess.go66
4 files changed, 155 insertions, 62 deletions
diff --git a/src/gmi/tpl.go b/src/gmi/tpl.go
index 7959b8e..7f41993 100644
--- a/src/gmi/tpl.go
+++ b/src/gmi/tpl.go
@@ -8,6 +8,7 @@ import (
"io"
"io/fs"
"net/url"
+ "path/filepath"
"strconv"
"strings"
"text/template"
@@ -83,6 +84,45 @@ func (r renderer) GetPostSeriesNextPrevious(p post.StoredPost) (rendererGetPostS
return res, nil
}
+func (r renderer) PostBody(p post.StoredPost) (string, error) {
+
+ preprocessFuncs := post.PreprocessFunctions{
+ BlogURL: func(path string) string {
+ return filepath.Join("/", 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 {
+ return filepath.Join("/static", path)
+ },
+ 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)
+
+ if err := p.PreprocessBody(buf, preprocessFuncs); err != nil {
+ return "", fmt.Errorf("preprocessing post body: %w", err)
+ }
+
+ return buf.String(), nil
+}
+
func (r renderer) GetQueryValue(key, def string) string {
v := r.url.Query().Get(key)
if v == "" {
diff --git a/src/gmi/tpl/posts/post.gmi b/src/gmi/tpl/posts/post.gmi
index 4f58c84..7d1719b 100644
--- a/src/gmi/tpl/posts/post.gmi
+++ b/src/gmi/tpl/posts/post.gmi
@@ -6,7 +6,7 @@
> {{ $post.Description }}
{{ end -}}
-{{ $post.Body }}
+{{ .PostBody $post }}
--------------------------------------------------------------------------------
diff --git a/src/http/posts.go b/src/http/posts.go
index eff0eaa..cae8f43 100644
--- a/src/http/posts.go
+++ b/src/http/posts.go
@@ -21,68 +21,43 @@ import (
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
)
-func (a *api) parsePostBody(p post.Post) (*txttpl.Template, error) {
- tpl := txttpl.New("root")
- tpl = tpl.Funcs(txttpl.FuncMap(a.tplFuncs()))
-
- tpl = txttpl.Must(tpl.New("image.html").Parse(mustReadTplFile("image.html")))
-
- if p.Format == post.FormatMarkdown {
- tpl = tpl.Funcs(txttpl.FuncMap{
- "Image": func(id string) (string, error) {
-
- tplPayload := struct {
- ID string
- Descr string
- Resizable bool
- }{
- ID: id,
- // I could use variadic args to make this work, I think
- Descr: "TODO: proper alt text",
- Resizable: isImgResizable(id),
- }
-
- buf := new(bytes.Buffer)
- if err := tpl.ExecuteTemplate(buf, "image.html", tplPayload); err != nil {
- return "", err
- }
-
- return buf.String(), nil
- },
- })
- }
+func (a *api) postPreprocessFuncImage(args ...string) (string, error) {
+
+ var (
+ id = args[0]
+ descr = "TODO"
+ )
- if p.Format == post.FormatGemtext {
- tpl = tpl.Funcs(txttpl.FuncMap{
- "Image": func(id, descr string) (string, error) {
-
- tplPayload := struct {
- ID string
- Descr string
- Resizable bool
- }{
- ID: id,
- Descr: descr,
- Resizable: isImgResizable(id),
- }
-
- buf := new(bytes.Buffer)
- if err := tpl.ExecuteTemplate(buf, "image.html", tplPayload); err != nil {
- return "", err
- }
-
- return buf.String(), nil
- },
- })
+ if len(args) > 1 {
+ descr = args[1]
}
- tpl, err := tpl.New(p.ID + "-body.html").Parse(p.Body)
+ tpl := txttpl.New("image.html")
- if err != nil {
- return nil, err
+ tpl.Funcs(txttpl.FuncMap{
+ "AssetURL": func(id string) string {
+ return a.assetURL(id, false)
+ },
+ })
+
+ tpl = txttpl.Must(tpl.Parse(mustReadTplFile("image.html")))
+
+ tplPayload := struct {
+ ID string
+ Descr string
+ Resizable bool
+ }{
+ ID: id,
+ Descr: descr,
+ Resizable: isImgResizable(id),
+ }
+
+ buf := new(bytes.Buffer)
+ if err := tpl.ExecuteTemplate(buf, "image.html", tplPayload); err != nil {
+ return "", err
}
- return tpl, nil
+ return buf.String(), nil
}
type postTplPayload struct {
@@ -93,15 +68,27 @@ type postTplPayload struct {
func (a *api) postToPostTplPayload(storedPost post.StoredPost) (postTplPayload, error) {
- bodyTpl, err := a.parsePostBody(storedPost.Post)
- if err != nil {
- return postTplPayload{}, fmt.Errorf("parsing post body as template: %w", err)
+ preprocessFuncs := post.PreprocessFunctions{
+ BlogURL: func(path string) string {
+ return a.blogURL(path, false)
+ },
+ AssetURL: func(id string) string {
+ return a.assetURL(id, false)
+ },
+ PostURL: func(id string) string {
+ return a.postURL(id, false)
+ },
+ StaticURL: func(path string) string {
+ path = filepath.Join("static", path)
+ return a.blogURL(path, false)
+ },
+ Image: a.postPreprocessFuncImage,
}
bodyBuf := new(bytes.Buffer)
- if err := bodyTpl.Execute(bodyBuf, nil); err != nil {
- return postTplPayload{}, fmt.Errorf("executing post body as template: %w", err)
+ if err := storedPost.PreprocessBody(bodyBuf, preprocessFuncs); err != nil {
+ return postTplPayload{}, fmt.Errorf("preprocessing post body: %w", err)
}
if storedPost.Format == post.FormatGemtext {
diff --git a/src/post/preprocess.go b/src/post/preprocess.go
new file mode 100644
index 0000000..4d4be9d
--- /dev/null
+++ b/src/post/preprocess.go
@@ -0,0 +1,66 @@
+package post
+
+import (
+ "fmt"
+ "io"
+ "text/template"
+)
+
+// PreprocessFunctions are functions which can be used by posts themselves to
+// interleave dynamic content into their bodies. Usually this is used for
+// properly constructing URLs, but also for things like displaying images.
+type PreprocessFunctions struct {
+
+ // BlogURL returns the given string, rooted to the blog's base url (which
+ // may or may not include path components itself).
+ //
+ // The given path should not have a leading slash.
+ BlogURL func(path string) string
+
+ // AssetURL returns the URL of the asset with the given ID.
+ AssetURL func(id string) string
+
+ // PostURL returns the URL of the post with the given ID.
+ PostURL func(id string) string
+
+ // StaticURL returns the URL of a file being served from the static
+ // directory. The given path should _not_ include the prefixed 'static/'
+ // path element.
+ StaticURL func(path string) string
+
+ // Image returns a string which should be inlined into the post body in
+ // order to display an.
+ //
+ // The first argument to Image _must_ be the ID of an image asset. The
+ // second argument _may_ be a description of the image which will be used as
+ // alt text, or possibly displayed to the user with the image.
+ Image func(args ...string) (string, error)
+}
+
+// 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
+// executes the template and writes the result to the given writer.
+func (p Post) PreprocessBody(into io.Writer, funcs PreprocessFunctions) error {
+
+ tpl := template.New("")
+
+ tpl.Funcs(template.FuncMap{
+ "BlogURL": funcs.BlogURL,
+ "AssetURL": funcs.AssetURL,
+ "PostURL": funcs.PostURL,
+ "StaticURL": funcs.StaticURL,
+ "Image": funcs.Image,
+ })
+
+ tpl, err := tpl.Parse(p.Body)
+
+ if err != nil {
+ return fmt.Errorf("parsing post body as template: %w", err)
+ }
+
+ if err := tpl.Execute(into, nil); err != nil {
+ return fmt.Errorf("executing post body as template: %w", err)
+ }
+
+ return nil
+}