Skip to content

Commit

Permalink
templates: add stateful function support
Browse files Browse the repository at this point in the history
Introduce new StatefulFuncs type where we can introduce template functions
that require other state.
  • Loading branch information
Wessie committed Jun 19, 2024
1 parent 4dfa4a4 commit 057382f
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 17 deletions.
25 changes: 23 additions & 2 deletions templates/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,34 @@ import (

radio "github.com/R-a-dio/valkyrie"
"github.com/R-a-dio/valkyrie/errors"
"github.com/R-a-dio/valkyrie/util"
)

func NewStatefulFunctions(status *util.Value[radio.Status]) *StatefulFuncs {
return &StatefulFuncs{
status: status,
}
}

type StatefulFuncs struct {
status *util.Value[radio.Status]
}

func (sf *StatefulFuncs) Status() radio.Status {
return sf.status.Latest()
}

func (sf *StatefulFuncs) FuncMap() template.FuncMap {
return map[string]any{
"Status": sf.Status,
}
}

func TemplateFuncs() template.FuncMap {
return fnMap
return defaultFunctions
}

var fnMap = map[string]any{
var defaultFunctions = map[string]any{
"printjson": PrintJSON,
"safeHTML": SafeHTML,
"safeHTMLAttr": SafeHTMLAttr,
Expand Down
37 changes: 26 additions & 11 deletions templates/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,15 @@ const (

// Site is an overarching struct containing all the themes of the website.
type Site struct {
// Production indicates if we should reload every page load
Production bool
// fnMap holds the functions we add to templates
fnMap template.FuncMap

// fs is the source fs for our template files
fs fs.FS

// mu protects the fields below it
mu sync.RWMutex
themes Themes
themeNamesPublic []string
Expand All @@ -55,7 +60,7 @@ func (s *Site) Reload() error {
s.mu.Lock()
defer s.mu.Unlock()

themes, err := LoadThemes(s.fs)
themes, err := LoadThemes(s.fs, s.fnMap)
if err != nil {
return errors.E(op, err)
}
Expand Down Expand Up @@ -183,27 +188,33 @@ func (s *Site) ResolveThemeName(name string) string {
return DEFAULT_DIR
}

func FromDirectory(dir string) (*Site, error) {
func FromDirectory(dir string, state *StatefulFuncs) (*Site, error) {
const op errors.Op = "templates/FromDirectory"

fsys := os.DirFS(dir)
s, err := FromFS(fsys)
s, err := FromFS(fsys, state)
if err != nil {
return nil, errors.E(op, err, dir)
}
return s, nil
}

func FromFS(fsys fs.FS) (*Site, error) {
func FromFS(fsys fs.FS, state *StatefulFuncs) (*Site, error) {
const op errors.Op = "templates/FromFS"

fnMap := maps.Clone(defaultFunctions)
if state != nil {
maps.Copy(fnMap, state.FuncMap())
}

var err error
tmpl := Site{
fs: fsys,
fnMap: fnMap,
cache: make(map[string]*template.Template),
}

tmpl.themes, err = LoadThemes(fsys)
tmpl.themes, err = LoadThemes(fsys, fnMap)
if err != nil {
return nil, errors.E(op, err)
}
Expand All @@ -216,7 +227,8 @@ func FromFS(fsys fs.FS) (*Site, error) {
// for the page
type TemplateBundle struct {
// fs to load the relative-filenames below
fs fs.FS
fs fs.FS
fnMap template.FuncMap
// the following fields contain all the filenames of the templates we're parsing
// into a html/template.Template. They're listed in load-order, last one wins.
base []string
Expand Down Expand Up @@ -280,7 +292,7 @@ func (tb *TemplateBundle) Files() []string {
func (tb *TemplateBundle) Template() (*template.Template, error) {
const op errors.Op = "templates/TemplateBundle.Template"

tmpl, err := createRoot().ParseFS(tb.fs, tb.Files()...)
tmpl, err := createRoot(tb.fnMap).ParseFS(tb.fs, tb.Files()...)
if err != nil {
return nil, errors.E(op, errors.TemplateParseError, err)
}
Expand All @@ -289,7 +301,7 @@ func (tb *TemplateBundle) Template() (*template.Template, error) {

// createRoot creates a root template that adds global utility functions to
// all other template files.
func createRoot() *template.Template {
func createRoot(fnMap template.FuncMap) *template.Template {
return template.New("root").Funcs(fnMap)
}

Expand Down Expand Up @@ -327,7 +339,8 @@ func (tb ThemeBundle) Assets() fs.FS {
}

type loadState struct {
fs fs.FS
fs fs.FS
fnMap template.FuncMap

baseTemplates []string
defaults loadStateDefault
Expand All @@ -339,13 +352,14 @@ type loadStateDefault struct {
bundle map[string]*TemplateBundle
}

func LoadThemes(fsys fs.FS) (Themes, error) {
func LoadThemes(fsys fs.FS, fnMap template.FuncMap) (Themes, error) {
const op errors.Op = "templates/LoadThemes"

var state loadState
var err error

state.fs = fsys
state.fnMap = fnMap

// first, we're looking for .tmpl files in the main template directory
// these are included in all other templates as a base
Expand Down Expand Up @@ -459,6 +473,7 @@ func (ls loadState) loadSubDir(dir string) (map[string]*TemplateBundle, error) {

var bundle = TemplateBundle{
fs: ls.fs,
fnMap: ls.fnMap,
base: ls.baseTemplates,
defaultPartials: ls.defaults.partials,
defaultForms: ls.defaults.forms,
Expand Down Expand Up @@ -603,7 +618,7 @@ func Definitions(fsys fs.FS, files []string) error {
}
contents := string(b)

tmpl, err := createRoot().New(noop).Parse(contents)
tmpl, err := createRoot(defaultFunctions).New(noop).Parse(contents)
if err != nil {
return errors.E(op, err)
}
Expand Down
6 changes: 3 additions & 3 deletions templates/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func TestLoadThemes(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := templates.LoadThemes(tt.args.fsys)
got, err := templates.LoadThemes(tt.args.fsys, templates.TemplateFuncs())
if (err != nil) != tt.wantErr {
t.Errorf("LoadThemes() error = %v, wantErr %v", err, tt.wantErr)
return
Expand All @@ -110,7 +110,7 @@ func FuzzLoadThemes(f *testing.F) {
}
f.Fuzz(func(t *testing.T, name string) {
rfs := randomFS(name)
themes, err := templates.LoadThemes(rfs)
themes, err := templates.LoadThemes(rfs, templates.TemplateFuncs())
if err == nil || themes != nil {
t.Errorf("there should probably be an error for %s", name)
}
Expand Down Expand Up @@ -170,7 +170,7 @@ admin-base
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := templates.FromFS(tt.args.fsys)
got, err := templates.FromFS(tt.args.fsys, nil)
if (err != nil) != tt.wantErr {
t.Errorf("FromFS() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
6 changes: 5 additions & 1 deletion website/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ func Execute(ctx context.Context, cfg config.Config) error {
// RPC values
statusValue := util.StreamValue(ctx, manager.CurrentStatus)
// templates
siteTemplates, err := templates.FromDirectory(cfg.Conf().TemplatePath)
templateFuncs := templates.NewStatefulFunctions(statusValue)
siteTemplates, err := templates.FromDirectory(
cfg.Conf().TemplatePath,
templateFuncs,
)
if err != nil {
return errors.E(op, err)
}
Expand Down

0 comments on commit 057382f

Please sign in to comment.