- Add support for whitespace control modifiers ({{-, -}}, {%-, -%})
- Implement {% spaceless %} tag for HTML whitespace removal
- Add token types for whitespace control tokens
- Improve text node handling to preserve spaces between words
- Add comprehensive tests for whitespace control features
- Update documentation in README.md and PROGRESS.md
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
9.5 KiB
Twig
Twig is a fast, memory-efficient Twig template engine implementation for Go. It aims to provide full support for the Twig template language in a Go-native way.
Features
- Zero-allocation rendering where possible
- Full Twig syntax support
- Template inheritance
- Extensible with filters, functions, tests, and operators
- Multiple loader types (filesystem, in-memory)
- Compatible with Go's standard library interfaces
Installation
go get github.com/semihalev/twig
Basic Usage
package main
import (
"fmt"
"github.com/semihalev/twig"
"os"
)
func main() {
// Create a new Twig engine
engine := twig.New()
// Add a template loader
loader := twig.NewFileSystemLoader([]string{"./templates"})
engine.RegisterLoader(loader)
// Render a template
context := map[string]interface{}{
"name": "World",
"items": []string{"apple", "banana", "orange"},
}
// Render to a string
result, err := engine.Render("index.twig", context)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(result)
// Or render directly to a writer
err = engine.RenderTo(os.Stdout, "index.twig", context)
if err != nil {
fmt.Println("Error:", err)
return
}
}
Supported Twig Syntax
- Variable printing:
{{ variable }} - Control structures:
{% if %},{% for %}, etc. - Filters:
{{ variable|filter }} - Functions:
{{ function(args) }} - Template inheritance:
{% extends %},{% block %} - Includes:
{% include %} - Comments:
{# comment #} - Array literals:
[1, 2, 3] - Conditional expressions:
condition ? true_expr : false_expr - And more...
Filter Support
Twig filters allow you to modify variables and expressions. Filters are applied using the pipe (|) character:
{{ 'hello'|upper }}
Supported Filters
This implementation supports many standard Twig filters:
upper: Converts a string to uppercaselower: Converts a string to lowercasecapitalize: Capitalizes a stringtrim: Removes whitespace from both sides of a stringslice: Extracts a slice of a string or arraydefault: Returns a default value if the variable is empty or undefinedjoin: Joins array elements with a delimitersplit: Splits a string by a delimiterlength/count: Returns the length of a string, array, or collectionreplace: Replaces occurrences of a substringescape/e: HTML-escapes a stringraw: Marks the value as safe (no escaping)first: Returns the first element of an array or first character of a stringlast: Returns the last element of an array or last character of a stringreverse: Reverses a string or arraysort: Sorts an arraykeys: Returns the keys of an array or mapmerge: Merges arrays or mapsdate: Formats a datenumber_format: Formats a numberabs: Returns the absolute value of a numberround: Rounds a numberstriptags: Strips HTML tags from a stringnl2br: Replaces newlines with HTML line breaks
Filter Usage Examples
Basic filters:
{{ 'hello'|upper }} {# Output: HELLO #}
{{ name|capitalize }} {# Output: Name #}
{{ 'hello world'|split(' ')|first }} {# Output: hello #}
Filters with arguments:
{{ 'hello world'|slice(0, 5) }} {# Output: hello #}
{{ [1, 2, 3]|join('-') }} {# Output: 1-2-3 #}
{{ 'hello'|replace('e', 'a') }} {# Output: hallo #}
Chained filters:
{{ 'hello'|upper|trim }} {# Output: HELLO #}
{{ ['a', 'b', 'c']|join(', ')|upper }} {# Output: A, B, C #}
Filters in expressions:
{{ (name|capitalize) ~ ' ' ~ (greeting|upper) }}
{% if name|length > 3 %}long{% else %}short{% endif %}
Custom Filter and Function Registration
Twig allows you to register custom filters and functions to extend its functionality.
Adding Custom Filters
// Create a new Twig engine
engine := twig.New()
// Add a simple filter that reverses words in a string
engine.AddFilter("reverse_words", func(value interface{}, args ...interface{}) (interface{}, error) {
s := toString(value)
words := strings.Fields(s)
// Reverse the order of words
for i, j := 0, len(words)-1; i < j; i, j = i+1, j-1 {
words[i], words[j] = words[j], words[i]
}
return strings.Join(words, " "), nil
})
// Use it in a template
template, _ := engine.ParseTemplate("{{ 'hello world'|reverse_words }}")
result, _ := template.Render(nil)
// Result: "world hello"
Adding Custom Functions
// Add a custom function that repeats a string n times
engine.AddFunction("repeat", func(args ...interface{}) (interface{}, error) {
if len(args) < 2 {
return "", nil
}
text := toString(args[0])
count, err := toInt(args[1])
if err != nil {
return "", err
}
return strings.Repeat(text, count), nil
})
// Use it in a template
template, _ := engine.ParseTemplate("{{ repeat('abc', 3) }}")
result, _ := template.Render(nil)
// Result: "abcabcabc"
Creating a Custom Extension
You can also create a custom extension with multiple filters and functions:
// Create and register a custom extension
engine.RegisterExtension("my_extension", func(ext *twig.CustomExtension) {
// Add a filter
ext.Filters["shuffle"] = func(value interface{}, args ...interface{}) (interface{}, error) {
s := toString(value)
runes := []rune(s)
// Simple shuffle algorithm
rand.Shuffle(len(runes), func(i, j int) {
runes[i], runes[j] = runes[j], runes[i]
})
return string(runes), nil
}
// Add a function
ext.Functions["add"] = func(args ...interface{}) (interface{}, error) {
if len(args) < 2 {
return 0, nil
}
a, errA := toFloat64(args[0])
b, errB := toFloat64(args[1])
if errA != nil || errB != nil {
return 0, nil
}
return a + b, nil
}
})
Development Mode and Caching
Twig provides several options to control template caching and debug behavior:
// Create a new Twig engine
engine := twig.New()
// Enable development mode (enables debug, enables auto-reload, disables caching)
engine.SetDevelopmentMode(true)
// Or control individual settings
engine.SetDebug(true) // Enable debug mode
engine.SetCache(false) // Disable template caching
engine.SetAutoReload(true) // Enable template auto-reloading
Development Mode
When development mode is enabled:
- Template caching is disabled, ensuring you always see the latest changes
- Auto-reload is enabled, which will check for template modifications
- Debug mode is enabled for more detailed error messages
This is ideal during development to avoid having to restart your application when templates change.
Auto-Reload & Template Modification Checking
The engine can automatically detect when template files change on disk and reload them:
// Enable auto-reload to detect template changes
engine.SetAutoReload(true)
When auto-reload is enabled:
- The engine tracks the last modification time of each template
- When a template is requested, it checks if the file has been modified
- If the file has changed, it automatically reloads the template
- If the file hasn't changed, it uses the cached version (if caching is enabled)
This provides the best of both worlds:
- Fast performance (no unnecessary file system access for unchanged templates)
- Always up-to-date content (automatic reload when templates change)
Production Mode
By default, Twig runs in production mode:
- Template caching is enabled for maximum performance
- Auto-reload is disabled to avoid unnecessary file system checks
- Debug mode is disabled to reduce overhead
Whitespace Handling
Twig templates can have significant whitespace that affects the rendered output. This implementation supports several mechanisms for controlling whitespace:
Whitespace Control Features
-
Whitespace Control Modifiers
The whitespace control modifiers (
-character) allow you to trim whitespace around tags:<div> {{- greeting -}} {# Removes whitespace before and after #} </div>Using these modifiers:
{{- ... }}: Removes whitespace before the variable output{{ ... -}}: Removes whitespace after the variable output{{- ... -}}: Removes whitespace both before and after{%- ... %}: Removes whitespace before the block tag{% ... -%}: Removes whitespace after the block tag{%- ... -%}: Removes whitespace both before and after
-
Spaceless Tag
The
spacelesstag removes whitespace between HTML tags (but preserves whitespace within text content):{% spaceless %} <div> <strong>Whitespace is removed between HTML tags</strong> </div> {% endspaceless %}This produces:
<div><strong>Whitespace is removed between HTML tags</strong></div>
These features help you create cleaner output, especially when generating HTML with proper indentation in templates but needing compact output for production.
Performance
The library is designed with performance in mind:
- Minimal memory allocations
- Efficient parsing and rendering
- Template caching
- Production/development mode toggle
- Optimized filter chain processing
License
This project is licensed under the MIT License - see the LICENSE file for details.