goldsmith/plugins/layout/layout.go

142 lines
3.4 KiB
Go

// Package layout transforms content by applying Go templates to the content
// and metadata of HTML files. This plugin can be easily used with the
// "frontmatter" and "markdown" plugins to generate easy to maintain
// content-driven websites that are completely decoupled from layout details.
package layout
import (
"bytes"
"html/template"
"sync"
"git.foosoft.net/alex/goldsmith"
"git.foosoft.net/alex/goldsmith/filters/wildcard"
)
// Layout chainable context.
type Layout struct {
layoutKey string
contentKey string
defaultLayout *string
helpers template.FuncMap
inputFiles []*goldsmith.File
templateFiles []*goldsmith.File
mutex sync.Mutex
template *template.Template
}
// New creates a new instance of the Layout plugin.
func New() *Layout {
return &Layout{
layoutKey: "Layout",
contentKey: "Content",
helpers: nil,
}
}
// LayoutKey sets the metadata key used to access the layout identifier (default: "Layout").
func (self *Layout) LayoutKey(key string) *Layout {
self.layoutKey = key
return self
}
// DefaultLayout sets the name of the layout to use if none is specified.
func (self *Layout) DefaultLayout(name string) *Layout {
self.defaultLayout = &name
return self
}
// ContentKey sets the metadata key used to access the source content (default: "Content").
func (self *Layout) ContentKey(key string) *Layout {
self.contentKey = key
return self
}
// Helpers sets the function map used to lookup template helper functions.
func (self *Layout) Helpers(helpers template.FuncMap) *Layout {
self.helpers = helpers
return self
}
func (*Layout) Name() string {
return "layout"
}
func (self *Layout) Initialize(context *goldsmith.Context) error {
self.template = template.New("").Funcs(self.helpers)
context.Filter(wildcard.New("**/*.html", "**/*.htm", "**/*.tmpl", "**/*.gohtml"))
return nil
}
func (self *Layout) Process(context *goldsmith.Context, inputFile *goldsmith.File) error {
self.mutex.Lock()
defer self.mutex.Unlock()
switch inputFile.Ext() {
case ".html", ".htm":
if _, ok := self.getFileLayout(inputFile); ok {
var buff bytes.Buffer
if _, err := inputFile.WriteTo(&buff); err != nil {
return err
}
inputFile.SetProp(self.contentKey, template.HTML(buff.Bytes()))
self.inputFiles = append(self.inputFiles, inputFile)
} else {
context.DispatchFile(inputFile)
}
case ".tmpl", ".gohtml":
self.templateFiles = append(self.templateFiles, inputFile)
}
return nil
}
func (self *Layout) Finalize(context *goldsmith.Context) error {
for _, templateFile := range self.templateFiles {
var buff bytes.Buffer
if _, err := templateFile.WriteTo(&buff); err != nil {
return err
}
if _, err := self.template.Parse(string(buff.Bytes())); err != nil {
return err
}
}
for _, inputFile := range self.inputFiles {
if name, ok := self.getFileLayout(inputFile); ok {
var buff bytes.Buffer
if err := self.template.ExecuteTemplate(&buff, name, inputFile); err != nil {
return err
}
outputFile, err := context.CreateFileFromReader(inputFile.Path(), &buff)
if err != nil {
return err
}
outputFile.CopyProps(inputFile)
context.DispatchFile(outputFile)
} else {
context.DispatchFile(inputFile)
}
}
return nil
}
func (self *Layout) getFileLayout(file *goldsmith.File) (string, bool) {
if name, ok := file.Props()[self.layoutKey].(string); ok {
return name, true
}
if self.defaultLayout != nil {
return *self.defaultLayout, true
}
return "", false
}