Skip to content

Generate typed accessors

reg.Get("components/header") is a stringly-typed API inside an otherwise type-safe flow: rename a file and the compiler stays silent. The accessor generator closes that gap. It scans your templates directory and emits a typed wrapper — one method per template:

home, err := tpl.GetHome() // templates/home.html
header, err := tpl.GetComponentsHeader() // templates/components/header.html

The generated code calls registry.Get under the hood, so behavior is identical — this is purely developer ergonomics plus refactor safety.

Terminal window
go run github.com/alesr/templator/cmd/generate \
-package main \
-templates ./templates \
-out ./templator_accessors_gen.go

Or install it once and run as a command:

Terminal window
go install github.com/alesr/templator/cmd/generate@latest

All flags and defaults are in the Generator CLI reference.

Given:

templates/
home.html
about.html
components/header.html

the generator writes templator_accessors_gen.go (methods are emitted in the lexical order of the walk, so about sorts before components/ before home):

// Code generated by go generate; DO NOT EDIT.
package main
import "github.com/alesr/templator"
// TemplateAccessors provides typed helper accessors over templator Registry.
type TemplateAccessors[T any] struct {
registry *templator.Registry[T]
}
// NewTemplateAccessors creates a TemplateAccessors wrapper for a templator Registry.
func NewTemplateAccessors[T any](registry *templator.Registry[T]) *TemplateAccessors[T] {
return &TemplateAccessors[T]{registry: registry}
}
// GetAbout returns a handler for the about template.
func (r *TemplateAccessors[T]) GetAbout() (*templator.Handler[T], error) {
return r.registry.Get("about")
}
// GetComponentsHeader returns a handler for the components/header template.
func (r *TemplateAccessors[T]) GetComponentsHeader() (*templator.Handler[T], error) {
return r.registry.Get("components/header")
}
// GetHome returns a handler for the home template.
func (r *TemplateAccessors[T]) GetHome() (*templator.Handler[T], error) {
return r.registry.Get("home")
}

Naming rule: each path segment is title-cased (English casing, via golang.org/x/text/cases) and the segments are joined after Get. The template name keeps its slash-separated form (components/header), which is exactly what Registry.Get expects (Registry and handlers). The output is gofmt-formatted.

Add the directive to one of your source files so regeneration is one command:

//go:generate go run github.com/alesr/templator/cmd/generate -package main -templates ./templates -out ./templator_accessors_gen.go
Terminal window
go generate ./...
fs := os.DirFS(".")
reg, _ := templator.NewRegistry[HomeData](fs)
tpl := NewTemplateAccessors(reg)
home, err := tpl.GetHome()
if err != nil {
log.Fatal(err)
}
// home is *templator.Handler[HomeData] — same full behavior:
err = home.Execute(ctx, w, HomeData{Title: "Welcome"})

TemplateAccessors[T] keeps the generic type parameter, so it works with any registry without boxing handlers away from the type-safe Handler[T] API.

  • Commit the generated file — it is small, reviewable, and keeps builds hermetic.
  • Regenerate on template inventory changes: adding, removing, or renaming a template requires a go generate ./... pass. If you forget, the build fails on stale or missing methods (GetAbout used without existing) — which is the same discipline as any _gen.go file.
  • Never hand-edit: the file is gofmt’d, deterministic, and DO NOT EDIT-marked for a reason; change the templates and regenerate instead.