goldsmith/plugins/collection/collection.go

132 lines
3.4 KiB
Go

// Package collection groups related pages into named collections. This can be
// useful for presenting blog posts on your front page, and displaying summary
// information about other types of content on your website. It can be used in
// conjunction with the "pager" plugin to create large collections which are
// split across several pages.
package collection
import (
"sort"
"strings"
"sync"
"git.foosoft.net/alex/goldsmith"
"git.foosoft.net/alex/goldsmith/filters/wildcard"
)
// A Comparer callback function is used to sort files within a collection group.
type Comparer func(i, j goldsmith.File) (less bool)
// Collection chainable plugin context.
type Collection struct {
collectionKey string
groupsKey string
comparer Comparer
groups map[string][]goldsmith.File
files []goldsmith.File
mutex sync.Mutex
}
// New creates a new instance of the Collection plugin.
func New() *Collection {
return &Collection{
collectionKey: "Collection",
groupsKey: "Groups",
groups: make(map[string][]goldsmith.File),
}
}
// CollectionKey sets the metadata key used to access the collection name (default: "Collection").
// The metadata associated with this key can be either a single string or an array of strings.
func (self *Collection) CollectionKey(collectionKey string) *Collection {
self.collectionKey = collectionKey
return self
}
// GroupsKey sets the metadata key used to store information about collection groups (default: "Groups").
// This information is stored as a mapping of group names to contained files.
func (self *Collection) GroupsKey(groupsKey string) *Collection {
self.groupsKey = groupsKey
return self
}
// Comparer sets the function used to sort files in collection groups (default: sort by filenames).
func (plugin *Collection) Comparer(comparer Comparer) *Collection {
plugin.comparer = comparer
return plugin
}
func (*Collection) Name() string {
return "collection"
}
func (*Collection) Initialize(context goldsmith.Context) error {
context.Filter(wildcard.New("**/*.html", "**/*.htm"))
return nil
}
func (self *Collection) Process(context goldsmith.Context, inputFile goldsmith.File) error {
self.mutex.Lock()
defer func() {
inputFile.SetProp(self.groupsKey, self.groups)
self.files = append(self.files, inputFile)
self.mutex.Unlock()
}()
collectionRaw, ok := inputFile.Prop(self.collectionKey)
if !ok {
return nil
}
var collectionNames []string
switch t := collectionRaw.(type) {
case string:
collectionNames = append(collectionNames, t)
case []string:
collectionNames = append(collectionNames, t...)
}
for _, collectionName := range collectionNames {
files, _ := self.groups[collectionName]
files = append(files, inputFile)
self.groups[collectionName] = files
}
return nil
}
func (self *Collection) Finalize(context goldsmith.Context) error {
for _, files := range self.groups {
fg := &fileSorter{files, self.comparer}
sort.Sort(fg)
}
for _, file := range self.files {
context.DispatchFile(file)
}
return nil
}
type fileSorter struct {
files []goldsmith.File
comparer Comparer
}
func (self fileSorter) Len() int {
return len(self.files)
}
func (self fileSorter) Swap(i, j int) {
self.files[i], self.files[j] = self.files[j], self.files[i]
}
func (self fileSorter) Less(i, j int) bool {
if self.comparer == nil {
return strings.Compare(self.files[i].Path(), self.files[j].Path()) < 0
}
return self.comparer(self.files[i], self.files[j])
}