package stage
import (
"context"
"fmt"
"html/template"
"io"
"os"
"path/filepath"
"strings"
"mercemay.top/src/tilstream/internal/pipeline"
)
// Write renders each post through a Go html/template and writes the
// resulting HTML to OutputDir. The template receives a post view with
// fields Title, URL, HTML, and Meta.
type Write struct {
Template *template.Template
FileMode os.FileMode
}
// Name returns the stage name.
func (*Write) Name() string { return "write" }
// NewWrite returns a Write stage that will render through tmpl.
func NewWrite(tmpl *template.Template) *Write {
return &Write{Template: tmpl, FileMode: 0o644}
}
// Run creates OutputDir if needed and writes one HTML file per post.
func (w *Write) Run(ctx context.Context, st *pipeline.State) error {
if err := os.MkdirAll(st.OutputDir, 0o755); err != nil {
return err
}
for i := range st.Posts {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
if err := w.writeOne(st.OutputDir, &st.Posts[i]); err != nil {
return err
}
}
return nil
}
func (w *Write) writeOne(dir string, p *pipeline.Post) error {
slug := strings.TrimSuffix(filepath.Base(p.Path), ".md")
if s := p.Meta["slug"]; s != "" {
slug = s
}
out := filepath.Join(dir, slug+".html")
f, err := os.OpenFile(out, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, w.FileMode)
if err != nil {
return fmt.Errorf("write: %w", err)
}
defer f.Close()
view := struct {
Title string
URL string
HTML template.HTML
Meta map[string]string
}{
Title: p.Meta["title"],
URL: "/" + slug + ".html",
HTML: template.HTML(p.HTML),
Meta: p.Meta,
}
if w.Template == nil {
return writePlain(f, view.HTML)
}
return w.Template.Execute(f, view)
}
func writePlain(w io.Writer, body template.HTML) error {
_, err := io.WriteString(w, string(body))
return err
}
// MustLoadTemplate is a test helper that loads every .tmpl file in a dir
// into a single Template instance.
func MustLoadTemplate(dir string) *template.Template {
return template.Must(template.ParseGlob(filepath.Join(dir, "*.tmpl")))
}