templator

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 9, 2025 License: MIT Imports: 9 Imported by: 0

README

Templator

codecov Go Report Card Go Reference

A type-safe HTML template rendering engine for Go.

Note: This is an experimental project in active development. While it's stable enough for use, expect possible API changes. Feedback and contributions are welcome!

Table of Contents

Problem Statement

Go's built-in template package lacks type safety, which can lead to runtime errors when template data doesn't match what the template expects. For example:

// Traditional approach with Go templates
tmpl := template.Must(template.ParseFiles("home.html"))

// This compiles but will fail at runtime if the template expects different fields
tmpl.Execute(w, struct {
    WrongField string
    MissingRequired int
}{})

// No compile-time checks for:
// - Missing required fields
// - Wrong field types
// - Typos in field names

Templator solves this by providing compile-time type checking for your templates:

// Define your template data type
type HomeData struct {
    Title   string
    Content string
}

// Initialize registry with type parameter
reg, _ := templator.NewRegistry[HomeData](fs)

// Get type-safe handler and execute template
home, _ := reg.GetHome()
home.Execute(ctx, w, HomeData{
    Title: "Welcome",
    Content: "Hello",
})

// Won't compile - wrong data structure
home.Execute(ctx, w, struct{
    WrongField string
}{})

Features

  • Type-safe template execution with generics
  • Concurrent-safe template management
  • Custom template functions support
  • Clean and simple API
  • HTML escaping by default

Installation

go install github.com/alesr/templator

Quick Start

package main

import (
    "context"
    "log"
    "os"

    "github.com/alesr/templator"
)

// Define your template data
type HomeData struct {
    Title   string
    Content string
}

func main() {
    // Use the filesystem of your choice
    fs := os.DirFS(".")

    // Initialize registry with your data type
    reg, err := templator.NewRegistry[HomeData](fs)
    if err != nil {
        log.Fatal(err)
    }

    // Get type-safe handler for home template
    home, err := reg.GetHome()
    if err != nil {
        log.Fatal(err)
    }

    // Execute template with proper data
    err = home.Execute(context.Background(), os.Stdout, HomeData{
        Title:   "Welcome",
        Content: "Hello, World!",
    })
    if err != nil {
        log.Fatal(err)
    }
}

Usage Examples

Type-Safe Templates
// Define different data types for different templates
type HomeData struct {
    Title    string
    Content  string
}

type AboutData struct {
    Company  string
    Year     int
}

// Create registries for different template types
homeReg := templator.NewRegistry[HomeData](fs)
aboutReg := templator.NewRegistry[AboutData](fs)

// Get handlers
home, _ := homeReg.GetHome()
about, _ := aboutReg.GetAbout()

// Type safety enforced at compile time
home.Execute(ctx, w, HomeData{...})  // ✅ Compiles
home.Execute(ctx, w, AboutData{...}) // ❌ Compile error
Using Template Functions
// Define your custom functions
funcMap := template.FuncMap{
    "upper": strings.ToUpper,
    "lower": strings.ToLower,
}

// Create registry with functions
reg, err := templator.NewRegistry[PageData](fs, 
    templator.WithTemplateFuncs[PageData](funcMap))

// Use functions in your templates:
// <h1>{{.Title | upper}}</h1>
File System Support
// Embedded FS
//go:embed templates/*
var embedFS embed.FS
reg := templator.NewRegistry[HomeData](embedFS)

// OS File System
reg := templator.NewRegistry[HomeData](os.DirFS("./templates"))

// In-Memory (testing)
fsys := fstest.MapFS{
    "templates/home.html": &fstest.MapFile{
        Data: []byte(`<h1>{{.Title}}</h1>`),
    },
}
reg := templator.NewRegistry[HomeData](fsys)
Field Validation
type ArticleData struct {
    Title    string    // Only these two fields
    Content  string    // are allowed in templates
}

// Enable validation during registry creation
reg := templator.NewRegistry[ArticleData](
    fs,
    templator.WithFieldValidation(ArticleData{}),
)

// Example templates:

// valid.html:
// <h1>{{.Title}}</h1>           // ✅ OK - Title exists in ArticleData
// <p>{{.Content}}</p>           // ✅ OK - Content exists in ArticleData

// invalid.html:
// <h1>{{.Author}}</h1>          // ❌ Error - Author field doesn't exist
// <p>{{.PublishedAt}}</p>       // ❌ Error - PublishedAt field doesn't exist

// Using the templates:
handler, err := reg.Get("valid")    // ✅ Success - all fields exist
if err != nil {
    log.Fatal(err)
}

handler, err := reg.Get("invalid")  // ❌ Error: "template 'invalid' validation error: Author - field 'Author' not found in type ArticleData"

// The validation error provides:
// - Template name
// - Invalid field path
// - Detailed error message
if validErr, ok := err.(*templator.ValidationError); ok {
    fmt.Printf("Template: %s\n", validErr.TemplateName)
    fmt.Printf("Invalid field: %s\n", validErr.FieldPath)
    fmt.Printf("Error: %v\n", validErr.Err)
}

This validation happens when loading the template, not during execution, helping catch field mismatches early in development.

Template Generation

Templates are automatically discovered and type-safe methods are generated:

templates/
├── home.html           -> reg.GetHome()
├── about.html          -> reg.GetAbout()
└── components/
    └── header.html     -> reg.GetComponentsHeader()

