diff options
Diffstat (limited to 'src/cmd')
-rw-r--r-- | src/cmd/export/main.go | 75 | ||||
-rw-r--r-- | src/cmd/export/posts.go | 179 |
2 files changed, 254 insertions, 0 deletions
diff --git a/src/cmd/export/main.go b/src/cmd/export/main.go new file mode 100644 index 0000000..1d39bc7 --- /dev/null +++ b/src/cmd/export/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + + cfgpkg "dev.mediocregopher.com/mediocre-blog.git/src/cfg" + "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/mlog" +) + +func main() { + var ( + ctx = context.Background() + cfg = cfgpkg.NewBlogCfg(cfgpkg.Params{}) + dataDir cfgpkg.DataDir + httpParams http.Params + gmiParams gmi.Params + ) + + dataDir.SetupCfg(cfg) + defer dataDir.Close() + + httpParams.SetupCfg(cfg) + gmiParams.SetupCfg(cfg) + + exportDirPath := cfg.String("export-dir-path", "", "Directory to export into") + + // initialization + err := cfg.Init(ctx) + + logger := mlog.NewLogger(nil) + defer logger.Close() + + logger.Info(ctx, "process started") + defer logger.Info(ctx, "process exiting") + + if err != nil { + logger.Fatal(ctx, "initializing", err) + } + + if *exportDirPath == "" { + logger.FatalString(ctx, "--export-dir-path is required") + } + + postSQLDB, err := post.NewSQLDB(dataDir) + if err != nil { + logger.Fatal(ctx, "initializing sql db for post data", err) + } + defer postSQLDB.Close() + + var ( + urlBuilder = render.NewURLBuilder( + gmiParams.PublicURL, + httpParams.PublicURL, + gmiParams.PublicURL, + ) + postStore = post.NewStore(postSQLDB) + //postAssetStore = asset.NewStore(postSQLDB) + //postDraftStore = post.NewDraftStore(postSQLDB) + ) + + err = exportPosts( + ctx, + logger.WithNamespace("posts"), + postStore, + urlBuilder, + *exportDirPath, + ) + if err != nil { + logger.Fatal(ctx, "Failed to export post data", err) + } +} 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 +} |