1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
package main

import (
	"flag"
	"fmt"
	"os"
)

// blocks — a notebook-style authoring tool for blog posts.
//
// A post is an ordered list of typed blocks (markdown, code, image, 3D model).
// Files are dropped onto the page and become blocks; everything, text and
// binary alike, is stored in a single SQLite file.
//
// See blocks(1).

const usage = `blocks — block-based authoring for blog posts

Usage:
  blocks serve -db <file> [-addr <host:port>]
  blocks render -db <file> -slug <slug>

Commands:
  serve    Run the editor and preview server
  render   Print a post's rendered HTML to stdout

Run 'blocks <command> -h' for the options of a command.
`

func main() {
	if len(os.Args) < 2 {
		fmt.Fprint(os.Stderr, usage)
		os.Exit(2)
	}
	var err error
	switch os.Args[1] {
	case "serve":
		err = runServe(os.Args[2:])
	case "render":
		err = runRender(os.Args[2:])
	case "-h", "--help", "help":
		fmt.Print(usage)
		return
	default:
		fmt.Fprintf(os.Stderr, "unknown command %q\n\n%s", os.Args[1], usage)
		os.Exit(2)
	}
	if err != nil {
		fmt.Fprintf(os.Stderr, "blocks: %v\n", err)
		os.Exit(1)
	}
}

// newFlagSet builds a subcommand flag set that reports errors to the caller
// rather than calling os.Exit itself, so main stays in charge of the exit path.
func newFlagSet(name string) *flag.FlagSet {
	fs := flag.NewFlagSet(name, flag.ContinueOnError)
	fs.SetOutput(os.Stderr)
	return fs
}

// runRender prints a post's HTML fragment.
//
// This exists as the second caller of the renderer, which keeps the AssetURL
// seam honest: if rendering ever grew a hidden dependency on the HTTP server's
// routes, this command would be the thing that breaks. It is also the shape the
// future static export will take.
func runRender(args []string) error {
	fs := newFlagSet("render")
	dbPath := fs.String("db", "", "path to the SQLite database (required)")
	slug := fs.String("slug", "", "slug of the post to render (required)")
	prefix := fs.String("asset-prefix", "/asset", "URL prefix for asset links")
	if err := fs.Parse(args); err != nil {
		return err
	}
	if *dbPath == "" || *slug == "" {
		return fmt.Errorf("-db and -slug are required")
	}

	db, err := openDB(*dbPath)
	if err != nil {
		return err
	}
	defer db.Close()

	post, err := postBySlug(db, *slug)
	if err != nil {
		return fmt.Errorf("post %q: %w", *slug, err)
	}
	blocks, err := blocksOfPost(db, post.ID)
	if err != nil {
		return err
	}
	html, _, err := renderPost(blocks, func(a *Asset, variant string) string {
		return fmt.Sprintf("%s/%d/%s", *prefix, a.ID, variant)
	})
	if err != nil {
		return err
	}
	fmt.Print(html)
	return nil
}