# Generate methods
go generate ./...

The generation process creates a templator_methods.go file containing type-safe method handlers for each template. For example:

// Code generated by go generate; DO NOT EDIT.
func (r *Registry[T]) GetHome() (*Handler[T], error) {
    return r.Get("home")
}

func (r *Registry[T]) GetAbout() (*Handler[T], error) {
    return r.Get("about")
}

This file is automatically generated and should not be manually edited.

Configuration

reg, err := templator.NewRegistry[HomeData](
    fs,
    // Custom template directory
    templator.WithTemplatesPath[HomeData]("views"),
    // Enable field validation
    templator.WithFieldValidation(HomeData{}),
)
Development Requirements
  • Go 1.21 or higher

License

MIT

Documentation

Overview

Package templator provides a type-safe template rendering system for Go applications. It offers a simple and concurrent-safe way to manage HTML templates with compile-time type checking for template data.

Example
package main

import (
	"bytes"
	"context"
	"fmt"
	"testing/fstest"

	"github.com/alesr/templator"
)

func main() {
	// Create template files in memory
	fs := fstest.MapFS{
		"templates/home.html": &fstest.MapFile{
			Data: []byte(`<h1>{{.Title}}</h1><p>{{.Content}}</p>`),
		},
		"templates/team.html": &fstest.MapFile{
			Data: []byte(`<h2>{{.Title}}</h2><p>{{.Content}}</p>`),
		},
	}

	// Create a new registry
	reg, _ := templator.NewRegistry[templator.TestData](fs)

	// Get and execute home template
	home, _ := reg.Get("home")
	var homeOutput bytes.Buffer
	home.Execute(context.Background(), &homeOutput, templator.TestData{
		Title:   "Welcome",
		Content: "Hello, World!",
	})

	// Get and execute team template
	team, _ := reg.Get("team")
	var teamOutput bytes.Buffer
	team.Execute(context.Background(), &teamOutput, templator.TestData{
		Title:   "Engineering",
		Content: "Building amazing things",
	})

	// Print the outputs
	fmt.Printf("Home template output:\n%s\n\n", homeOutput.String())
	fmt.Printf("Team template output:\n%s\n", teamOutput.String())

}
Output:

Home template output:
<h1>Welcome</h1><p>Hello, World!</p>

Team template output:
<h2>Engineering</h2><p>Building amazing things</p>

Index

Examples

Constants

View Source
const (
	// DefaultTemplateDir is the default directory where templates are stored.
	DefaultTemplateDir = "templates"
	// DefaultTemplateExt is the default file extension for templates.
	DefaultTemplateExt = "html"

	// ExtensionHTML defines the standard HTML template file extension.
	ExtensionHTML Extension = ".html"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type ErrTemplateExecution

type ErrTemplateExecution struct {
	Name string
	Err  error
}

ErrTemplateExecution is returned when a template fails to execute.

func (ErrTemplateExecution) Error

func (e ErrTemplateExecution) Error() string

func (ErrTemplateExecution) Unwrap

func (e ErrTemplateExecution) Unwrap() error

type ErrTemplateNotFound

type ErrTemplateNotFound struct {
	Name string
}

ErrTemplateNotFound is returned when a template cannot be found.

func (ErrTemplateNotFound) Error

func (e ErrTemplateNotFound) Error() string

type Extension

type Extension string

Extension represents a template file extension type.

type Handler

type Handler[T any] struct {
	// contains filtered or unexported fields
}

Handler manages a specific template instance with type-safe data handling. It provides methods for template execution and customization.

func (*Handler[T]) Execute

func (h *Handler[T]) Execute(ctx context.Context, w io.Writer, data T) error

Execute renders the template with the provided data and writes the output to the writer. The context parameter can be used for cancellation and deadline control.

type Option

type Option[T any] func(*Registry[T])

Option configures a Registry instance.

func WithFieldValidation

func WithFieldValidation[T any](model T) Option[T]

WithFieldValidation enables template field validation against the provided model

func WithTemplateFuncs

func WithTemplateFuncs[T any](funcMap template.FuncMap) Option[T]

func WithTemplatesPath

func WithTemplatesPath[T any](path string) Option[T]

WithTemplatesPath returns an Option that sets a custom template directory path. If an empty path is provided, the default path will be used.

type Registry

type Registry[T any] struct {
	// contains filtered or unexported fields
}

Registry manages template handlers in a concurrent-safe manner.

func NewRegistry

func NewRegistry[T any](fsys fs.FS, opts ...Option[T]) (*Registry[T], error)

NewRegistry creates a new template registry with the provided filesystem and options. It accepts a filesystem interface and variadic options for customization.

func (*Registry[T]) Get

func (r *Registry[T]) Get(name string) (*Handler[T], error)

Get retrieves or creates a type-safe handler for a specific template. It automatically appends the .html extension to the template name. Returns an error if the template cannot be parsed.

type ValidationError

type ValidationError struct {
	TemplateName string
	FieldPath    string
	Err          error
}

func (*ValidationError) Error

func (e *ValidationError) Error() string

Directories

Path Synopsis
cmd
generate command
Package main provides a code generator for creating type-safe template handler methods.
Package main provides a code generator for creating type-safe template handler methods.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL