package cmd
import (
"context"
"fmt"
"os"
"time"
"github.com/spf13/cobra"
"mercemay.top/src/tilstream/internal/pipeline"
"mercemay.top/src/tilstream/internal/pipeline/stage"
)
var (
buildSrc string
buildOut string
buildTemplate string
buildDrafts bool
)
var buildCmd = &cobra.Command{
Use: "build",
Short: "Build the site",
Long: "Read markdown from --src, write HTML + feeds + search index to --out.",
RunE: runBuild,
}
func init() {
buildCmd.Flags().StringVar(&buildSrc, "src", "content", "source directory")
buildCmd.Flags().StringVar(&buildOut, "out", "public", "output directory")
buildCmd.Flags().StringVar(&buildTemplate, "template", "templates", "template directory")
buildCmd.Flags().BoolVar(&buildDrafts, "drafts", false, "include draft posts")
}
func runBuild(cmd *cobra.Command, args []string) error {
start := time.Now()
PrintBanner()
tmpl := stage.MustLoadTemplate(buildTemplate)
p := pipeline.New(
stage.NewLoad(),
&stage.Parse{},
&stage.Hash{Short: true},
stage.NewRender(),
stage.NewWrite(tmpl),
)
st := &pipeline.State{
SourceDir: buildSrc,
OutputDir: buildOut,
}
ctx, cancel := context.WithTimeout(cmd.Context(), 30*time.Second)
defer cancel()
if err := p.Run(ctx, st); err != nil {
return fmt.Errorf("build failed: %w", err)
}
if len(st.Errors) > 0 {
for _, e := range st.Errors {
fmt.Fprintln(os.Stderr, "warn:", e)
}
}
fmt.Printf("built %d posts in %s\n", len(st.Posts), time.Since(start).Round(time.Millisecond))
if verbose {
for name, d := range st.Metrics.Snapshot() {
fmt.Printf(" %-8s %s\n", name, d)
}
}
return nil
}
// BuildStateFor is exposed for tests that want to exercise the same
// pipeline without spawning a child process.
func BuildStateFor(src, out, tmplDir string) (*pipeline.State, error) {
tmpl := stage.MustLoadTemplate(tmplDir)
p := pipeline.New(
stage.NewLoad(),
&stage.Parse{},
&stage.Hash{Short: true},
stage.NewRender(),
stage.NewWrite(tmpl),
)
st := &pipeline.State{SourceDir: src, OutputDir: out}
if err := p.Run(context.Background(), st); err != nil {
return nil, err
}
return st, nil
}