Skip to content

kaptinlin/template

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

112 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

template

Go Version License: MIT

A Go template engine with Django-style control flow, Liquid-style filters, and optional layout-aware HTML rendering.

For development guidelines and project conventions, see AGENTS.md.

Features

  • One entry point: Configure a single Engine for source-string parsing or loader-backed rendering.
  • Two output modes: FormatText for raw text, FormatHTML for automatic escaping of {{ expr }} output.
  • Layout on demand: WithLayout() enables include, extends, block, raw, and safe.
  • Composable loaders: NewMemoryLoader, NewDirLoader, NewFSLoader, and NewChainLoader for tests, embedded assets, and override layers.
  • Sandboxed disk reads: NewDirLoader uses os.Root so template lookups cannot escape the configured directory.
  • Typed diagnostics: *RenderError carries the failing template name, line, column, and a sentinel cause for errors.Is / errors.As.
  • Engine-local extensions: Register filters and tags on a single engine without touching global state.

Installation

go get github.com/kaptinlin/template

Requires Go 1.26+.

Quick Start

package main

import (
	"fmt"
	"log"

	"github.com/kaptinlin/template"
)

func main() {
	engine := template.New()
	tmpl, err := engine.ParseString("Hello, {{ name | upcase }}!")
	if err != nil {
		log.Fatal(err)
	}

	out, err := tmpl.Render(template.Data{"name": "alice"})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(out) // Hello, ALICE!
}

Core Concepts

Task API Notes
Parse a source string Engine.ParseString + Template.Render / Template.RenderTo Best for one-off templates
Render named templates WithLoader(...) + Engine.Render / Engine.RenderTo Compiles and caches by loader-resolved name
Choose output semantics WithFormat(FormatText) / WithFormat(FormatHTML) FormatHTML auto-escapes {{ expr }} output
Enable layout syntax WithLayout() Turns on include, extends, block, raw, and safe
Provide shared defaults WithDefaults(Data) Render-time keys override engine defaults
Extend behavior RegisterFilter, ReplaceFilter, RegisterTag, ReplaceTag Scoped to the engine instance

Loader-Backed Rendering

Use a loader when templates live outside the source string:

loader := template.NewMemoryLoader(map[string]string{
	"base.html": `<h1>{{ page.title }}</h1>{% block content %}{% endblock %}`,
	"page.html": `{% extends "base.html" %}{% block content %}{{ page.content }}{% endblock %}`,
})

engine := template.New(
	template.WithLoader(loader),
	template.WithFormat(template.FormatHTML),
	template.WithLayout(),
)

out, err := engine.Render("page.html", template.Data{
	"page": map[string]any{
		"title":   "Hello <world>",
		"content": "<p>escaped</p>",
	},
})
if err != nil {
	log.Fatal(err)
}

fmt.Println(out)
// <h1>Hello &lt;world&gt;</h1>&lt;p&gt;escaped&lt;/p&gt;

For runnable programs, see the examples below.

Errors and Diagnostics

Parse-time failures return *ParseError. Render-time failures return *RenderError, which carries the failing template name, 1-based line and column, and the underlying sentinel.

out, err := engine.Render("page.html", data)
if err != nil {
	var re *template.RenderError
	if errors.As(err, &re) {
		log.Printf("%s:%d:%d: %v", re.Template, re.Line, re.Col, re.Cause)
	}
	if errors.Is(err, template.ErrFilterNotFound) {
		// branch on the sentinel category
	}
	return err
}

Sentinels live in errors.go and are matched with errors.Is. The human-readable RenderError.Error() format is not part of the contract; read the fields for stable output.

Extension Points

Need API
Custom filter Engine.RegisterFilter, Engine.ReplaceFilter, Engine.MustRegisterFilter
Custom tag Engine.RegisterTag, Engine.ReplaceTag, Engine.MustRegisterTag
Custom loader Implement Loader and pass it through WithLoader(...)
Direct runtime control Template.Execute with RenderContext
Trusted HTML SafeString or `

Examples

Runnable examples live in examples/:

Example Path What it shows
Basic usage examples/usage Parse and render a source string through Engine
Custom filters examples/custom_filters Register an engine-local filter
Custom tags examples/custom_tags Register an engine-local tag parser
HTML layouts examples/layout WithLayout(), FormatHTML, includes, extends, and block.super
Text generation examples/multifile_text FormatText with loader-backed multi-file output
Secret redaction examples/secret_redaction Redact rendered output at the writer or filter boundary

Run any example with:

go run ./examples/<name>

Documentation

Development

task test           # Run all tests with race detection
task test-coverage  # Generate coverage.out and coverage.html
task bench          # Run benchmarks
task fmt            # Run go fmt ./...
task vet            # Run go vet ./...
task lint           # Run golangci-lint and go mod tidy checks
task verify         # Run deps, fmt, vet, lint, and test

Contributing

Contributions are welcome. See CONTRIBUTING.md for design boundaries, documentation expectations, and pull request guidance.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

Go template engine for dynamic rendering with variable interpolation and custom filters, inspired by Liquid and Django

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages