Merge Page and Dir structs

This commit is contained in:
Adnan Maolood 2021-10-02 17:50:23 -04:00
parent c0fcac882a
commit 45d2f0e4cb
4 changed files with 105 additions and 132 deletions

View file

@ -10,7 +10,6 @@ import (
// funcs returns functions for use in templates. // funcs returns functions for use in templates.
func (s *Site) funcs() map[string]interface{} { func (s *Site) funcs() map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"dir": s.dir,
"exec": executeString, "exec": executeString,
"page": s.page, "page": s.page,
"path": func() _path { return _path{} }, "path": func() _path { return _path{} },

View file

@ -71,7 +71,7 @@ func (site *Site) run() error {
func (s *Site) runTask(task *Task) error { func (s *Site) runTask(task *Task) error {
// Read content // Read content
s.root = &Dir{Permalink: "/", path: ""} s.root = &Page{Permalink: "/", FilePath: ""}
if err := s.root.read("content", task, s); err != nil { if err := s.root.read("content", task, s); err != nil {
return err return err
} }

View file

@ -16,35 +16,29 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// Dir represents a directory.
type Dir struct {
Permalink string
Pages []*Page
Dirs []*Dir
index *Page // The index page.
feeds map[string][]byte // Atom/RSS/Custom feeds.
path string // relative to the content dir
}
// Page represents a page. // Page represents a page.
type Page struct { type Page struct {
Title string Title string
Date time.Time Date time.Time
Weight int Weight int
Permalink string `yaml:"-"`
FilePath string `yaml:"-"`
Content string `yaml:"-"`
Params map[string]interface{} Params map[string]interface{}
Prev *Page `yaml:"-"` FilePath string `yaml:"-"`
Next *Page `yaml:"-"` Permalink string `yaml:"-"`
Content string `yaml:"-"`
Prev *Page `yaml:"-"`
Next *Page `yaml:"-"`
Pages []*Page `yaml:"-"`
Dirs []*Page `yaml:"-"`
feeds map[string][]byte
index bool
} }
// read reads from a directory and indexes the files and directories within it. // read reads from a directory and indexes the files and directories within it.
func (d *Dir) read(srcDir string, task *Task, cfg *Site) error { func (p *Page) read(srcDir string, task *Task, cfg *Site) error {
return d._read(srcDir, "", task, cfg) return p._read(srcDir, "", task, cfg)
} }
func (d *Dir) _read(srcDir, path string, task *Task, cfg *Site) error { func (p *Page) _read(srcDir, path string, task *Task, cfg *Site) error {
entries, err := ioutil.ReadDir(pathpkg.Join(srcDir, path)) entries, err := ioutil.ReadDir(pathpkg.Join(srcDir, path))
if err != nil { if err != nil {
return err return err
@ -58,11 +52,11 @@ func (d *Dir) _read(srcDir, path string, task *Task, cfg *Site) error {
continue continue
} }
// Gather directory data // Gather directory data
dir := &Dir{Permalink: "/" + path + "/", path: path} dir := &Page{Permalink: "/" + path + "/", FilePath: path}
if err := dir._read(srcDir, path, task, cfg); err != nil { if err := dir._read(srcDir, path, task, cfg); err != nil {
return err return err
} }
d.Dirs = append(d.Dirs, dir) p.Dirs = append(p.Dirs, dir)
} else if ext := pathpkg.Ext(name); task.Match(ext) { } else if ext := pathpkg.Ext(name); task.Match(ext) {
// Ignore pages beginning with "_" with the exception of _index pages // Ignore pages beginning with "_" with the exception of _index pages
namePrefix := strings.TrimSuffix(name, ext) namePrefix := strings.TrimSuffix(name, ext)
@ -76,7 +70,13 @@ func (d *Dir) _read(srcDir, path string, task *Task, cfg *Site) error {
return err return err
} }
page := &Page{} page := &Page{
FilePath: path,
}
if namePrefix == "_index" {
p.index = true
page = p
}
// Try to parse the date from the page filename // Try to parse the date from the page filename
const layout = "2006-01-02" const layout = "2006-01-02"
@ -123,12 +123,7 @@ func (d *Dir) _read(srcDir, path string, task *Task, cfg *Site) error {
} }
page.Content = string(content) page.Content = string(content)
page.FilePath = path if !page.index {
if namePrefix == "_index" {
page.Permalink = d.Permalink
d.index = page
} else {
if namePrefix == "index" { if namePrefix == "index" {
path = "/" + strings.TrimSuffix(path, name) path = "/" + strings.TrimSuffix(path, name)
} else { } else {
@ -140,12 +135,12 @@ func (d *Dir) _read(srcDir, path string, task *Task, cfg *Site) error {
} }
} }
page.Permalink = path page.Permalink = path
if permalink, ok := cfg.permalinks[d.Permalink]; ok { if permalink, ok := cfg.permalinks[p.Permalink]; ok {
var b strings.Builder var b strings.Builder
permalink.Execute(&b, page) permalink.Execute(&b, page)
page.Permalink = b.String() page.Permalink = b.String()
} }
d.Pages = append(d.Pages, page) p.Pages = append(p.Pages, page)
} }
} }
} }
@ -153,44 +148,44 @@ func (d *Dir) _read(srcDir, path string, task *Task, cfg *Site) error {
} }
// process processes the directory's contents. // process processes the directory's contents.
func (d *Dir) process(cfg *Site, task *Task) error { func (p *Page) process(cfg *Site, task *Task) error {
// Build feeds before templates are applied to the page contents // Build feeds before templates are applied to the page contents
for _, feed := range task.feeds[d.path] { for _, feed := range task.feeds[p.FilePath] {
b, err := d.buildFeed(cfg, feed) b, err := p.buildFeed(cfg, feed)
if err != nil { if err != nil {
return err return err
} }
d.addFeed(feed.Output, b) p.addFeed(feed.Output, b)
} }
if task.TemplateExt != "" { if task.TemplateExt != "" {
// Create index // Create index
if d.index != nil { if p.index {
tmpl, ok := cfg.templates.FindTemplate(d.Permalink, "index"+task.TemplateExt) tmpl, ok := cfg.templates.FindTemplate(p.Permalink, "index"+task.TemplateExt)
if ok { if ok {
var b strings.Builder var b strings.Builder
if err := tmpl.Execute(&b, d); err != nil { if err := tmpl.Execute(&b, p); err != nil {
return err return err
} }
d.index.Content = b.String() p.Content = b.String()
} }
} }
// Process pages // Process pages
for i := range d.Pages { for i := range p.Pages {
var b strings.Builder var b strings.Builder
tmpl, ok := cfg.templates.FindTemplate(d.Permalink, "page"+task.TemplateExt) tmpl, ok := cfg.templates.FindTemplate(p.Permalink, "page"+task.TemplateExt)
if ok { if ok {
if err := tmpl.Execute(&b, d.Pages[i]); err != nil { if err := tmpl.Execute(&b, p.Pages[i]); err != nil {
return err return err
} }
d.Pages[i].Content = b.String() p.Pages[i].Content = b.String()
} }
} }
} }
// Process subdirectories // Process subdirectories
for _, d := range d.Dirs { for _, d := range p.Dirs {
if err := d.process(cfg, task); err != nil { if err := d.process(cfg, task); err != nil {
return err return err
} }
@ -199,7 +194,7 @@ func (d *Dir) process(cfg *Site, task *Task) error {
} }
// buildFeed build the feed of the directory // buildFeed build the feed of the directory
func (d Dir) buildFeed(cfg *Site, feed Feed) ([]byte, error) { func (p *Page) buildFeed(cfg *Site, feed Feed) ([]byte, error) {
// Feed represents a feed. // Feed represents a feed.
type Feed struct { type Feed struct {
Title string Title string
@ -207,7 +202,7 @@ func (d Dir) buildFeed(cfg *Site, feed Feed) ([]byte, error) {
Pages []*Page Pages []*Page
} }
tmpl, ok := cfg.templates.FindTemplate(d.Permalink, feed.Template) tmpl, ok := cfg.templates.FindTemplate(p.Permalink, feed.Template)
if !ok { if !ok {
return nil, fmt.Errorf("failed to generate feed %q: missing feed template %q", feed.Title, feed.Template) return nil, fmt.Errorf("failed to generate feed %q: missing feed template %q", feed.Title, feed.Template)
} }
@ -215,8 +210,8 @@ func (d Dir) buildFeed(cfg *Site, feed Feed) ([]byte, error) {
var b bytes.Buffer var b bytes.Buffer
data := Feed{ data := Feed{
Title: feed.Title, Title: feed.Title,
Permalink: d.Permalink, Permalink: p.Permalink,
Pages: d.Pages, Pages: p.Pages,
} }
if err := tmpl.Execute(&b, data); err != nil { if err := tmpl.Execute(&b, data); err != nil {
return nil, err return nil, err
@ -224,48 +219,37 @@ func (d Dir) buildFeed(cfg *Site, feed Feed) ([]byte, error) {
return b.Bytes(), nil return b.Bytes(), nil
} }
func (d *Dir) addFeed(name string, content []byte) { func (p *Page) addFeed(name string, content []byte) {
if d.feeds == nil { if p.feeds == nil {
d.feeds = map[string][]byte{} p.feeds = map[string][]byte{}
} }
d.feeds[name] = content p.feeds[name] = content
} }
// write writes the directory's contents to the provided destination path. // write writes the directory's contents to the provided destination path.
func (d *Dir) write(dstDir string, task *Task) error { func (p *Page) write(dstDir string, task *Task) error {
dirPath := pathpkg.Join(dstDir, d.Permalink) dirPath := pathpkg.Join(dstDir, p.Permalink)
// Write pages // Write pages
pages := d.Pages for _, page := range p.Pages {
if d.index != nil { dstPath := pathpkg.Join(dstDir, page.Permalink)
pages = append(pages, d.index) if !task.UglyURLs {
dstPath = pathpkg.Join(dstPath, "index"+task.OutputExt)
}
if err := page.writeTo(dstPath, task); err != nil {
return err
}
} }
for _, page := range pages { // Write index page
path := page.Permalink if p.index {
if !task.UglyURLs || page == d.index { dstPath := pathpkg.Join(dirPath, p.Permalink, "index"+task.OutputExt)
path = pathpkg.Join(path, "index"+task.OutputExt) if err := p.writeTo(dstPath, task); err != nil {
}
var content []byte
if cmd := task.Postprocess; cmd != "" {
var buf bytes.Buffer
if err := execute(cmd, strings.NewReader(page.Content), &buf); err != nil {
return err
}
content = buf.Bytes()
} 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 return err
} }
} }
// Write feeds // Write feeds
for name, content := range d.feeds { for name, content := range p.feeds {
dstPath := pathpkg.Join(dstDir, name) dstPath := pathpkg.Join(dstDir, name)
os.MkdirAll(dirPath, 0755) os.MkdirAll(dirPath, 0755)
if err := os.WriteFile(dstPath, content, 0644); err != nil { if err := os.WriteFile(dstPath, content, 0644); err != nil {
@ -274,40 +258,60 @@ func (d *Dir) write(dstDir string, task *Task) error {
} }
// Write subdirectories // Write subdirectories
for _, dir := range d.Dirs { for _, dir := range p.Dirs {
dir.write(dstDir, task) dir.write(dstDir, task)
} }
return nil return nil
} }
func (p *Page) writeTo(dstPath string, task *Task) error {
var content []byte
if cmd := task.Postprocess; cmd != "" {
var buf bytes.Buffer
if err := execute(cmd, strings.NewReader(p.Content), &buf); err != nil {
return err
}
content = buf.Bytes()
} else {
content = []byte(p.Content)
}
dir := pathpkg.Dir(dstPath)
os.MkdirAll(dir, 0755)
if err := os.WriteFile(dstPath, content, 0644); err != nil {
return err
}
return nil
}
// sort sorts the directory's pages by weight, then date, then filepath. // sort sorts the directory's pages by weight, then date, then filepath.
func (d *Dir) sort() { func (p *Page) sort() {
sort.Slice(d.Pages, func(i, j int) bool { sort.Slice(p.Pages, func(i, j int) bool {
pi, pj := d.Pages[i], d.Pages[j] pi, pj := p.Pages[i], p.Pages[j]
return pi.FilePath < pj.FilePath return pi.FilePath < pj.FilePath
}) })
sort.SliceStable(d.Pages, func(i, j int) bool { sort.SliceStable(p.Pages, func(i, j int) bool {
pi, pj := d.Pages[i], d.Pages[j] pi, pj := p.Pages[i], p.Pages[j]
return pi.Date.After(pj.Date) return pi.Date.After(pj.Date)
}) })
sort.SliceStable(d.Pages, func(i, j int) bool { sort.SliceStable(p.Pages, func(i, j int) bool {
pi, pj := d.Pages[i], d.Pages[j] pi, pj := p.Pages[i], p.Pages[j]
return pi.Weight < pj.Weight return pi.Weight < pj.Weight
}) })
for i := range d.Pages { for i := range p.Pages {
if i-1 >= 0 { if i-1 >= 0 {
d.Pages[i].Prev = d.Pages[i-1] p.Pages[i].Prev = p.Pages[i-1]
} }
if i+1 < len(d.Pages) { if i+1 < len(p.Pages) {
d.Pages[i].Next = d.Pages[i+1] p.Pages[i].Next = p.Pages[i+1]
} }
} }
// Sort subdirectories // Sort subdirectories
for _, d := range d.Dirs { for _, d := range p.Dirs {
d.sort() d.sort()
} }
} }
@ -326,48 +330,22 @@ func execute(command string, input io.Reader, output io.Writer) error {
return nil return nil
} }
func (d *Dir) Title() string { func (p *Page) getPage(path string) *Page {
return d.index.Title
}
func (d *Dir) Date() time.Time {
return d.index.Date
}
func (d *Dir) Content() string {
return d.index.Content
}
func (d *Dir) Params() map[string]interface{} {
return d.index.Params
}
func (d *Dir) getDir(path string) *Dir {
// XXX: This is inefficient // XXX: This is inefficient
if d.Permalink == path { if p.Permalink == path {
return d return p
} }
for _, dir := range d.Dirs { for _, page := range p.Pages {
if dir.Permalink == path {
return dir
}
}
for i := range d.Dirs {
if dir := d.Dirs[i].getDir(path); dir != nil {
return dir
}
}
return nil
}
func (d *Dir) getPage(path string) *Page {
// XXX: This is inefficient
for _, page := range d.Pages {
if page.FilePath == path { if page.FilePath == path {
return page return page
} }
} }
for _, dir := range d.Dirs { for _, dir := range p.Dirs {
if dir.Permalink == path {
return dir
}
}
for _, dir := range p.Dirs {
if page := dir.getPage(path); page != nil { if page := dir.getPage(path); page != nil {
return page return page
} }

View file

@ -18,7 +18,7 @@ type Site struct {
Generated time.Time `toml:"-"` Generated time.Time `toml:"-"`
permalinks map[string]*template.Template permalinks map[string]*template.Template
templates Templates templates Templates
root *Dir root *Page
} }
// Task represents a site build task. // Task represents a site build task.
@ -103,10 +103,6 @@ func LoadSite(config string) (*Site, error) {
return site, nil return site, nil
} }
func (s *Site) dir(path string) *Dir {
return s.root.getDir(path)
}
func (s *Site) page(path string) *Page { func (s *Site) page(path string) *Page {
return s.root.getPage(path) return s.root.getPage(path)
} }