-
Notifications
You must be signed in to change notification settings - Fork 0
/
build.go
135 lines (115 loc) · 3.08 KB
/
build.go
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package build
import (
"bufio"
"errors"
"fmt"
"io"
"io/fs"
"log"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"text/template"
"github.com/russross/blackfriday/v2"
)
var titlePat = regexp.MustCompile(`^=== ([^=]+) ===$`)
func readTitle(r *bufio.Reader) (string, error) {
line, err := r.ReadString('\n')
if err != nil {
return "", fmt.Errorf("error reading title: %w", err)
}
ms := titlePat.FindStringSubmatch(line[:len(line)-1])
if len(ms) != 2 {
return "", errors.New("title is missing")
}
return ms[1], nil
}
type Page struct {
Title string
Content string
}
const extensions = blackfriday.CommonExtensions | blackfriday.AutoHeadingIDs
func buildMarkdown(dst io.Writer, src io.Reader, tmpl *template.Template) error {
r := bufio.NewReader(src)
w := bufio.NewWriter(dst)
// process the title
title, err := readTitle(r)
if err != nil {
return fmt.Errorf("error building markdown: %w", err)
}
// process the content
input, err := io.ReadAll(r)
if err != nil {
return fmt.Errorf("error reading markdown source: %w", err)
}
output := blackfriday.Run(input,
blackfriday.WithNoExtensions(),
blackfriday.WithExtensions(extensions))
// process the post template
if err := tmpl.Execute(w, Page{title, string(output)}); err != nil {
return fmt.Errorf("error rendering post template: %w", err)
}
if err := w.Flush(); err != nil {
return fmt.Errorf("error writing to destination: %w", err)
}
return nil
}
func BuildFile(
path string, dst io.Writer, src io.Reader, tmpl *template.Template,
) error {
log.Printf("building file: %s", path)
if strings.HasSuffix(path, "md") {
return buildMarkdown(dst, src, tmpl)
}
_, err := io.Copy(dst, src)
return err
}
func makeDstPath(srcDir, dstDir, filePath string) (string, error) {
relPath, err := filepath.Rel(srcDir, filePath)
if err != nil {
return "", fmt.Errorf("failed to get relative dest path: %w", err)
}
if strings.HasSuffix(relPath, "md") {
relPath = strings.TrimSuffix(relPath, filepath.Ext(relPath))
relPath = relPath + ".html"
}
dstPath := path.Join(dstDir, relPath)
if err := os.MkdirAll(path.Dir(dstPath), 0755); err != nil {
return "", fmt.Errorf("failed to make dest dirs: %w", err)
}
return dstPath, nil
}
func walk(dstDir, srcDir string, tmpl *template.Template) fs.WalkDirFunc {
return func(filePath string, d fs.DirEntry, err error) error {
if err != nil {
// io error unrelated to building
return err
}
if d.IsDir() {
return nil
}
dstPath, err := makeDstPath(srcDir, dstDir, filePath)
if err != nil {
return fmt.Errorf("error creating dest path: %w", err)
}
src, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("error opening src: %w", err)
}
defer src.Close()
dst, err := os.Create(dstPath)
if err != nil {
return fmt.Errorf("error opening dst: %w", err)
}
defer dst.Close()
if err := BuildFile(filePath, dst, src, tmpl); err != nil {
return fmt.Errorf("error building file %s: %s", filePath, err)
}
return nil
}
}
func BuildTree(dst, src string, tmpl *template.Template) error {
return filepath.WalkDir(src, walk(dst, src, tmpl))
}