template

package module
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Mar 1, 2026 License: MIT Imports: 16 Imported by: 0

README

Template Engine

A lightweight Go template engine with Liquid/Django-style syntax, supporting variable interpolation, filters, conditionals, and loop control.

This engine implements the Liquid filter standard with 41 out of 46 filters fully compliant, plus 20 extension filters. See Liquid Compatibility for details.

Installation

go get github.com/kaptinlin/template

Quick Start

One-Step Rendering
output, err := template.Render("Hello, {{ name|upcase }}!", map[string]any{
    "name": "alice",
})
// output: "Hello, ALICE!"
Compile and Reuse

For templates that need to be rendered multiple times, compile once and reuse:

tmpl, err := template.Compile("Hello, {{ name }}!")
if err != nil {
    log.Fatal(err)
}

output, err := tmpl.Render(map[string]any{"name": "World"})
// output: "Hello, World!"
Using io.Writer
tmpl, _ := template.Compile("Hello, {{ name }}!")
ctx := template.NewExecutionContext(map[string]any{"name": "World"})
tmpl.Execute(ctx, os.Stdout)

Template Syntax

Variables

Use {{ }} to output variables, with dot notation for nested properties:

{{ user.name }}
{{ user.address.city }}
{{ items.0 }}

See Variables Documentation for details.

Filters

Use pipe | to apply filters to variables, supporting chaining and arguments:

{{ name|upcase }}
{{ title|truncate:20 }}
{{ name|downcase|capitalize }}
{{ price|plus:10|times:2 }}

See Filters Documentation for details.

Conditionals
{% if score > 80 %}
    Excellent
{% elif score > 60 %}
    Pass
{% else %}
    Fail
{% endif %}
Loops
{% for item in items %}
    {{ item }}
{% endfor %}

{% for key, value in dict %}
    {{ key }}: {{ value }}
{% endfor %}

The loop variable is available inside loops:

Property Description
loop.Index Current index (starting from 0)
loop.Revindex Reverse index
loop.First Whether this is the first iteration
loop.Last Whether this is the last iteration
loop.Length Total length of the collection

Supports {% break %} and {% continue %} for loop control.

See Control Structure Documentation for details.

