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, ` %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 }