115 lines
3.1 KiB
Go
115 lines
3.1 KiB
Go
// Package tplx wraps the standard html/template library to provide a little more
|
|
// structure and ease of use.
|
|
package tplx
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"io/fs"
|
|
)
|
|
|
|
var (
|
|
// ErrUnknownTemplate is returned when a template is not found in the renderer.
|
|
ErrUnknownTemplate = errors.New("template not found in renderer")
|
|
|
|
// ErrInvalidSpec is returned when the template renderer specification is invalid.
|
|
ErrInvalidSpec = errors.New("template renderer spec is invalid")
|
|
)
|
|
|
|
// Renderer is an interface for rendering templates.
|
|
type Renderer interface {
|
|
Render(w io.Writer, name string, data any, funcs template.FuncMap) error
|
|
}
|
|
|
|
type renderer struct {
|
|
m map[string]*template.Template
|
|
}
|
|
|
|
// Spec describes the structure of all templates managed by the renderer.
|
|
//
|
|
// The keys of the Spec map represent top-level template names. Each key maps
|
|
// to a slice of Meta, where each Meta defines the name, path, and functions
|
|
// associated with a template fragment.
|
|
type Spec map[string][]Meta
|
|
|
|
// Meta represents metadata for a single template fragment.
|
|
//
|
|
// Name specifies the name of the template fragment. Path specifies the path to
|
|
// the template file in the file system. Funcs provides template-specific
|
|
// functions.
|
|
type Meta struct {
|
|
Name string
|
|
Path string
|
|
Funcs template.FuncMap
|
|
}
|
|
|
|
// NewRenderer creates a new Renderer instance from a file system, specification,
|
|
// and global function map.
|
|
//
|
|
// The fsys parameter specifies the file system from which template files are
|
|
// loaded. The spec parameter defines the structure of the templates, mapping
|
|
// top-level template names to their fragments. The funcs parameter provides
|
|
// global template functions.
|
|
//
|
|
// Returns a Renderer instance or an error if the templates cannot be initialized
|
|
// according to the specification.
|
|
func NewRenderer(fsys fs.FS, spec Spec, funcs template.FuncMap) (Renderer, error) {
|
|
r := renderer{
|
|
m: make(map[string]*template.Template, len(spec)),
|
|
}
|
|
|
|
for name, metas := range spec {
|
|
inc := false
|
|
|
|
t := template.New(name).Funcs(funcs)
|
|
|
|
for _, meta := range metas {
|
|
if meta.Name == name {
|
|
inc = true
|
|
}
|
|
|
|
text, err := fs.ReadFile(fsys, meta.Path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to read template file: %w", err)
|
|
}
|
|
|
|
t = t.New(meta.Name).Funcs(meta.Funcs)
|
|
|
|
t, err = t.Parse(string(text))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if !inc {
|
|
return nil, ErrInvalidSpec
|
|
}
|
|
|
|
r.m[name] = t
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// Render writes the rendered output of a named template to the provided writer.
|
|
//
|
|
// The wr parameter specifies the writer where the rendered template output will
|
|
// be written. The name parameter specifies the name of the template to render
|
|
// The data parameter provides the context data for rendering, and the funcs
|
|
// parameter provides additional template functions.
|
|
//
|
|
// Returns an error if the template cannot be rendered or does not exist.
|
|
func (r renderer) Render(wr io.Writer, name string, data any, funcs template.FuncMap) error {
|
|
t, ok := r.m[name]
|
|
if !ok {
|
|
return ErrUnknownTemplate
|
|
}
|
|
err := t.ExecuteTemplate(wr, name, data)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot render template: %w", err)
|
|
}
|
|
return nil
|
|
}
|