Comments
{# This content will not appear in the output #}
Expressions

Supports the following operators (from lowest to highest precedence):

Operator Description
or, || Logical OR
and, && Logical AND
==, !=, <, >, <=, >= Comparison
+, - Addition, Subtraction
*, /, % Multiplication, Division, Modulo
not, -, + Unary operators

Literal support: strings ("text" / 'text'), numbers (42, 3.14), booleans (true / false), null (null).

Built-in Filters

String
Filter Description Example
default Return default value if empty {{ name|default:'Anonymous' }}
upcase Convert to uppercase {{ name|upcase }}
downcase Convert to lowercase {{ name|downcase }}
capitalize Capitalize first letter {{ name|capitalize }}
strip Remove leading/trailing whitespace {{ text|strip }}
lstrip Remove leading whitespace {{ text|lstrip }}
rstrip Remove trailing whitespace {{ text|rstrip }}
truncate Truncate to length (default 50) {{ text|truncate:20 }}
truncatewords Truncate to word count (default 15) {{ text|truncatewords:5 }}
replace Replace all occurrences {{ text|replace:'old','new' }}
replace_first Replace first occurrence {{ text|replace_first:'old','new' }}
replace_last Replace last occurrence {{ text|replace_last:'old','new' }}
remove Remove all occurrences {{ text|remove:'bad' }}
remove_first Remove first occurrence {{ text|remove_first:'x' }}
remove_last Remove last occurrence {{ text|remove_last:'x' }}
append Append string {{ name|append:'!' }}
prepend Prepend string {{ name|prepend:'Hi ' }}
split Split by delimiter {{ csv|split:',' }}
slice Extract substring by offset and length {{ text|slice:1,3 }}
escape Escape HTML characters {{ html|escape }}
escape_once Escape without double-escaping {{ html|escape_once }}
strip_html Remove HTML tags {{ html|strip_html }}
strip_newlines Remove newline characters {{ text|strip_newlines }}
url_encode Percent-encode for URLs {{ text|url_encode }}
url_decode Decode percent-encoded string {{ text|url_decode }}
base64_encode Encode to Base64 {{ text|base64_encode }}
base64_decode Decode from Base64 {{ text|base64_decode }}

String extensions (not in Liquid standard):

Filter Description Example
titleize Capitalize first letter of each word {{ title|titleize }}
camelize Convert to camelCase {{ name|camelize }}
pascalize Convert to PascalCase {{ name|pascalize }}
dasherize Convert to dash-separated {{ name|dasherize }}
slugify Convert to URL-friendly format {{ title|slugify }}
pluralize Singular/plural selection {{ count|pluralize:'item','items' }}
ordinalize Convert to ordinal {{ num|ordinalize }}
length Get string/array/map length {{ name|length }}
Math
Filter Description Example
plus Addition {{ price|plus:10 }}
minus Subtraction {{ price|minus:5 }}
times Multiplication {{ price|times:2 }}
divided_by Division {{ total|divided_by:3 }}
modulo Modulo {{ num|modulo:2 }}
abs Absolute value {{ num|abs }}
round Round (default precision 0) {{ pi|round:2 }}
floor Floor {{ num|floor }}
ceil Ceiling {{ num|ceil }}
at_least Ensure minimum value {{ num|at_least:0 }}
at_most Ensure maximum value {{ num|at_most:100 }}
Array
Filter Description Example
join Join with separator (default " ") {{ items|join:', ' }}
first First element {{ items|first }}
last Last element {{ items|last }}
size Collection or string length {{ items|size }}
reverse Reverse order {{ items|reverse }}
sort Sort elements {{ items|sort }}
sort_natural Case-insensitive sort {{ items|sort_natural }}
uniq Remove duplicates {{ items|uniq }}
compact Remove nil values {{ items|compact }}
concat Combine two arrays {{ items|concat:more }}
map Extract key from each element {{ users|map:'name' }}
where Select items matching key/value {{ users|where:'active','true' }}
reject Reject items matching key/value {{ users|reject:'active','false' }}
find Find first matching item {{ users|find:'name','Bob' }}
find_index Find index of first match {{ users|find_index:'name','Bob' }}
has Check if any item matches {{ users|has:'name','Alice' }}
sum Sum values (supports property) {{ scores|sum }}

Array extensions (not in Liquid standard):

Filter Description Example
shuffle Random shuffle {{ items|shuffle }}
random Random element {{ items|random }}
max Maximum value {{ scores|max }}
min Minimum value {{ scores|min }}
average Average {{ scores|average }}
Date
Filter Description Example
date Format date (PHP-style) {{ timestamp|date:'Y-m-d' }}
day Extract day {{ timestamp|day }}
month Extract month number {{ timestamp|month }}
month_full Full month name {{ timestamp|month_full }}
year Extract year {{ timestamp|year }}
week ISO week number {{ timestamp|week }}
weekday Day of week {{ timestamp|weekday }}
time_ago Relative time {{ timestamp|time_ago }}
Number Formatting
Filter Description Example
number Number formatting {{ price|number:'0.00' }}
bytes Convert to readable byte units {{ fileSize|bytes }}
Serialization
Filter Description Example
json Serialize to JSON {{ data|json }}
Map
Filter Description Example
extract Extract nested value by dot path {{ data|extract:'user.name' }}

Extension

Custom Filters
template.RegisterFilter("repeat", func(value any, args ...any) (any, error) {
    s := fmt.Sprintf("%v", value)
    n := 2
    if len(args) > 0 {
        if parsed, err := strconv.Atoi(fmt.Sprintf("%v", args[0])); err == nil {
            n = parsed
        }
    }
    return strings.Repeat(s, n), nil
})

// {{ "ha"|repeat:3 }} -> "hahaha"
Custom Tags

Register custom tags by implementing the Statement interface and calling RegisterTag. Here's an example of a {% set %} tag:

type SetNode struct {
    VarName    string
    Expression template.Expression
    Line, Col  int
}

func (n *SetNode) Position() (int, int) { return n.Line, n.Col }
func (n *SetNode) String() string       { return fmt.Sprintf("Set(%s)", n.VarName) }
func (n *SetNode) Execute(ctx *template.ExecutionContext, _ io.Writer) error {
    val, err := n.Expression.Evaluate(ctx)
    if err != nil {
        return err
    }
    ctx.Set(n.VarName, val.Interface())
    return nil
}

template.RegisterTag("set", func(doc *template.Parser, start *template.Token, arguments *template.Parser) (template.Statement, error) {
    varToken, err := arguments.ExpectIdentifier()
    if err != nil {
        return nil, arguments.Error("expected variable name after 'set'")
    }
    if arguments.Match(template.TokenSymbol, "=") == nil {
        return nil, arguments.Error("expected '=' after variable name")
    }
    expr, err := arguments.ParseExpression()
    if err != nil {
        return nil, err
    }
    return &SetNode{
        VarName:    varToken.Value,
        Expression: expr,
        Line:       start.Line,
        Col:        start.Col,
    }, nil
})

See examples directory for more examples.

Context Building

// Using map directly
output, _ := template.Render(source, map[string]any{
    "name": "Alice",
    "age":  30,
})

// Using ContextBuilder (supports struct expansion)
ctx, err := template.NewContextBuilder().
    KeyValue("name", "Alice").
    Struct(user).
    Build()
output, _ := tmpl.Render(ctx)

Error Reporting

All errors include precise line and column position information:

lexer error at line 1, col 7: unclosed variable tag, expected '}}'
parse error at line 1, col 4: unknown tag: unknown
parse error at line 1, col 19: unexpected EOF, expected one of: [elif else endif]

Liquid Compatibility

This engine targets compatibility with the Liquid template standard. Filter names follow Liquid conventions (upcase, downcase, strip, at_least, divided_by, etc.).

For a complete comparison including behavioral differences, missing filters, extension filters, and convenience aliases, see Liquid Compatibility.

Architecture

See ARCHITECTURE.md for details.

Contributing

Contributions are welcome. Please see Contributing Guide.

License

MIT License - see LICENSE for details.

Documentation

Overview

Package template provides a lightweight template engine with Django/Jinja-style syntax.

Package template provides a simple and efficient template engine for Go.

The template engine supports:

  • Variable interpolation: {{ variable }}
  • Filters: {{ variable|filter:arg }}
  • Control structures: {% if condition %}, {% for item in collection %}
  • Comments: {# comment #}

Basic Usage:

tmpl, err := template.Compile("Hello, {{ name|upper }}!")
if err != nil {
	panic(err)
}

output, err := tmpl.Render(map[string]any{"name": "world"})
// Output: "Hello, WORLD!"

Architecture:

The package is organized into several key components:

  • Lexer: Tokenizes template source into a token stream
  • Parser: Converts tokens into an AST (Abstract Syntax Tree)
  • Expression Parser: Parses expressions with operator precedence
  • Template: Executes the AST with a given context
  • Filters: Transforms values during template execution
  • Context: Stores and retrieves template variables

Control Flow:

The template engine supports break and continue statements within loops:

{% for item in items %}
	{% if item == "skip" %}
		{% continue %}
	{% endif %}
	{% if item == "stop" %}
		{% break %}
	{% endif %}
	{{ item }}
{% endfor %}

Loop Context:

Within loops, a special "loop" variable is available providing:

  • loop.Index: Current index (0-based)
  • loop.Revindex: Reverse index
  • loop.First: True if first iteration
  • loop.Last: True if last iteration
  • loop.Length: Total collection length

For detailed examples, see the examples/ directory.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrContextKeyNotFound     = errors.New("key not found in context")
	ErrContextInvalidKeyType  = errors.New("invalid key type for navigation")
	ErrContextIndexOutOfRange = errors.New("index out of range in context")
)

ErrContextKeyNotFound indicates a key was not found in the execution context. ErrContextInvalidKeyType indicates an invalid key type during context navigation. ErrContextIndexOutOfRange indicates an index out of range during context navigation.

View Source
var (
	ErrFilterNotFound               = errors.New("filter not found")
	ErrFilterExecutionFailed        = errors.New("filter execution failed")
	ErrFilterInputInvalid           = errors.New("filter input is invalid")
	ErrFilterArgsInvalid            = errors.New("filter arguments are invalid")
	ErrFilterInputEmpty             = errors.New("filter input is empty")
	ErrFilterInputNotSlice          = errors.New("filter input is not a slice")
	ErrFilterInputNotNumeric        = errors.New("filter input is not numeric")
	ErrFilterInputInvalidTimeFormat = errors.New("filter input has an invalid time format")
	ErrFilterInputUnsupportedType   = errors.New("filter input is of an unsupported type")
	ErrInsufficientArgs             = errors.New("insufficient arguments provided")
	ErrInvalidFilterName            = errors.New("invalid filter name")
	ErrUnknownFilterArgumentType    = errors.New("unknown argument type")
	ErrExpectedFilterName           = errors.New("expected filter name after '|'")
)

ErrFilterNotFound indicates a referenced filter does not exist. ErrFilterExecutionFailed indicates a filter failed during execution. ErrFilterInputInvalid indicates the filter received invalid input. ErrFilterArgsInvalid indicates the filter received invalid arguments. ErrFilterInputEmpty indicates the filter received empty input. ErrFilterInputNotSlice indicates the filter expected a slice input. ErrFilterInputNotNumeric indicates the filter expected numeric input. ErrFilterInputInvalidTimeFormat indicates the filter received an invalid time format. ErrFilterInputUnsupportedType indicates the filter received an unsupported input type. ErrInsufficientArgs indicates insufficient arguments were provided to a filter. ErrInvalidFilterName indicates an invalid filter name was used. ErrUnknownFilterArgumentType indicates an unknown argument type was passed to a filter. ErrExpectedFilterName indicates a filter name was expected after the pipe operator.

View Source
var (
	ErrUnexpectedCharacter = errors.New("unexpected character")
	ErrUnterminatedString  = errors.New("unterminated string literal")
)

ErrUnexpectedCharacter indicates the lexer encountered an unexpected character. ErrUnterminatedString indicates a string literal was not properly closed.

View Source
var (
	ErrInvalidNumber   = errors.New("invalid number")
	ErrExpectedRParen  = errors.New("expected ')'")
	ErrUnexpectedToken = errors.New("unexpected token")
	ErrUnknownNodeType = errors.New("unknown node type")
	ErrIntegerOverflow = errors.New("unsigned integer value exceeds maximum int64 value")
)

ErrInvalidNumber indicates the parser encountered an invalid numeric literal. ErrExpectedRParen indicates a closing parenthesis was expected but not found. ErrUnexpectedToken indicates the parser encountered an unexpected token. ErrUnknownNodeType indicates an unknown AST node type was encountered. ErrIntegerOverflow indicates an unsigned integer value exceeds the maximum int64 value.

View Source
var (
	ErrUnsupportedType     = errors.New("unsupported type")
	ErrUnsupportedOperator = errors.New("unsupported operator")
	ErrUnsupportedUnaryOp  = errors.New("unsupported unary operator")
)

ErrUnsupportedType indicates an unsupported type was encountered. ErrUnsupportedOperator indicates an unsupported operator was used. ErrUnsupportedUnaryOp indicates an unsupported unary operator was used.

View Source
var (
	ErrUndefinedVariable     = errors.New("undefined variable")
	ErrUndefinedProperty     = errors.New("undefined property")
	ErrNonStructProperty     = errors.New("cannot access property of non-struct value")
	ErrCannotAccessProperty  = errors.New("cannot access property")
	ErrNonObjectProperty     = errors.New("cannot access property of non-object")
	ErrInvalidVariableAccess = errors.New("invalid variable access")
)

ErrUndefinedVariable indicates a referenced variable is not defined. ErrUndefinedProperty indicates a referenced property is not defined. ErrNonStructProperty indicates a property access was attempted on a non-struct value. ErrCannotAccessProperty indicates a property cannot be accessed. ErrNonObjectProperty indicates a property access was attempted on a non-object value. ErrInvalidVariableAccess indicates an invalid variable access pattern.

View Source
var (
	ErrCannotAddTypes       = errors.New("cannot add values of these types")
	ErrCannotSubtractTypes  = errors.New("cannot subtract values of these types")
	ErrCannotMultiplyTypes  = errors.New("cannot multiply values of these types")
	ErrCannotDivideTypes    = errors.New("cannot divide values of these types")
	ErrCannotModuloTypes    = errors.New("cannot modulo values of these types")
	ErrDivisionByZero       = errors.New("division by zero")
	ErrModuloByZero         = errors.New("modulo by zero")
	ErrCannotConvertToBool  = errors.New("cannot convert type to boolean")
	ErrCannotNegate         = errors.New("cannot negate value")
	ErrCannotApplyUnaryPlus = errors.New("cannot apply unary plus")
	ErrCannotCompareTypes   = errors.New("cannot compare values of these types")
)

ErrCannotAddTypes indicates addition is not supported for the given types. ErrCannotSubtractTypes indicates subtraction is not supported for the given types. ErrCannotMultiplyTypes indicates multiplication is not supported for the given types. ErrCannotDivideTypes indicates division is not supported for the given types. ErrCannotModuloTypes indicates modulo is not supported for the given types. ErrDivisionByZero indicates a division by zero was attempted. ErrModuloByZero indicates a modulo by zero was attempted. ErrCannotConvertToBool indicates a value cannot be converted to boolean. ErrCannotNegate indicates a value cannot be negated. ErrCannotApplyUnaryPlus indicates unary plus cannot be applied to the value. ErrCannotCompareTypes indicates comparison is not supported for the given types.

View Source
var (
	ErrInvalidIndexType      = errors.New("invalid index type")
	ErrInvalidArrayIndex     = errors.New("invalid array index")
	ErrIndexOutOfRange       = errors.New("index out of range")
	ErrCannotIndexNil        = errors.New("cannot index nil")
	ErrTypeNotIndexable      = errors.New("type is not indexable")
	ErrCannotGetKeyFromNil   = errors.New("cannot get key from nil")
	ErrTypeNotMap            = errors.New("type is not a map")
	ErrCannotGetFieldFromNil = errors.New("cannot get field from nil")
	ErrStructHasNoField      = errors.New("struct has no field")
	ErrTypeHasNoField        = errors.New("type has no field")
	ErrUnsupportedArrayType  = errors.New("unsupported array type")
)

ErrInvalidIndexType indicates an invalid index type was used. ErrInvalidArrayIndex indicates an invalid array index was used. ErrIndexOutOfRange indicates an index is out of range. ErrCannotIndexNil indicates an indexing operation was attempted on nil. ErrTypeNotIndexable indicates the type does not support indexing. ErrCannotGetKeyFromNil indicates a key lookup was attempted on nil. ErrTypeNotMap indicates the type is not a map. ErrCannotGetFieldFromNil indicates a field access was attempted on nil. ErrStructHasNoField indicates the struct does not have the requested field. ErrTypeHasNoField indicates the type does not have the requested field. ErrUnsupportedArrayType indicates an unsupported array type was encountered.

View Source
var (
	ErrUnsupportedCollectionType = errors.New("unsupported collection type for for loop")
	ErrTypeNotIterable           = errors.New("type is not iterable")
	ErrTypeHasNoLength           = errors.New("type has no length")
)

ErrUnsupportedCollectionType indicates the collection type is not supported in a for loop. ErrTypeNotIterable indicates the type does not support iteration. ErrTypeHasNoLength indicates the type does not support the length operation.

View Source
var (
	ErrCannotConvertNilToInt   = errors.New("cannot convert nil to int")
	ErrCannotConvertToInt      = errors.New("cannot convert value to int")
	ErrCannotConvertNilToFloat = errors.New("cannot convert nil to float")
	ErrCannotConvertToFloat    = errors.New("cannot convert value to float")
	ErrExpectedSliceOrArray    = errors.New("expected slice or array")
)

ErrCannotConvertNilToInt indicates nil cannot be converted to int. ErrCannotConvertToInt indicates the value cannot be converted to int. ErrCannotConvertNilToFloat indicates nil cannot be converted to float. ErrCannotConvertToFloat indicates the value cannot be converted to float.

View Source
var (
	ErrBreakOutsideLoop    = errors.New("break statement outside of loop")
	ErrContinueOutsideLoop = errors.New("continue statement outside of loop")
)

ErrBreakOutsideLoop indicates a break statement was used outside of a loop. ErrContinueOutsideLoop indicates a continue statement was used outside of a loop.

View Source
var (
	ErrTagAlreadyRegistered = errors.New("tag already registered")

	ErrMultipleElseStatements         = errors.New("multiple 'else' statements found in if block, use 'elif' for additional conditions")
	ErrUnexpectedTokensAfterCondition = errors.New("unexpected tokens after condition")
	ErrElseNoArgs                     = errors.New("else does not take arguments")
	ErrEndifNoArgs                    = errors.New("endif does not take arguments")
	ErrElifAfterElse                  = errors.New("elif cannot appear after else")

	ErrExpectedVariable                = errors.New("expected variable name")
	ErrExpectedSecondVariable          = errors.New("expected second variable name after comma")
	ErrExpectedInKeyword               = errors.New("expected 'in' keyword")
	ErrUnexpectedTokensAfterCollection = errors.New("unexpected tokens after collection")
	ErrEndforNoArgs                    = errors.New("endfor does not take arguments")

	ErrBreakNoArgs    = errors.New("break does not take arguments")
	ErrContinueNoArgs = errors.New("continue does not take arguments")
)

ErrTagAlreadyRegistered indicates a tag with the same name is already registered.

ErrMultipleElseStatements indicates multiple else clauses were found in an if block. ErrUnexpectedTokensAfterCondition indicates unexpected tokens after a condition expression. ErrElseNoArgs indicates the else tag received unexpected arguments. ErrEndifNoArgs indicates the endif tag received unexpected arguments. ErrElifAfterElse indicates an elif clause appeared after an else clause.

ErrExpectedVariable indicates a variable name was expected but not found. ErrExpectedSecondVariable indicates a second variable name was expected after a comma. ErrExpectedInKeyword indicates the "in" keyword was expected but not found. ErrUnexpectedTokensAfterCollection indicates unexpected tokens after a collection expression. ErrEndforNoArgs indicates the endfor tag received unexpected arguments.

ErrBreakNoArgs indicates the break tag received unexpected arguments. ErrContinueNoArgs indicates the continue tag received unexpected arguments.

Functions

func HasFilter added in v0.4.0

func HasFilter(name string) bool

HasFilter reports whether the default registry contains a filter with the given name.

func HasTag added in v0.4.0

func HasTag(name string) bool

HasTag checks if a tag with the given name is registered. It is safe to call from multiple goroutines.

func IsKeyword added in v0.4.0

func IsKeyword(ident string) bool

IsKeyword reports whether ident is a reserved keyword.

func IsSymbol added in v0.4.0

func IsSymbol(s string) bool

IsSymbol reports whether s is a valid operator or punctuation symbol.

func ListFilters added in v0.4.0

func ListFilters() []string

ListFilters returns a sorted list of all filter names in the default registry.

func ListTags added in v0.4.0

func ListTags() []string

ListTags returns a sorted list of all registered tag names. It is safe to call from multiple goroutines.

func RegisterFilter

func RegisterFilter(name string, fn FilterFunc)

RegisterFilter registers a filter in the default registry. It panics if fn is nil.

func RegisterTag added in v0.4.0

func RegisterTag(name string, parser TagParser) error

RegisterTag registers a tag parser. It is safe to call from multiple goroutines.

func Render

func Render(source string, data map[string]any) (string, error)

Render compiles and renders a template in one step.

Render is a shorthand for calling Compile followed by Template.Render. For repeated rendering of the same template, compile once with Compile and call Template.Render to avoid redundant compilation.

func UnregisterFilter added in v0.4.0

func UnregisterFilter(name string)

UnregisterFilter removes a filter from the default registry.

func UnregisterTag added in v0.4.0

func UnregisterTag(name string)

UnregisterTag removes a tag from the registry. It is safe to call from multiple goroutines.

Types

type BinaryOpNode added in v0.4.0

type BinaryOpNode struct {
	Operator string
	Left     Expression
	Right    Expression
	Line     int
	Col      int
}

BinaryOpNode represents a binary operation.

func NewBinaryOpNode added in v0.4.0

func NewBinaryOpNode(operator string, left, right Expression, line, col int) *BinaryOpNode

NewBinaryOpNode returns a new BinaryOpNode.

func (*BinaryOpNode) Evaluate added in v0.4.0

func (n *BinaryOpNode) Evaluate(ctx *ExecutionContext) (*Value, error)

Evaluate computes the binary operation result.

func (*BinaryOpNode) Position added in v0.4.0

func (n *BinaryOpNode) Position() (int, int)

Position returns the position of the BinaryOpNode.

func (*BinaryOpNode) String added in v0.4.0

func (n *BinaryOpNode) String() string

String returns a debug representation of the BinaryOpNode.

type BreakError added in v0.4.0

type BreakError struct{}

BreakError signals loop termination.

func (*BreakError) Error added in v0.4.0

func (e *BreakError) Error() string

Error implements the error interface.

type BreakNode added in v0.4.0

type BreakNode struct {
	Line int
	Col  int
}

BreakNode represents a {% break %} statement.

func (*BreakNode) Execute added in v0.4.0

func (n *BreakNode) Execute(_ *ExecutionContext, _ io.Writer) error

Execute signals loop termination via BreakError.

func (*BreakNode) Position added in v0.4.0

func (n *BreakNode) Position() (int, int)

Position returns the position of the BreakNode.

func (*BreakNode) String added in v0.4.0

func (n *BreakNode) String() string

String returns a debug representation of the BreakNode.

type Context

type Context map[string]any

Context stores template variables as a string-keyed map. Values can be of any type. Dot-notation (e.g., "user.name") is supported for nested access.

func NewContext

func NewContext() Context

NewContext creates and returns a new empty Context.

func (Context) Get

func (c Context) Get(key string) (any, error)

Get retrieves a value from the Context by key. Dot-separated keys (e.g., "user.profile.name") navigate nested structures. Array indices are supported (e.g., "items.0").

Get returns ErrContextKeyNotFound, ErrContextIndexOutOfRange, or ErrContextInvalidKeyType on failure.

func (Context) Set

func (c Context) Set(key string, value any)

Set inserts a value into the Context with the specified key. Dot-notation (e.g., "user.address.city") creates nested map structures. Top-level keys preserve original data types; nested keys use map[string]any. Empty keys are silently ignored.

type ContextBuilder added in v0.3.6

type ContextBuilder struct {
	// contains filtered or unexported fields
}

ContextBuilder provides a fluent API for building a Context with error collection.

func NewContextBuilder added in v0.3.6

func NewContextBuilder() *ContextBuilder

NewContextBuilder creates a new ContextBuilder for fluent Context construction.

ctx, err := NewContextBuilder().
    KeyValue("name", "John").
    Struct(user).
    Build()

func (*ContextBuilder) Build added in v0.3.6

func (cb *ContextBuilder) Build() (Context, error)

Build returns the constructed Context and any collected errors. Errors from ContextBuilder.KeyValue or ContextBuilder.Struct operations are joined into a single error.

func (*ContextBuilder) KeyValue added in v0.3.6

func (cb *ContextBuilder) KeyValue(key string, value any) *ContextBuilder

KeyValue sets a key-value pair and returns the builder for chaining.

builder := NewContextBuilder().
    KeyValue("name", "John").
    KeyValue("age", 30)

func (*ContextBuilder) Struct added in v0.3.6

func (cb *ContextBuilder) Struct(v any) *ContextBuilder

Struct expands struct fields into the Context using JSON serialization. Fields are flattened to top-level keys based on their json tags. Nested structs are preserved as nested maps accessible via dot notation. If serialization fails, the error is collected and returned by ContextBuilder.Build.

type ContinueError added in v0.4.0

type ContinueError struct{}

ContinueError signals loop continuation.

func (*ContinueError) Error added in v0.4.0

func (e *ContinueError) Error() string

Error implements the error interface.

type ContinueNode added in v0.4.0

type ContinueNode struct {
	Line int
	Col  int
}

ContinueNode represents a {% continue %} statement.

func (*ContinueNode) Execute added in v0.4.0

func (n *ContinueNode) Execute(_ *ExecutionContext, _ io.Writer) error

Execute signals loop continuation via ContinueError.

func (*ContinueNode) Position added in v0.4.0

func (n *ContinueNode) Position() (int, int)

Position returns the position of the ContinueNode.

func (*ContinueNode) String added in v0.4.0

func (n *ContinueNode) String() string

String returns a debug representation of the ContinueNode.

type ExecutionContext added in v0.4.0

type ExecutionContext struct {
	Public  Context // user-provided variables
	Private Context // internal variables (e.g., loop counters)
}

ExecutionContext holds the execution state for template rendering, separating user-provided variables (Public) from internal variables (Private).

func NewChildContext added in v0.4.0

func NewChildContext(parent *ExecutionContext) *ExecutionContext

NewChildContext creates a child ExecutionContext that shares the parent's Public context but copies the Private context for isolated scope.

func NewExecutionContext added in v0.4.0

func NewExecutionContext(data map[string]any) *ExecutionContext

NewExecutionContext creates a new ExecutionContext from user data.

func (*ExecutionContext) Get added in v0.4.0

func (ec *ExecutionContext) Get(name string) (any, bool)

Get retrieves a variable, checking Private first, then Public.

func (*ExecutionContext) Set added in v0.4.0

func (ec *ExecutionContext) Set(name string, value any)

Set stores a variable in the private context.

type ExprParser added in v0.4.0

type ExprParser struct {
	// contains filtered or unexported fields
}

ExprParser parses expressions from a token stream. It handles operator precedence, filters, property access, etc.

func NewExprParser added in v0.4.0

func NewExprParser(tokens []*Token) *ExprParser

NewExprParser creates a new expression parser.

func (*ExprParser) ParseExpression added in v0.4.0

func (p *ExprParser) ParseExpression() (Expression, error)

ParseExpression parses a complete expression. This is the entry point for expression parsing.

type Expression added in v0.4.0

type Expression interface {
	Node
	Evaluate(ctx *ExecutionContext) (*Value, error)
}

Expression is the interface for all expression nodes. Expressions are evaluated to produce values.

type FilterFunc

type FilterFunc func(value any, args ...any) (any, error)

FilterFunc represents the signature of functions that can be applied as filters.

func Filter

func Filter(name string) (FilterFunc, bool)

Filter retrieves a filter from the default registry.

type FilterNode added in v0.4.0

type FilterNode struct {
	Expr Expression
	Name string
	Args []Expression
	Line int
	Col  int
}

FilterNode represents a filter application.

func NewFilterNode added in v0.4.0

func NewFilterNode(expr Expression, name string, args []Expression, line, col int) *FilterNode

NewFilterNode returns a new FilterNode.

func (*FilterNode) Evaluate added in v0.4.0

func (n *FilterNode) Evaluate(ctx *ExecutionContext) (*Value, error)

Evaluate applies the named filter to the expression value.

func (*FilterNode) Position added in v0.4.0

func (n *FilterNode) Position() (int, int)

Position returns the position of the FilterNode.

func (*FilterNode) String added in v0.4.0

func (n *FilterNode) String() string

String returns a debug representation of the FilterNode.

type ForNode added in v0.4.0

type ForNode struct {
	Vars       []string
	Collection Expression
	Body       []Node
	Line       int
	Col        int
}

ForNode represents a for loop.

func (*ForNode) Execute added in v0.4.0

func (n *ForNode) Execute(ctx *ExecutionContext, w io.Writer) error

Execute evaluates the iterable and executes the loop body for each element.

func (*ForNode) Position added in v0.4.0

func (n *ForNode) Position() (int, int)

Position returns the position of the ForNode.

func (*ForNode) String added in v0.4.0

func (n *ForNode) String() string

String returns a debug representation of the ForNode.

type IfBranch added in v0.4.0

type IfBranch struct {
	Condition Expression
	Body      []Node
}

IfBranch represents a single if or elif branch.

type IfNode added in v0.4.0

type IfNode struct {
	Branches []IfBranch
	ElseBody []Node
	Line     int
	Col      int
}

IfNode represents an if-elif-else conditional block.

func (*IfNode) Execute added in v0.4.0

func (n *IfNode) Execute(ctx *ExecutionContext, w io.Writer) error

Execute runs the first truthy branch, or the else block if no branch matches.

func (*IfNode) Position added in v0.4.0

func (n *IfNode) Position() (int, int)

Position returns the position of the IfNode.

func (*IfNode) String added in v0.4.0

func (n *IfNode) String() string

String returns a debug representation of the IfNode.

type Lexer added in v0.2.0

type Lexer struct {
	// contains filtered or unexported fields
}

Lexer performs lexical analysis on template input.

func NewLexer added in v0.4.0

func NewLexer(input string) *Lexer

NewLexer creates a new Lexer for the given input.

func (*Lexer) Tokenize added in v0.4.0

func (l *Lexer) Tokenize() ([]*Token, error)

Tokenize performs lexical analysis and returns all tokens.

type LexerError added in v0.4.0

type LexerError struct {
	Message string
	Line    int
	Col     int
}

LexerError represents a lexical analysis error with position information.

func (*LexerError) Error added in v0.4.0

func (e *LexerError) Error() string

Error implements the error interface.

type LiteralNode added in v0.4.0

type LiteralNode struct {
	Value any
	Line  int
	Col   int
}

LiteralNode represents a literal value (string, number, boolean).

func NewLiteralNode added in v0.4.0

func NewLiteralNode(value any, line, col int) *LiteralNode

NewLiteralNode returns a new LiteralNode.

func (*LiteralNode) Evaluate added in v0.4.0

func (n *LiteralNode) Evaluate(_ *ExecutionContext) (*Value, error)

Evaluate returns the literal value wrapped in a Value.

func (*LiteralNode) Position added in v0.4.0

func (n *LiteralNode) Position() (int, int)

Position returns the position of the LiteralNode.

func (*LiteralNode) String added in v0.4.0

func (n *LiteralNode) String() string

String returns a debug representation of the LiteralNode.

type LoopContext added in v0.2.8

type LoopContext struct {
	Index      int
	Counter    int
	Revindex   int
	Revcounter int
	First      bool
	Last       bool
	Length     int
	Parent     *LoopContext
}

LoopContext represents loop metadata for templates.

type Node

type Node interface {
	// Position returns the line and column where this node starts.
	Position() (line, col int)

	// String returns a string representation of the node for debugging.
	String() string
}

Node is the interface that all AST nodes must implement. Each node represents a part of the template syntax tree.

type OutputNode added in v0.4.0

type OutputNode struct {
	Expr Expression
	Line int
	Col  int
}

OutputNode represents a variable output {{ ... }}.

func NewOutputNode added in v0.4.0

func NewOutputNode(expr Expression, line, col int) *OutputNode

NewOutputNode returns a new OutputNode.

func (*OutputNode) Execute added in v0.4.0

func (n *OutputNode) Execute(ctx *ExecutionContext, w io.Writer) error

Execute evaluates the expression and writes its string value.

func (*OutputNode) Position added in v0.4.0

func (n *OutputNode) Position() (int, int)

Position returns the position of the OutputNode.

func (*OutputNode) String added in v0.4.0

func (n *OutputNode) String() string

String returns a debug representation of the OutputNode.

type ParseError added in v0.4.0

type ParseError struct {
	Message string
	Line    int
	Col     int
}

ParseError represents a parsing error with source location.

func (*ParseError) Error added in v0.4.0

func (e *ParseError) Error() string

Error returns a human-readable error message with source location.

type Parser

type Parser struct {
	// contains filtered or unexported fields
}

Parser consumes tokens and builds an AST.

func NewParser

func NewParser(tokens []*Token) *Parser

NewParser creates a new Parser.

func (*Parser) Advance added in v0.4.0

func (p *Parser) Advance()

Advance moves to the next token.

func (*Parser) Current added in v0.4.0

func (p *Parser) Current() *Token

Current returns the current token without advancing the parser.

func (*Parser) Error added in v0.4.0

func (p *Parser) Error(msg string) error

Error creates a parse error at the current token position.

func (*Parser) Errorf added in v0.4.0

func (p *Parser) Errorf(format string, args ...any) error

Errorf creates a formatted parse error at the current token position.

func (*Parser) ExpectIdentifier added in v0.4.0

func (p *Parser) ExpectIdentifier() (*Token, error)

ExpectIdentifier expects and consumes an identifier token.

func (*Parser) Match added in v0.4.0

func (p *Parser) Match(tokenType TokenType, value string) *Token

Match consumes and returns the current token if type and value match. It returns nil when there is no match.

func (*Parser) Parse

func (p *Parser) Parse() ([]Statement, error)

Parse parses the entire template and returns AST statement nodes.

func (*Parser) ParseExpression added in v0.4.0

func (p *Parser) ParseExpression() (Expression, error)

ParseExpression parses an expression from the current token position.

func (*Parser) ParseUntil added in v0.4.0

func (p *Parser) ParseUntil(endTags ...string) ([]Statement, string, error)

ParseUntil parses nodes until one of the given end tags is encountered.

Returns the parsed nodes, the matched end-tag name, and any error.

func (*Parser) ParseUntilWithArgs added in v0.4.0

func (p *Parser) ParseUntilWithArgs(endTags ...string) ([]Statement, string, *Parser, error)

ParseUntilWithArgs parses nodes until one of the given end tags is encountered, and also returns a parser for the end-tag arguments.

func (*Parser) Remaining added in v0.4.0

func (p *Parser) Remaining() int

Remaining returns the number of unconsumed tokens.

type PropertyAccessNode added in v0.4.0

type PropertyAccessNode struct {
	Object   Expression
	Property string
	Line     int
	Col      int
}

PropertyAccessNode represents property/attribute access.

func NewPropertyAccessNode added in v0.4.0

func NewPropertyAccessNode(object Expression, property string, line, col int) *PropertyAccessNode

NewPropertyAccessNode returns a new PropertyAccessNode.

func (*PropertyAccessNode) Evaluate added in v0.4.0

func (n *PropertyAccessNode) Evaluate(ctx *ExecutionContext) (*Value, error)

Evaluate returns the property value from the evaluated object.

func (*PropertyAccessNode) Position added in v0.4.0

func (n *PropertyAccessNode) Position() (int, int)

Position returns the position of the PropertyAccessNode.

func (*PropertyAccessNode) String added in v0.4.0

func (n *PropertyAccessNode) String() string

String returns a debug representation of the PropertyAccessNode.

type Registry added in v0.4.0

type Registry struct {
	// contains filtered or unexported fields
}

Registry is a concurrency-safe collection of named filter functions. Use NewRegistry to create an instance, or use the package-level functions that operate on the default registry.

func NewRegistry added in v0.4.0

func NewRegistry() *Registry

NewRegistry creates an empty filter registry.

func (*Registry) Filter added in v0.4.0

func (r *Registry) Filter(name string) (FilterFunc, bool)

Filter returns the filter registered under name and a boolean indicating whether it was found.

func (*Registry) Has added in v0.4.0

func (r *Registry) Has(name string) bool

Has reports whether a filter with the given name is registered.

func (*Registry) List added in v0.4.0

func (r *Registry) List() []string

List returns the names of all registered filters in sorted order.

func (*Registry) Register added in v0.4.0

func (r *Registry) Register(name string, fn FilterFunc)

Register adds a filter function under the given name. If a filter with the same name already exists, it is overwritten.

Register panics if fn is nil.

func (*Registry) Unregister added in v0.4.0

func (r *Registry) Unregister(name string)

Unregister removes the filter registered under name. It is a no-op if no such filter exists.

type Statement added in v0.4.0

type Statement interface {
	Node
	Execute(ctx *ExecutionContext, writer io.Writer) error
}

Statement is the interface for all statement nodes. Statements are executed and produce output or side effects.

type SubscriptNode added in v0.4.0

type SubscriptNode struct {
	Object Expression
	Index  Expression
	Line   int
	Col    int
}

SubscriptNode represents subscript/index access.

func NewSubscriptNode added in v0.4.0

func NewSubscriptNode(object, index Expression, line, col int) *SubscriptNode

NewSubscriptNode returns a new SubscriptNode.

func (*SubscriptNode) Evaluate added in v0.4.0

func (n *SubscriptNode) Evaluate(ctx *ExecutionContext) (*Value, error)

Evaluate returns the indexed or keyed value.

func (*SubscriptNode) Position added in v0.4.0

func (n *SubscriptNode) Position() (int, int)

Position returns the position of the SubscriptNode.

func (*SubscriptNode) String added in v0.4.0

func (n *SubscriptNode) String() string

String returns a debug representation of the SubscriptNode.

type TagParser added in v0.4.0

type TagParser func(doc *Parser, start *Token, arguments *Parser) (Statement, error)

TagParser is the parse function signature for template tags.

Parameters:

  • doc: The document-level parser used to parse nested tag bodies. Example: an if tag parses content between {% if %} and {% endif %}.
  • start: The tag-name token (for example "if", "for"), including source position information used in error reporting.
  • arguments: A dedicated parser for tag arguments. Example: in {% if x > 5 %}, this parser sees "x > 5".

Returns:

  • Statement: The parsed statement node.
  • error: Any parse error encountered.

func Tag added in v0.4.0

func Tag(name string) (TagParser, bool)

Tag returns the parser registered for the given tag name. It is safe to call from multiple goroutines.

type Template

type Template struct {
	// contains filtered or unexported fields
}

Template represents a compiled template ready for execution. A Template is immutable after compilation.

func Compile added in v0.4.0

func Compile(source string) (*Template, error)

Compile compiles a template source string and returns an executable Template.

Compile performs lexical analysis, parsing, and template creation in sequence. On failure, the returned error wraps the underlying lexer or parser error.

tmpl, err := template.Compile("Hello {{ name }}!")
if err != nil {
    log.Fatal(err)
}
output, _ := tmpl.Render(map[string]any{"name": "World"})

func NewTemplate

func NewTemplate(root []Statement) *Template

NewTemplate creates a new Template from parsed AST nodes.

Most callers should use Compile instead, which handles lexing and parsing automatically.

func (*Template) Execute

func (t *Template) Execute(ctx *ExecutionContext, w io.Writer) error

Execute writes the template output to w using the given execution context.

For most use cases, Template.Render is simpler. Use Execute when you need control over the output destination or execution context.

func (*Template) Render added in v0.4.0

func (t *Template) Render(data map[string]any) (string, error)

Render executes the template with data and returns the output as a string.

Render is a convenience wrapper around Template.Execute for the common case where a string result is needed.

type TextNode added in v0.4.0

type TextNode struct {
	Text string
	Line int
	Col  int
}

TextNode represents plain text outside of template tags.

func NewTextNode added in v0.4.0

func NewTextNode(text string, line, col int) *TextNode

NewTextNode returns a new TextNode.

func (*TextNode) Execute added in v0.4.0

func (n *TextNode) Execute(_ *ExecutionContext, w io.Writer) error

Execute writes the raw text to the output.

func (*TextNode) Position added in v0.4.0

func (n *TextNode) Position() (int, int)

Position returns the position of the TextNode.

func (*TextNode) String added in v0.4.0

func (n *TextNode) String() string

String returns a debug representation of the TextNode.

type Token added in v0.2.0

type Token struct {
	Type  TokenType
	Value string
	Line  int // 1-based
	Col   int // 1-based
}

Token represents a single lexical token.

func (*Token) String added in v0.4.0

func (t *Token) String() string

String returns a human-readable representation of the token.

type TokenType added in v0.2.0

type TokenType int

TokenType represents the type of a token.

const (
	// TokenError indicates a lexical error.
	TokenError TokenType = iota

	// TokenEOF indicates the end of input.
	TokenEOF

	// TokenText represents plain text outside of template tags.
	TokenText

	// TokenVarBegin represents the {{ variable tag opener.
	TokenVarBegin

	// TokenVarEnd represents the }} variable tag closer.
	TokenVarEnd

	// TokenTagBegin represents the {% block tag opener.
	TokenTagBegin

	// TokenTagEnd represents the %} block tag closer.
	TokenTagEnd

	// TokenIdentifier represents an identifier such as a variable name or keyword.
	TokenIdentifier

	// TokenString represents a quoted string literal.
	TokenString

	// TokenNumber represents an integer or floating-point literal.
	TokenNumber

	// TokenSymbol represents an operator or punctuation character.
	TokenSymbol
)

func (TokenType) String added in v0.4.0

func (t TokenType) String() string

String returns a string representation of the token type.

type UnaryOpNode added in v0.4.0

type UnaryOpNode struct {
	Operator string
	Operand  Expression
	Line     int
	Col      int
}

UnaryOpNode represents a unary operation.

func NewUnaryOpNode added in v0.4.0

func NewUnaryOpNode(operator string, operand Expression, line, col int) *UnaryOpNode

NewUnaryOpNode returns a new UnaryOpNode.

func (*UnaryOpNode) Evaluate added in v0.4.0

func (n *UnaryOpNode) Evaluate(ctx *ExecutionContext) (*Value, error)

Evaluate computes the unary operation result.

func (*UnaryOpNode) Position added in v0.4.0

func (n *UnaryOpNode) Position() (int, int)

Position returns the position of the UnaryOpNode.

func (*UnaryOpNode) String added in v0.4.0

func (n *UnaryOpNode) String() string

String returns a debug representation of the UnaryOpNode.

type Value added in v0.2.0

type Value struct {
	// contains filtered or unexported fields
}

Value wraps a Go value for template execution, providing type checking, conversion, and comparison operations.

func NewValue added in v0.2.0

func NewValue(v any) *Value

NewValue creates a Value wrapping v.

func (*Value) Bool added in v0.2.0

func (v *Value) Bool() bool

Bool reports whether the value is truthy.

func (*Value) Compare added in v0.4.0

func (v *Value) Compare(other *Value) (int, error)

Compare compares v with other. It returns -1 if v < other, 0 if v == other, 1 if v > other.

func (*Value) Equals added in v0.4.0

func (v *Value) Equals(other *Value) bool

Equals reports whether v and other represent the same value.

func (*Value) Field added in v0.4.0

func (v *Value) Field(name string) (*Value, error)

Field returns the value of a struct field or map key by name. For structs, it searches by JSON tag first, then by exported field name.

func (*Value) Float added in v0.2.0

func (v *Value) Float() (float64, error)

Float returns the value as float64, converting if possible.

func (*Value) Index added in v0.4.0

func (v *Value) Index(i int) (*Value, error)

Index returns the element at index i (for slices, arrays, strings).

func (*Value) Int added in v0.2.0

func (v *Value) Int() (int64, error)

Int returns the value as int64, converting if possible.

func (*Value) Interface added in v0.4.0

func (v *Value) Interface() any

Interface returns the underlying Go value.

func (*Value) IsNil added in v0.4.0

func (v *Value) IsNil() bool

IsNil reports whether the value is nil.

func (*Value) IsTrue added in v0.4.0

func (v *Value) IsTrue() bool

IsTrue reports whether the value is truthy in a template context. False values: nil, false, 0, "", empty slice/map/array.

func (*Value) Iterate added in v0.4.0

func (v *Value) Iterate(fn func(idx, count int, key, val *Value) bool) error

Iterate calls fn for each element in a collection (slice, array, map, string). fn receives the iteration index, total count, key, and value. Returning false from fn stops iteration early.

func (*Value) Key added in v0.4.0

func (v *Value) Key(key any) (*Value, error)

Key returns the map value for the given key.

func (*Value) Len added in v0.4.0

func (v *Value) Len() (int, error)

Len returns the length of the value (string, slice, map, or array).

func (*Value) String added in v0.4.0

func (v *Value) String() string

String returns the string representation of the value.

type VariableNode added in v0.2.0

type VariableNode struct {
	Name string
	Line int
	Col  int
}

VariableNode represents a variable reference.

func NewVariableNode added in v0.4.0

func NewVariableNode(name string, line, col int) *VariableNode

NewVariableNode returns a new VariableNode.

func (*VariableNode) Evaluate added in v0.2.0

func (n *VariableNode) Evaluate(ctx *ExecutionContext) (*Value, error)

Evaluate resolves the variable in the current execution context.

func (*VariableNode) Position added in v0.4.0

func (n *VariableNode) Position() (int, int)

Position returns the position of the VariableNode.

func (*VariableNode) String added in v0.4.0

func (n *VariableNode) String() string

String returns a debug representation of the VariableNode.

Directories

Path Synopsis
examples
custom_filters command
Package main demonstrates registering custom filters.
Package main demonstrates registering custom filters.
custom_tags command
Package main demonstrates registering a custom tag via RegisterTag.
Package main demonstrates registering a custom tag via RegisterTag.
usage command
Package main demonstrates typical template usage.
Package main demonstrates typical template usage.

Jump to

Keyboard shortcuts

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