mirror of
https://git.sr.ht/~adnano/kiln
synced 2024-10-30 01:13:08 +00:00
225 lines
4.8 KiB
Go
225 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
pathpkg "path"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Dir represents a directory.
|
|
type Dir struct {
|
|
Title string
|
|
Date time.Time
|
|
Content string
|
|
Path string
|
|
Pages []*Page
|
|
Dirs []*Dir
|
|
index *Page // The index page.
|
|
feed []byte // Atom feed.
|
|
}
|
|
|
|
// NewDir returns a new Dir with the given path.
|
|
func NewDir(path string) *Dir {
|
|
if path == "" {
|
|
path = "/"
|
|
} else {
|
|
path = "/" + path + "/"
|
|
}
|
|
return &Dir{
|
|
Path: path,
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
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 {
|
|
return err
|
|
}
|
|
d.Dirs = append(d.Dirs, dir)
|
|
} else if ext := pathpkg.Ext(name); ext == task.InputExt {
|
|
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
|
|
d.Date = d.index.Date
|
|
d.Content = d.index.Content
|
|
} else {
|
|
d.Pages = append(d.Pages, NewPage(path, content))
|
|
}
|
|
}
|
|
}
|
|
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 {
|
|
tmpl, ok := cfg.templates.FindTemplate(d.Path, "index"+task.TemplateExt)
|
|
if ok {
|
|
var b strings.Builder
|
|
if err := tmpl.Execute(&b, d); err != nil {
|
|
return err
|
|
}
|
|
d.index.Content = b.String()
|
|
}
|
|
}
|
|
|
|
// Process pages
|
|
for i := range d.Pages {
|
|
var b strings.Builder
|
|
tmpl, ok := cfg.templates.FindTemplate(d.Path, "page"+task.TemplateExt)
|
|
if ok {
|
|
if err := tmpl.Execute(&b, d.Pages[i]); err != nil {
|
|
return err
|
|
}
|
|
d.Pages[i].Content = b.String()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Feed represents a feed.
|
|
type Feed struct {
|
|
Title string // Feed title.
|
|
Path string // Feed path.
|
|
Updated time.Time // Last updated time.
|
|
Entries []*Page // Feed entries.
|
|
}
|
|
|
|
// Create feeds
|
|
if title, ok := cfg.Feeds[d.Path]; ok {
|
|
var b bytes.Buffer
|
|
feed := &Feed{
|
|
Title: title,
|
|
Path: d.Path,
|
|
Updated: time.Now(),
|
|
Entries: d.Pages,
|
|
}
|
|
tmpl, ok := cfg.templates.FindTemplate(d.Path, "atom.xml")
|
|
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)
|
|
}
|
|
}
|
|
|
|
// Process subdirectories
|
|
for _, d := range d.Dirs {
|
|
if err := d.Process(cfg, task); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Write writes the directory's contents to the provided destination path.
|
|
func (d *Dir) Write(dstDir string, task *Task) error {
|
|
// 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)
|
|
}
|
|
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)
|
|
}
|
|
|
|
dstPath := pathpkg.Join(dstDir, path)
|
|
dir := pathpkg.Dir(dstPath)
|
|
os.MkdirAll(dir, 0755)
|
|
if err := os.WriteFile(dstPath, content, 0644); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write subdirectories
|
|
for _, dir := range d.Dirs {
|
|
dir.Write(dstDir, task)
|
|
}
|
|
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
|
|
}
|