summaryrefslogtreecommitdiff
path: root/src/cmd/export/posts.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/export/posts.go')
-rw-r--r--src/cmd/export/posts.go179
1 files changed, 179 insertions, 0 deletions
diff --git a/src/cmd/export/posts.go b/src/cmd/export/posts.go
new file mode 100644
index 0000000..f54fc2c
--- /dev/null
+++ b/src/cmd/export/posts.go
@@ -0,0 +1,179 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ txttpl "text/template"
+
+ gmnhg "github.com/tdemin/gmnhg"
+
+ "dev.mediocregopher.com/mediocre-blog.git/src/gmi"
+ "dev.mediocregopher.com/mediocre-blog.git/src/http"
+ "dev.mediocregopher.com/mediocre-blog.git/src/post"
+ "dev.mediocregopher.com/mediocre-blog.git/src/render"
+ "dev.mediocregopher.com/mediocre-go-lib.git/mctx"
+ "dev.mediocregopher.com/mediocre-go-lib.git/mlog"
+)
+
+var postFileExtensions = map[post.Format]string{
+ post.FormatGemtext: "gmi",
+ post.FormatMarkdown: "md",
+}
+
+func postTargetFormat(p post.StoredPost) post.Format {
+ if p.Format == post.FormatMarkdown && strings.Contains(p.Body, `</`) {
+ return post.FormatMarkdown
+ }
+ return post.FormatGemtext
+}
+
+func writePostBody(post post.StoredPost, path, body string) error {
+ f, err := os.Create(path)
+ if err != nil {
+ return fmt.Errorf("opening file: %w", err)
+ }
+ defer func() { _ = f.Close() }()
+
+ writeString := func(str string) {
+ if err != nil {
+ return
+ }
+ _, err = f.WriteString(str)
+ }
+
+ writeString(fmt.Sprintf("# %s\n\n", post.Title))
+ if post.Description != "" {
+ writeString(fmt.Sprintf("> %s\n\n", post.Description))
+ }
+ writeString(body)
+
+ return err
+}
+
+func writePosts(
+ urlBuilder render.URLBuilder, posts []post.StoredPost, path string,
+) error {
+ f, err := os.Create(path)
+ if err != nil {
+ return fmt.Errorf("opening file: %w", err)
+ }
+ defer func() { _ = f.Close() }()
+
+ writeString := func(str string) {
+ if err != nil {
+ return
+ }
+ _, err = f.WriteString(str)
+ }
+
+ writeString("# Posts\n\n")
+ for _, p := range posts {
+ var (
+ targetFormat = postTargetFormat(p)
+ u = urlBuilder.Post(p.ID)
+ )
+
+ if targetFormat == post.FormatMarkdown {
+ u = u.HTTP()
+ }
+
+ writeString(fmt.Sprintf(
+ "=> %s %s - %s\n\n",
+ u.String(),
+ p.PublishedAt.Format("2006-01-02"),
+ p.Title,
+ ))
+ }
+
+ return err
+}
+
+func exportPosts(
+ ctx context.Context,
+ logger *mlog.Logger,
+ postStore post.Store,
+ urlBuilder render.URLBuilder,
+ exportDirPath string,
+) error {
+ var (
+ postsDir = filepath.Join(exportDirPath, "posts")
+ preprocessFuncs = map[post.Format]post.PreprocessFunctions{
+ post.FormatGemtext: gmi.PostPreprocessFuncs{URLBuilder: urlBuilder},
+ post.FormatMarkdown: http.NewPostPreprocessFuncs(urlBuilder),
+ }
+ )
+
+ if err := os.MkdirAll(postsDir, 0755); err != nil {
+ return fmt.Errorf("creating posts dir %q: %w", postsDir, err)
+ }
+
+ logger.Info(ctx, "Getting posts")
+ posts, _, err := postStore.Get(0, 10000)
+ if err != nil {
+ return fmt.Errorf("getting posts: %w", err)
+ }
+
+ {
+ postsIndexPath := filepath.Join(postsDir, "index.gmi")
+ ctx := mctx.Annotate(ctx, "path", postsIndexPath)
+ logger.Info(ctx, "Writing posts index page")
+ err = writePosts(urlBuilder, posts, postsIndexPath)
+ if err != nil {
+ return fmt.Errorf("writing posts index page: %w", err)
+ }
+ }
+
+ for _, p := range posts {
+ var (
+ targetFormat = postTargetFormat(p)
+ ext = postFileExtensions[targetFormat]
+ postFilePath = filepath.Join(postsDir, p.ID+"."+ext)
+
+ ctx = mctx.Annotate(
+ ctx,
+ "post_id", p.ID,
+ "post_title", p.Title,
+ "post_format", p.Format,
+ "target_format", targetFormat,
+ )
+
+ body = new(bytes.Buffer)
+ )
+
+ logger.Info(ctx, "Rendering post body")
+ tpl := txttpl.New("")
+
+ tpl, err := tpl.Parse(p.Body)
+ if err != nil {
+ return fmt.Errorf("parsing post %q body as template: %w", p.ID, err)
+ }
+
+ err = tpl.Execute(body, struct {
+ RootURL render.URLBuilder
+ post.PreprocessFunctions
+ }{
+ urlBuilder, preprocessFuncs[targetFormat],
+ })
+ if err != nil {
+ return fmt.Errorf("executing post %q body as template: %w", p.ID, err)
+ }
+
+ if p.Format == post.FormatMarkdown && targetFormat == post.FormatGemtext {
+ gemtextBodyBytes, err := gmnhg.RenderMarkdown(body.Bytes(), 0)
+ if err != nil {
+ return fmt.Errorf("converting post %q from markdown: %w", p.ID, err)
+ }
+ body = bytes.NewBuffer(gemtextBodyBytes)
+ }
+
+ if err := writePostBody(p, postFilePath, body.String()); err != nil {
+ return fmt.Errorf("writing post %q body to file %q: %w", p.ID, postFilePath, err)
+ }
+ }
+
+ return nil
+}