2020-11-20 18:07:38 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-11-22 21:14:50 +01:00
|
|
|
"bytes"
|
2020-11-20 18:07:38 +01:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
pathpkg "path"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Dir represents a directory.
|
|
|
|
type Dir struct {
|
2020-11-20 21:03:41 +01:00
|
|
|
Title string // Directory title.
|
|
|
|
Content string // Directory index content.
|
|
|
|
Path string // Directory path.
|
|
|
|
Pages []*Page // Pages in this directory.
|
|
|
|
Dirs []*Dir // Subdirectories.
|
|
|
|
files map[string][]byte // Static files.
|
|
|
|
index *Page // The index page.
|
2020-11-20 18:07:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewDir returns a new Dir with the given path.
|
|
|
|
func NewDir(path string) *Dir {
|
|
|
|
if path == "" {
|
|
|
|
path = "/"
|
|
|
|
} else {
|
|
|
|
path = "/" + path + "/"
|
|
|
|
}
|
|
|
|
return &Dir{
|
|
|
|
Path: path,
|
|
|
|
files: map[string][]byte{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// read reads from a directory and indexes the files and directories within it.
|
|
|
|
func (d *Dir) read(srcDir string, path string) 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); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.Dirs = append(d.Dirs, dir)
|
|
|
|
} else {
|
|
|
|
srcPath := pathpkg.Join(srcDir, path)
|
|
|
|
content, err := ioutil.ReadFile(srcPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if pathpkg.Ext(name) == ".gmi" {
|
|
|
|
// Gather page data
|
|
|
|
if name == "index.gmi" {
|
2020-11-20 21:03:41 +01:00
|
|
|
d.index = NewPage(d.Path, content)
|
|
|
|
d.Title = d.index.Title
|
|
|
|
d.Content = d.index.Content
|
2020-11-20 18:07:38 +01:00
|
|
|
} else {
|
2020-11-20 21:03:41 +01:00
|
|
|
d.Pages = append(d.Pages, NewPage(path, content))
|
2020-11-20 18:07:38 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Static file
|
|
|
|
d.files[path] = content
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// manipulate processes and manipulates the directory's contents.
|
|
|
|
func (d *Dir) manipulate(cfg *Config) error {
|
2020-11-20 21:03:41 +01:00
|
|
|
// Create index
|
|
|
|
if d.index != nil {
|
2020-11-20 18:07:38 +01:00
|
|
|
var b strings.Builder
|
|
|
|
tmpl := cfg.Templates.FindTemplate(d.Path, "index.gmi")
|
|
|
|
if err := tmpl.Execute(&b, d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-11-20 21:03:41 +01:00
|
|
|
d.index.Content = b.String()
|
2020-11-20 18:07:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Manipulate pages
|
|
|
|
for i := range d.Pages {
|
|
|
|
var b strings.Builder
|
2020-11-28 00:49:53 +01:00
|
|
|
tmpl := cfg.Templates.FindTemplate(d.Path, "page.gmi")
|
2020-11-20 18:07:38 +01:00
|
|
|
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.
|
2020-11-20 21:03:41 +01:00
|
|
|
Path string // Feed path.
|
2020-11-20 18:07:38 +01:00
|
|
|
Updated time.Time // Last updated time.
|
|
|
|
Entries []*Page // Feed entries.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create feeds
|
|
|
|
if title, ok := cfg.Feeds[d.Path]; ok {
|
2020-11-22 21:14:50 +01:00
|
|
|
var b bytes.Buffer
|
2020-11-20 18:07:38 +01:00
|
|
|
feed := &Feed{
|
|
|
|
Title: title,
|
2020-11-20 21:03:41 +01:00
|
|
|
Path: d.Path,
|
2020-11-20 18:07:38 +01:00
|
|
|
Updated: time.Now(),
|
|
|
|
Entries: d.Pages,
|
|
|
|
}
|
2020-11-22 21:14:50 +01:00
|
|
|
tmpl := cfg.Templates.FindTemplate(d.Path, "atom.xml")
|
2020-11-20 18:07:38 +01:00
|
|
|
if err := tmpl.Execute(&b, feed); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-11-22 21:14:50 +01:00
|
|
|
d.files[pathpkg.Join(d.Path, "atom.xml")] = b.Bytes()
|
2020-11-20 18:07:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Manipulate subdirectories
|
|
|
|
for _, d := range d.Dirs {
|
|
|
|
if err := d.manipulate(cfg); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// write writes the Dir to the provided destination path.
|
2021-02-28 03:53:16 +01:00
|
|
|
func (d *Dir) write(dstDir string, format Format, cfg *Config) error {
|
2020-11-20 18:07:38 +01:00
|
|
|
// Create the directory
|
|
|
|
dirPath := pathpkg.Join(dstDir, d.Path)
|
|
|
|
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write static files
|
|
|
|
for path := range d.files {
|
|
|
|
dstPath := pathpkg.Join(dstDir, path)
|
|
|
|
f, err := os.Create(dstPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
data := d.files[path]
|
|
|
|
if _, err := f.Write(data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-20 21:03:41 +01:00
|
|
|
// Write pages
|
2020-11-20 18:07:38 +01:00
|
|
|
for _, page := range d.Pages {
|
2021-02-28 03:53:16 +01:00
|
|
|
path, content := format.Format(page, cfg)
|
2020-11-20 18:07:38 +01:00
|
|
|
dstPath := pathpkg.Join(dstDir, path)
|
|
|
|
dir := pathpkg.Dir(dstPath)
|
|
|
|
os.MkdirAll(dir, 0755)
|
|
|
|
f, err := os.Create(dstPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, err := f.Write(content); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the index file
|
|
|
|
if d.index != nil {
|
2021-02-28 03:53:16 +01:00
|
|
|
path, content := format.Format(d.index, cfg)
|
2020-11-20 18:07:38 +01:00
|
|
|
dstPath := pathpkg.Join(dstDir, path)
|
2020-11-20 21:03:41 +01:00
|
|
|
dir := pathpkg.Dir(dstPath)
|
|
|
|
os.MkdirAll(dir, 0755)
|
2020-11-20 18:07:38 +01:00
|
|
|
f, err := os.Create(dstPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, err := f.Write(content); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write subdirectories
|
|
|
|
for _, dir := range d.Dirs {
|
|
|
|
dir.write(dstDir, format, cfg)
|
|
|
|
}
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|