kiln/dir.go

225 lines
4.8 KiB
Go
Raw Normal View History

2020-11-20 17:07:38 +00:00
package main
import (
2020-11-22 20:14:50 +00:00
"bytes"
2021-04-21 18:39:13 +00:00
"fmt"
"io"
2020-11-20 17:07:38 +00:00
"io/ioutil"
"log"
2020-11-20 17:07:38 +00:00
"os"
"os/exec"
2020-11-20 17:07:38 +00:00
pathpkg "path"
"sort"
"strings"
"time"
)
// Dir represents a directory.
type Dir struct {
2021-04-26 18:47:45 +00:00
Title string
Date time.Time
Content string
Path string
Pages []*Page
Dirs []*Dir
index *Page // The index page.
feed []byte // Atom feed.
2020-11-20 17:07:38 +00:00
}
// NewDir returns a new Dir with the given path.
func NewDir(path string) *Dir {
if path == "" {
path = "/"
} else {
path = "/" + path + "/"
}
return &Dir{
2021-04-20 18:49:45 +00:00
Path: path,
2020-11-20 17:07:38 +00:00
}
}
// Read reads from a directory and indexes the files and directories within it.
func (d *Dir) Read(srcDir string, task *Task) error {
return d.read(srcDir, "", task)
}
func (d *Dir) read(srcDir, path string, task *Task) error {
2020-11-20 17:07:38 +00:00
entries, err := ioutil.ReadDir(pathpkg.Join(srcDir, path))
if err != nil {
return err
}
for _, entry := range entries {
name := entry.Name()
// Ignore names that start with "_"
if strings.HasPrefix(name, "_") {
continue
}
path := pathpkg.Join(path, name)
if entry.IsDir() {
// Gather directory data
dir := NewDir(path)
if err := dir.read(srcDir, path, task); err != nil {
2020-11-20 17:07:38 +00:00
return err
}
d.Dirs = append(d.Dirs, dir)
} else if ext := pathpkg.Ext(name); ext == task.InputExt {
2020-11-20 17:07:38 +00:00
srcPath := pathpkg.Join(srcDir, path)
content, err := ioutil.ReadFile(srcPath)
if err != nil {
return err
}
if cmd := task.PreProcess; cmd != "" {
content = RunProcessCmd(cmd, bytes.NewReader(content))
}
// Gather page data
if strings.TrimSuffix(name, ext) == "index" {
d.index = NewPage(d.Path, content)
d.Title = d.index.Title
2021-04-26 18:47:45 +00:00
d.Date = d.index.Date
d.Content = d.index.Content
} else {
d.Pages = append(d.Pages, NewPage(path, content))
2020-11-20 17:07:38 +00:00
}
}
}
return nil
}
// Process processes the directory's contents.
func (d *Dir) Process(cfg *Config, task *Task) error {
if task.TemplateExt != "" {
// Create index
if d.index != nil {
2021-05-09 00:27:13 +00:00
tmpl, ok := cfg.templates.FindTemplate(d.Path, "index"+task.TemplateExt)
2021-04-21 18:39:13 +00:00
if ok {
var b strings.Builder
if err := tmpl.Execute(&b, d); err != nil {
return err
}
d.index.Content = b.String()
}
2020-11-20 17:07:38 +00:00
}
// Process pages
for i := range d.Pages {
var b strings.Builder
2021-05-09 00:27:13 +00:00
tmpl, ok := cfg.templates.FindTemplate(d.Path, "page"+task.TemplateExt)
2021-04-21 18:39:13 +00:00
if ok {
if err := tmpl.Execute(&b, d.Pages[i]); err != nil {
return err
}
d.Pages[i].Content = b.String()
}
2020-11-20 17:07:38 +00:00
}
}
// Feed represents a feed.
type Feed struct {
Title string // Feed title.
Path string // Feed path.
2020-11-20 17:07:38 +00:00
Updated time.Time // Last updated time.
Entries []*Page // Feed entries.
}
// Create feeds
if title, ok := cfg.Feeds[d.Path]; ok {
2020-11-22 20:14:50 +00:00
var b bytes.Buffer
2020-11-20 17:07:38 +00:00
feed := &Feed{
Title: title,
Path: d.Path,
2020-11-20 17:07:38 +00:00
Updated: time.Now(),
Entries: d.Pages,
}
2021-05-09 00:27:13 +00:00
tmpl, ok := cfg.templates.FindTemplate(d.Path, "atom.xml")
2021-04-21 18:39:13 +00:00
if ok {
if err := tmpl.Execute(&b, feed); err != nil {
return err
}
d.feed = b.Bytes()
} else {
fmt.Printf("Warning: failed to generate feed %q: missing template \"atom.xml\"\n", title)
2020-11-20 17:07:38 +00:00
}
}
// Process subdirectories
2020-11-20 17:07:38 +00:00
for _, d := range d.Dirs {
if err := d.Process(cfg, task); err != nil {
2020-11-20 17:07:38 +00:00
return err
}
}
return nil
}
// Write writes the directory's contents to the provided destination path.
func (d *Dir) Write(dstDir string, task *Task) error {
2020-11-20 17:07:38 +00:00
// Create the directory
dirPath := pathpkg.Join(dstDir, d.Path)
if err := os.MkdirAll(dirPath, 0755); err != nil {
return err
}
// Write pages
pages := d.Pages
if d.index != nil {
pages = append(pages, d.index)
2020-11-20 17:07:38 +00:00
}
for _, page := range pages {
path := task.OutputPath(page.Path)
var content []byte
if cmd := task.PostProcess; cmd != "" {
content = RunProcessCmd(cmd, strings.NewReader(page.Content))
} else {
content = []byte(page.Content)
}
2020-11-20 17:07:38 +00:00
dstPath := pathpkg.Join(dstDir, path)
dir := pathpkg.Dir(dstPath)
os.MkdirAll(dir, 0755)
if err := os.WriteFile(dstPath, content, 0644); err != nil {
2020-11-20 17:07:38 +00:00
return err
}
}
2021-04-20 18:49:45 +00:00
// Write the atom feed
if d.feed != nil {
const path = "atom.xml"
dstPath := pathpkg.Join(dstDir, path)
os.MkdirAll(dstDir, 0755)
if err := os.WriteFile(dstPath, d.feed, 0644); err != nil {
2021-04-20 18:49:45 +00:00
return err
}
}
2020-11-20 17:07:38 +00:00
// Write subdirectories
for _, dir := range d.Dirs {
dir.Write(dstDir, task)
2020-11-20 17:07:38 +00:00
}
return nil
}
// sort sorts the directory's pages by date.
func (d *Dir) sort() {
sort.Slice(d.Pages, func(i, j int) bool {
return d.Pages[i].Date.After(d.Pages[j].Date)
})
// Sort subdirectories
for _, d := range d.Dirs {
d.sort()
}
}
// RunProcessCmd runs a process command.
func RunProcessCmd(command string, input io.Reader) []byte {
split := strings.Split(command, " ")
cmd := exec.Command(split[0], split[1:]...)
cmd.Stdin = input
cmd.Stderr = os.Stderr
output, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
return output
}