mirror of
https://git.sr.ht/~adnano/kiln
synced 2024-10-30 01:13:08 +00:00
0a2e7b6fd0
Previously, kiln would error out if any blocks defined in the base template were not defined in page or index templates. Instead, make customizing base template blocks optional. This also allows base template blocks to have a default value.
203 lines
4.6 KiB
Go
203 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
htemplate "html/template"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
pathpkg "path"
|
|
"strings"
|
|
"text/template"
|
|
)
|
|
|
|
// Template represents a template.
|
|
type Template interface {
|
|
Clone() (Template, error)
|
|
AddTemplates(Template) error
|
|
Execute(io.Writer, interface{}) error
|
|
}
|
|
|
|
type textTemplate struct {
|
|
tmpl *template.Template
|
|
}
|
|
|
|
func (t textTemplate) Clone() (Template, error) {
|
|
clone, err := t.tmpl.Clone()
|
|
return textTemplate{clone}, err
|
|
}
|
|
|
|
func (t textTemplate) AddTemplates(other Template) error {
|
|
otherTmpl := other.(textTemplate).tmpl
|
|
for _, def := range otherTmpl.Templates() {
|
|
if def.Name() == otherTmpl.Name() {
|
|
continue
|
|
}
|
|
_, err := t.tmpl.AddParseTree(def.Name(), def.Tree)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t textTemplate) Execute(w io.Writer, data interface{}) error {
|
|
return t.tmpl.Execute(w, data)
|
|
}
|
|
|
|
type htmlTemplate struct {
|
|
tmpl *htemplate.Template
|
|
}
|
|
|
|
func (t htmlTemplate) Clone() (Template, error) {
|
|
clone, err := t.tmpl.Clone()
|
|
return htmlTemplate{clone}, err
|
|
}
|
|
|
|
func (t htmlTemplate) AddTemplates(other Template) error {
|
|
otherTmpl := other.(htmlTemplate).tmpl
|
|
for _, def := range otherTmpl.Templates() {
|
|
if def.Name() == otherTmpl.Name() {
|
|
continue
|
|
}
|
|
_, err := t.tmpl.AddParseTree(def.Name(), def.Tree)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t htmlTemplate) Execute(w io.Writer, data interface{}) error {
|
|
return t.tmpl.Execute(w, data)
|
|
}
|
|
|
|
// Templates contains site templates.
|
|
type Templates struct {
|
|
tmpls map[string]Template
|
|
funcs map[string]interface{}
|
|
}
|
|
|
|
// Funcs sets the functions available to newly created templates.
|
|
func (t *Templates) Funcs(funcs map[string]interface{}) {
|
|
t.funcs = funcs
|
|
}
|
|
|
|
// LoadTemplate loads a template from the provided filenames.
|
|
func (t *Templates) LoadTemplate(fsys fs.FS, path string) error {
|
|
if t.tmpls == nil {
|
|
t.tmpls = map[string]Template{}
|
|
}
|
|
if ext := pathpkg.Ext(path); ext == ".html" || ext == ".xml" {
|
|
return t.loadHTMLTemplate(fsys, path)
|
|
}
|
|
return t.loadTextTemplate(fsys, path)
|
|
}
|
|
|
|
func (t *Templates) loadTextTemplate(fsys fs.FS, path string) error {
|
|
tmpl := template.New(path).Funcs(t.funcs)
|
|
b, err := fs.ReadFile(fsys, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := tmpl.Parse(string(b)); err != nil {
|
|
return err
|
|
}
|
|
t.tmpls[path] = textTemplate{tmpl}
|
|
return nil
|
|
}
|
|
|
|
func (t *Templates) loadHTMLTemplate(fsys fs.FS, path string) error {
|
|
tmpl := htemplate.New(path).Funcs(t.funcs)
|
|
b, err := fs.ReadFile(fsys, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := tmpl.Parse(string(b)); err != nil {
|
|
return err
|
|
}
|
|
t.tmpls[path] = htmlTemplate{tmpl}
|
|
return nil
|
|
}
|
|
|
|
// Load loads templates from the provided directory.
|
|
func (t *Templates) Load(dir string, exts []string) error {
|
|
fsys := os.DirFS(dir)
|
|
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.Type().IsRegular() || d.Type()&fs.ModeSymlink != 0 {
|
|
if err := t.LoadTemplate(fsys, path); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
|
|
// Add base templates
|
|
var extsMap = map[string]struct{}{}
|
|
for _, ext := range exts {
|
|
extsMap[ext] = struct{}{}
|
|
}
|
|
for path := range t.tmpls {
|
|
ext := pathpkg.Ext(path)
|
|
if _, ok := extsMap[ext]; !ok {
|
|
continue
|
|
}
|
|
basePath := pathpkg.Join(pathpkg.Dir(path), "base"+ext)
|
|
if path == basePath {
|
|
continue
|
|
}
|
|
if base, ok := t.tmpls[basePath]; ok {
|
|
tmpl, err := base.Clone()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Load customized template definitions
|
|
if err := tmpl.AddTemplates(t.tmpls[path]); err != nil {
|
|
return err
|
|
}
|
|
t.tmpls[path] = tmpl
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FindTemplate returns the template for the given path.
|
|
func (t *Templates) FindTemplate(path string, tmpl string) (Template, bool) {
|
|
tmplPath := pathpkg.Join(path, tmpl)
|
|
if t, ok := t.tmpls[tmplPath]; ok {
|
|
return t, true
|
|
}
|
|
if t, ok := t.tmpls[pathpkg.Join("_default", tmpl)]; ok {
|
|
return t, true
|
|
}
|
|
// Failed to find template
|
|
return nil, false
|
|
}
|
|
|
|
// FindPartial returns the partial template of the given name.
|
|
func (t *Templates) FindPartial(name string) (Template, bool) {
|
|
if t, ok := t.tmpls[pathpkg.Join("_partials", name)]; ok {
|
|
return t, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// ExecutePartial executes the partial with the given name.
|
|
func (t *Templates) ExecutePartial(name string, data interface{}) (string, error) {
|
|
tmpl, ok := t.FindPartial(name)
|
|
if !ok {
|
|
return "", fmt.Errorf("Error: partial %q not found", name)
|
|
}
|
|
var b strings.Builder
|
|
if err := tmpl.Execute(&b, data); err != nil {
|
|
return "", err
|
|
}
|
|
return b.String(), nil
|
|
}
|