main.go

// Command tilstream renders a folder of TIL markdown notes into a static site.
// See mercemay.top/src/tilstream/ for usage.
package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"mercemay.top/src/tilstream/internal/feed"
	"mercemay.top/src/tilstream/internal/index"
	"mercemay.top/src/tilstream/internal/render"
	"mercemay.top/src/tilstream/internal/watch"
)

func main() {
	if len(os.Args) < 2 {
		usage()
		os.Exit(2)
	}
	switch os.Args[1] {
	case "build":
		runBuild(os.Args[2:])
	case "serve":
		runServe(os.Args[2:])
	case "version":
		fmt.Println("tilstream 0.6.1")
	default:
		usage()
		os.Exit(2)
	}
}

func usage() {
	fmt.Fprintln(os.Stderr, "usage: tilstream <build|serve|version> [flags]")
}

type commonFlags struct {
	src      string
	out      string
	drafts   bool
	template string
	baseURL  string
}

func registerCommon(fs *flag.FlagSet, c *commonFlags) {
	fs.StringVar(&c.src, "src", ".", "source directory of markdown notes")
	fs.StringVar(&c.out, "out", "public", "output directory")
	fs.BoolVar(&c.drafts, "drafts", false, "include posts with draft: true")
	fs.StringVar(&c.template, "template", "templates/post.tmpl", "post template")
	fs.StringVar(&c.baseURL, "base-url", "https://example.com", "site base URL")
}

func runBuild(args []string) {
	var c commonFlags
	fs := flag.NewFlagSet("build", flag.ExitOnError)
	registerCommon(fs, &c)
	_ = fs.Parse(args)
	if err := build(c); err != nil {
		log.Fatalf("build: %v", err)
	}
	log.Printf("wrote %s", c.out)
}

func runServe(args []string) {
	var c commonFlags
	var port int
	fs := flag.NewFlagSet("serve", flag.ExitOnError)
	registerCommon(fs, &c)
	fs.IntVar(&port, "port", 8080, "HTTP port")
	_ = fs.Parse(args)

	c.drafts = true // always preview drafts in serve mode
	if err := build(c); err != nil {
		log.Fatalf("initial build: %v", err)
	}
	go watch.Loop(c.src, 150*time.Millisecond, func() {
		if err := build(c); err != nil {
			log.Printf("rebuild: %v", err)
			return
		}
		log.Println("rebuilt")
	})
	addr := fmt.Sprintf(":%d", port)
	log.Printf("serving %s on http://localhost%s", c.out, addr)
	log.Fatal(http.ListenAndServe(addr, http.FileServer(http.Dir(c.out))))
}

func build(c commonFlags) error {
	if err := os.MkdirAll(c.out, 0o755); err != nil {
		return err
	}
	posts, err := render.RenderTree(c.src, c.out, c.template, c.drafts)
	if err != nil {
		return err
	}
	if err := feed.Write(filepath.Join(c.out, "feed.xml"), posts, c.baseURL); err != nil {
		return err
	}
	if err := index.Write(filepath.Join(c.out, "search.json"), posts); err != nil {
		return err
	}
	return nil
}