A Go implementation of the Stencil template engine for DOCX files.
go-stencil is a powerful template engine that allows you to create dynamic Microsoft Word (.docx) documents using a simple template syntax. It's a Go port of the original Stencil project, with the implementation primarily developed by Claude Code (Anthropic's AI assistant) through a systematic, test-driven approach.
- Simple template syntax using
{{placeholders}}
- Control structures for conditionals and loops
- Built-in functions for formatting and data manipulation
- Support for tables and complex document structures
- High performance with template caching
- Thread-safe rendering with concurrent template support
- Extensible with custom functions and providers
- Minimal dependencies - uses only the Go standard library
go get github.com/benjaminschreck/go-stencil
package main
import (
"io"
"log"
"os"
"time"
"github.com/benjaminschreck/go-stencil/pkg/stencil"
)
func main() {
// Prepare a template
tmpl, err := stencil.PrepareFile("template.docx")
if err != nil {
log.Fatal(err)
}
defer tmpl.Close()
// Create data for rendering
data := stencil.TemplateData{
"name": "John Doe",
"date": time.Now().Format("January 2, 2006"),
"items": []map[string]interface{}{
{"name": "Item 1", "price": 10.00},
{"name": "Item 2", "price": 20.00},
},
}
// Render the template
output, err := tmpl.Render(data)
if err != nil {
log.Fatal(err)
}
// Save the output
file, err := os.Create("output.docx")
if err != nil {
log.Fatal(err)
}
defer file.Close()
_, err = io.Copy(file, output)
if err != nil {
log.Fatal(err)
}
}
Hello {{name}}!
Your age is {{age}}.
{{if isPremium}}
Welcome, premium user!
{{else}}
Consider upgrading to premium.
{{end}}
{{for item in items}}
- {{item.name}}: ${{item.price}}
{{end}}
Important: All functions require parentheses ()
, even when called with no arguments.
Total: {{format("%.2f", total)}}
Date: {{date("2006-01-02", orderDate)}}
No arguments: {{timestamp()}}
format
Function
- Purpose: Formats values using printf-style formatting (like Go's
fmt.Sprintf
) - Syntax:
{{format("pattern", value)}}
- Example:
{{format("%.2f", 19.999)}}
outputs"19.99"
- Common patterns:
"%.2f"
- Two decimal places (19.99)"%d"
- Integer (42)"%05d"
- Zero-padded integer (00042)"%s: %d"
- String formatting ("Count: 5")
date
Function
- Purpose: Formats date/time values into readable strings
- Syntax:
{{date("pattern", dateValue)}}
- Example:
{{date("2006-01-02", orderDate)}}
outputs"2024-03-15"
- Uses Go's time format based on the reference date: Mon Jan 2 15:04:05 MST 2006
- Common patterns:
"2006-01-02"
- Date only (2024-03-15)"January 2, 2006"
- Full date (March 15, 2024)"02/01/2006"
- European format (15/03/2024)"01/02/2006"
- US format (03/15/2024)"Monday, Jan 2, 2006 3:04 PM"
- Full datetime"15:04:05"
- Time only (10:30:00)
The simplest way to use go-stencil is through the package-level functions:
// Prepare a template from a file
tmpl, err := stencil.PrepareFile("template.docx")
// Or from an io.Reader
tmpl, err := stencil.Prepare(reader)
// Render with data
output, err := tmpl.Render(data)
// Don't forget to close when done
tmpl.Close()
For more control, use the Engine API:
// Create an engine with custom configuration
engine := stencil.NewWithOptions(
stencil.WithCache(100), // Enable caching with max 100 templates
stencil.WithFunction("customFunc", myFunc),
)
// Or with a complete custom configuration
config := &stencil.Config{
CacheMaxSize: 100,
CacheTTL: 10 * time.Minute,
LogLevel: "info",
MaxRenderDepth: 50,
StrictMode: true,
}
engine := stencil.NewWithConfig(config)
// Use the engine
tmpl, err := engine.PrepareFile("template.docx")
You can extend go-stencil with custom functions:
// Define a custom function
type GreetingFunction struct{}
func (f GreetingFunction) Name() string {
return "greeting"
}
func (f GreetingFunction) Call(args ...interface{}) (interface{}, error) {
if len(args) < 1 {
return "Hello!", nil
}
return fmt.Sprintf("Hello, %v!", args[0]), nil
}
// Register the function
stencil.RegisterGlobalFunction("greeting", GreetingFunction{})
// Use in template: {{greeting("World")}} or {{greeting()}}
// Note: Functions always require parentheses, even with no arguments
// Or use a function provider for multiple functions
type MyFunctionProvider struct{}
func (p MyFunctionProvider) ProvideFunctions() map[string]stencil.Function {
return map[string]stencil.Function{
"greeting": GreetingFunction{},
"farewell": FarewellFunction{},
}
}
stencil.RegisterFunctionsFromProvider(MyFunctionProvider{})
Fragments allow you to reuse content across templates:
// Add a text fragment
err := tmpl.AddFragment("copyright", "© 2024 My Company. All rights reserved.")
// Add a pre-formatted DOCX fragment
fragmentBytes, _ := os.ReadFile("header.docx")
err := tmpl.AddFragmentFromBytes("header", fragmentBytes)
// Use in template: {{include "copyright"}}
Caching improves performance when rendering the same template multiple times:
// Enable caching globally
stencil.SetCacheConfig(100, 10*time.Minute)
// Templates prepared with PrepareFile are automatically cached
tmpl1, _ := stencil.PrepareFile("template.docx") // Reads from disk
tmpl2, _ := stencil.PrepareFile("template.docx") // Returns from cache
// Clear the cache when needed
stencil.ClearCache()
go-stencil includes a comprehensive set of built-in functions:
empty(value)
- Check if a value is emptycoalesce(value1, value2, ...)
- Return the first non-empty valuelist(items...)
- Create a list from argumentsdata()
- Access the entire template data context (no arguments required)map(key, collection)
- Extract a specific field from each item in a collection
str(value)
- Convert to stringlowercase(text)
- Convert to lowercaseuppercase(text)
- Convert to uppercasetitlecase(text)
- Convert to title casejoin(items, separator)
- Join items with separatorjoinAnd(items)
- Join items with commas and "and"replace(text, old, new)
- Replace textlength(value)
- Get length of string, array, or map
integer(value)
- Convert to integerdecimal(value)
- Convert to decimalround(number)
- Round to nearest integerfloor(number)
- Round downceil(number)
- Round upsum(numbers...)
- Sum of numbers
format(pattern, value)
- Format using printf-style patternformatWithLocale(locale, pattern, value)
- Format with specific localedate(pattern, date)
- Format date/timecurrency(amount)
- Format as currencypercent(value)
- Format as percentage
switch(value, case1, result1, case2, result2, ..., default)
- Switch expressioncontains(item, collection)
- Check if collection contains itemrange(start, end)
- Generate a range of numbers
pageBreak()
- Insert a page break (no arguments required)hideRow()
- Hide the current table row (no arguments required)hideColumn()
- Hide the current table column (no arguments required)html(content)
- Insert HTML-formatted contentxml(content)
- Insert raw XML contentreplaceLink(url)
- Replace a hyperlinkinclude(fragmentName)
- Include a named fragment
See the examples directory for complete working examples:
- Simple Example - Basic template rendering
- Advanced Example - Custom functions, fragments, and more
- Getting Started Guide - Quick introduction and first steps
- API Reference - Complete API documentation
- Functions Reference - All built-in template functions
- Examples - Real-world usage examples
- Best Practices - Tips for effective template design
This project is licensed under the Eclipse Public License 2.0 (EPL-2.0), the same license as the original Stencil project.
go-stencil is a Go implementation inspired by the original Stencil project by Janos Erdos.