Documentation
¶
Overview ¶
Package liquid is a pure Go implementation of Shopify Liquid templates, developed for use in https://github.com/osteele/gojekyll.
See the project README https://github.com/joaqu1m/liquid for additional information and implementation status.
The liquid package itself is versioned in gopkg.in. Subpackages have no compatibility guarantees. Except where specifically documented, the “public” entities of subpackages are intended only for use by the liquid package and its subpackages.
Example ¶
engine := NewEngine()
source := `<h1>{{ page.title }}</h1>`
bindings := map[string]any{
"page": map[string]string{
"title": "Introduction",
},
}
out, err := engine.ParseAndRenderString(source, bindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
Output: <h1>Introduction</h1>
Index ¶
- func FromDrop(object any) any
- func IterationKeyedMap(m map[string]any) tags.IterationKeyedMap
- type AssignmentTrace
- type AuditError
- type AuditOptions
- type AuditResult
- type Bindings
- type CaptureTrace
- type ComparisonTrace
- type CompiledExpression
- type ConditionBranch
- type ConditionItem
- type ConditionTrace
- type ContextDrop
- type Diagnostic
- type DiagnosticSeverity
- type Drop
- type DropMethodMissing
- type DropRenderContext
- type Engine
- func (e *Engine) Analyze(t *Template) (*StaticAnalysis, error)
- func (e *Engine) ClearCache()
- func (e *Engine) Delims(objectLeft, objectRight, tagLeft, tagRight string) *Engine
- func (e *Engine) EnableCache()
- func (e *Engine) EnableJekyllExtensions()
- func (e *Engine) FullVariables(t *Template) ([]Variable, error)
- func (e *Engine) GetGlobals() map[string]any
- func (e *Engine) GlobalFullVariables(t *Template) ([]Variable, error)
- func (e *Engine) GlobalVariableSegments(t *Template) ([]VariableSegment, error)
- func (e *Engine) GlobalVariables(t *Template) ([]string, error)
- func (e *Engine) LaxFilters()
- func (e *Engine) LaxTags()
- func (e *Engine) ParseAndAnalyze(source []byte) (*Template, *StaticAnalysis, error)
- func (e *Engine) ParseAndFRender(w io.Writer, source []byte, b Bindings, opts ...RenderOption) SourceError
- func (e *Engine) ParseAndRender(source []byte, b Bindings, opts ...RenderOption) ([]byte, SourceError)
- func (e *Engine) ParseAndRenderString(source string, b Bindings, opts ...RenderOption) (string, SourceError)
- func (e *Engine) ParseString(source string) (*Template, SourceError)
- func (e *Engine) ParseStringAudit(source string) *ParseResult
- func (e *Engine) ParseTemplate(source []byte) (*Template, SourceError)
- func (e *Engine) ParseTemplateAndCache(source []byte, path string, line int) (*Template, SourceError)
- func (e *Engine) ParseTemplateAudit(source []byte) *ParseResult
- func (e *Engine) ParseTemplateLocation(source []byte, path string, line int) (*Template, SourceError)
- func (e *Engine) RegisterBlock(name string, td Renderer)
- func (e *Engine) RegisterBlockAnalyzer(name string, a render.BlockAnalyzer)
- func (e *Engine) RegisterFilter(name string, fn any)
- func (e *Engine) RegisterTag(name string, td Renderer)
- func (e *Engine) RegisterTagAnalyzer(name string, a render.TagAnalyzer)
- func (e *Engine) RegisterTemplateStore(templateStore render.TemplateStore)
- func (e *Engine) SetAutoEscapeReplacer(replacer render.Replacer)
- func (e *Engine) SetExceptionHandler(fn func(error) string)
- func (e *Engine) SetGlobalFilter(fn func(any) (any, error))
- func (e *Engine) SetGlobals(globals map[string]any)
- func (e *Engine) SetGreedy(v bool)
- func (e *Engine) SetTrimOutputLeft(v bool)
- func (e *Engine) SetTrimOutputRight(v bool)
- func (e *Engine) SetTrimTagLeft(v bool)
- func (e *Engine) SetTrimTagRight(v bool)
- func (e *Engine) StrictNestedVariables()
- func (e *Engine) StrictVariables()
- func (e *Engine) UnregisterTag(name string)
- func (e *Engine) VariableSegments(t *Template) ([]VariableSegment, error)
- func (e *Engine) Variables(t *Template) ([]string, error)
- type Expression
- type ExpressionKind
- type FilterStep
- type GroupTrace
- type IterationTrace
- type ParseResult
- type Position
- type Range
- type RelatedInfo
- type RenderOption
- func WithContext(ctx context.Context) RenderOption
- func WithErrorHandler(fn func(error) string) RenderOption
- func WithGlobalFilter(fn func(any) (any, error)) RenderOption
- func WithGlobals(globals map[string]any) RenderOption
- func WithLaxFilters() RenderOption
- func WithSizeLimit(n int64) RenderOption
- func WithStrictVariables() RenderOption
- type Renderer
- type SourceError
- type StaticAnalysis
- type Template
- func (t *Template) Analyze() (*StaticAnalysis, error)
- func (t *Template) FRender(w io.Writer, vars Bindings, opts ...RenderOption) SourceError
- func (t *Template) FullVariables() ([]Variable, error)
- func (t *Template) GetRoot() render.Node
- func (t *Template) GlobalFullVariables() ([]Variable, error)
- func (t *Template) GlobalVariableSegments() ([]VariableSegment, error)
- func (t *Template) GlobalVariables() ([]string, error)
- func (t *Template) ParseTree() *TemplateNode
- func (t *Template) Render(vars Bindings, opts ...RenderOption) ([]byte, SourceError)
- func (t *Template) RenderAudit(vars Bindings, opts AuditOptions, renderOpts ...RenderOption) (*AuditResult, *AuditError)
- func (t *Template) RenderString(b Bindings, opts ...RenderOption) (string, SourceError)
- func (t *Template) Validate() (*AuditResult, error)
- func (t *Template) VariableSegments() ([]VariableSegment, error)
- func (t *Template) Variables() ([]string, error)
- func (t *Template) Walk(fn WalkFunc)
- type TemplateNode
- type TemplateNodeKind
- type Variable
- type VariableSegment
- type VariableTrace
- type WalkFunc
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FromDrop ¶
FromDrop returns object.ToLiquid() if object's type implements this function; else the object itself.
func IterationKeyedMap ¶
func IterationKeyedMap(m map[string]any) tags.IterationKeyedMap
IterationKeyedMap returns a map whose {% for %} tag iteration values are its keys, instead of [key, value] pairs. Use this to create a Go map with the semantics of a Ruby struct drop.
Example ¶
vars := map[string]any{
"map": map[string]any{"a": 1},
"keyed_map": IterationKeyedMap(map[string]any{"a": 1}),
}
engine := NewEngine()
out, err := engine.ParseAndRenderString(
`{% for k in map %}{{ k[0] }}={{ k[1] }}.{% endfor %}`, vars)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
out, err = engine.ParseAndRenderString(
`{% for k in keyed_map %}{{ k }}={{ keyed_map[k] }}.{% endfor %}`, vars)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
Output: a=1. a=1.
Types ¶
type AssignmentTrace ¶
type AssignmentTrace struct {
Variable string `json:"variable"`
Path []string `json:"path,omitempty"`
Value any `json:"value"`
Pipeline []FilterStep `json:"pipeline"`
}
AssignmentTrace is produced by {% assign %}.
type AuditError ¶
type AuditError struct {
// contains filtered or unexported fields
}
AuditError is returned by RenderAudit when one or more runtime errors were encountered during rendering. It implements the error interface and exposes the individual typed errors via Errors().
func (*AuditError) Error ¶
func (e *AuditError) Error() string
func (*AuditError) Errors ¶
func (e *AuditError) Errors() []SourceError
Errors returns the individual errors that were encountered during the render. Each element is typed (e.g. *render.UndefinedVariableError) and is the same kind of error that a normal Render would return.
type AuditOptions ¶
type AuditOptions struct {
// --- Render trace ---
TraceVariables bool // Trace {{ expr }} with resolved value and filter pipeline
TraceConditions bool // Trace {% if/unless/case %} with branch structure
TraceIterations bool // Trace {% for/tablerow %} with loop metadata
TraceAssignments bool // Trace {% assign %} and {% capture %} with resulting values
// MaxIterationTraceItems limits how many loop iterations have their inner
// expressions traced. 0 means unlimited.
// When the limit is reached, the IterationTrace.Truncated field is set to true.
MaxIterationTraceItems int
}
AuditOptions controls what RenderAudit collects. It does not duplicate engine/render options — behaviours like StrictVariables are passed via the ...RenderOption variadic, exactly like Render.
type AuditResult ¶
type AuditResult struct {
Output string `json:"output"`
Expressions []Expression `json:"expressions"`
Diagnostics []Diagnostic `json:"diagnostics"`
}
AuditResult is the structured output of RenderAudit. It is always non-nil, even when an error was returned — Output may be partial and Diagnostics explains what happened.
type Bindings ¶
Bindings is a map of variable names to values.
Clients need not use this type. It is used solely for documentation. Callers can use instances of map[string]any itself as argument values to functions declared with this parameter type.
type CaptureTrace ¶
CaptureTrace is produced by {% capture %}…{% endcapture %}.
type ComparisonTrace ¶
type ComparisonTrace struct {
Expression string `json:"expression"` // raw source text of this comparison
Left any `json:"left"`
Operator string `json:"operator"` // "==", "!=", ">", "<", ">=", "<=", "contains"
Right any `json:"right"`
Result bool `json:"result"`
}
ComparisonTrace records a single primitive binary comparison in a condition.
type CompiledExpression ¶
type CompiledExpression = expressions.Expression
CompiledExpression is a compiled Liquid expression that can evaluate variable references. It is used when implementing custom tag/block analyzers via RegisterTagAnalyzer and RegisterBlockAnalyzer — pass it in NodeAnalysis.Arguments so the static analysis engine can walk its variable references.
func ParseExpression ¶
func ParseExpression(source string) (CompiledExpression, error)
ParseExpression parses a Liquid expression string into a CompiledExpression that can be used with RegisterTagAnalyzer / RegisterBlockAnalyzer. Returns an error if the expression contains a syntax error.
Example:
e.RegisterTagAnalyzer("my_tag", func(args string) render.NodeAnalysis {
expr, err := ParseExpression(args)
if err != nil { return render.NodeAnalysis{} }
return render.NodeAnalysis{Arguments: []CompiledExpression{expr}}
})
type ConditionBranch ¶
type ConditionBranch struct {
Kind string `json:"kind"`
Range Range `json:"range"`
Executed bool `json:"executed"`
Items []ConditionItem `json:"items,omitempty"` // condition items tree; empty for else
}
ConditionBranch represents one branch (if / elsif / else / unless / when) of a condition block.
type ConditionItem ¶
type ConditionItem struct {
Comparison *ComparisonTrace `json:"comparison,omitempty"`
Group *GroupTrace `json:"group,omitempty"`
}
ConditionItem is a union node in a condition branch's items tree. Exactly one of Comparison or Group is non-nil.
type ConditionTrace ¶
type ConditionTrace struct {
Branches []ConditionBranch `json:"branches"`
}
ConditionTrace is produced by {% if %}, {% unless %}, or {% case %}.
type ContextDrop ¶
type ContextDrop = values.ContextSetter
ContextDrop is an optional interface for Drop types that need access to the current rendering context. When Liquid resolves a variable and the value implements ContextDrop, it calls SetContext with the current render context.
This mirrors Ruby Liquid's context= setter and LiquidJS's contextDrop.
Example:
type RegistersDrop struct {
ctx liquid.DropRenderContext
}
func (d *RegistersDrop) ToLiquid() any { return d }
func (d *RegistersDrop) SetContext(ctx liquid.DropRenderContext) {
d.ctx = ctx
}
func (d *RegistersDrop) CurrentUser() any {
return d.ctx.Get("current_user")
}
Example ¶
// type scopeDrop struct {
// watchKey string
// ctx liquid.DropRenderContext
// }
//
// func (d *scopeDrop) SetContext(ctx liquid.DropRenderContext) { d.ctx = ctx }
// func (d *scopeDrop) Observed() any { return d.ctx.Get(d.watchKey) }
engine := NewEngine()
bindings := map[string]any{
"probe": &scopeDrop{watchKey: "user"},
"user": "Alice",
}
out, err := engine.ParseAndRenderString(`{{ probe.Observed }}`, bindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
Output: Alice
type Diagnostic ¶
type Diagnostic struct {
Range Range `json:"range"`
Severity DiagnosticSeverity `json:"severity"`
Code string `json:"code"`
Message string `json:"message"`
Source string `json:"source"`
Related []RelatedInfo `json:"related,omitempty"`
}
Diagnostic represents an error, warning, or informational message tied to a source location. The design follows the LSP Diagnostic pattern.
type DiagnosticSeverity ¶
type DiagnosticSeverity string
DiagnosticSeverity indicates how serious a diagnostic is.
const ( SeverityError DiagnosticSeverity = "error" SeverityWarning DiagnosticSeverity = "warning" SeverityInfo DiagnosticSeverity = "info" )
type Drop ¶
type Drop interface {
ToLiquid() any
}
Drop indicates that the object will present to templates as its ToLiquid value.
Example (Map) ¶
// type redConvertible struct{}
//
// func (c redConvertible) ToLiquid() any {
// return map[string]any{
// "color": "red",
// }
// }
engine := NewEngine()
bindings := map[string]any{
"car": redConvertible{},
}
template := `{{ car.color }}`
out, err := engine.ParseAndRenderString(template, bindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
Output: red
Example (Struct) ¶
// type car struct{ color, model string }
//
// func (c car) ToLiquid() any {
// return carDrop{c.model, c.color}
// }
//
// type carDrop struct {
// Model string
// Color string `liquid:"color"`
// }
//
// func (c carDrop) Drive() string {
// return "AWD"
// }
engine := NewEngine()
bindings := map[string]any{
"car": car{"blue", "S85"},
}
template := `{{ car.color }} {{ car.Drive }} Model {{ car.Model }}`
out, err := engine.ParseAndRenderString(template, bindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
Output: blue AWD Model S85
type DropMethodMissing ¶
DropMethodMissing is an optional interface that custom Drop types may implement to handle property accesses for keys that are not defined as struct fields or methods. When Liquid looks up a property on a struct and finds nothing, it checks whether the struct implements DropMethodMissing and calls MissingMethod with the missing key name.
This mirrors Ruby Liquid's liquid_method_missing and LiquidJS's liquidMethodMissing.
Example:
type MyDrop struct{ Data map[string]any }
func (d MyDrop) MissingMethod(key string) any {
return d.Data[key]
}
Example ¶
// type dynamicDrop struct {
// Name string
// dynamic map[string]any
// }
//
// func (d dynamicDrop) MissingMethod(key string) any {
// return d.dynamic[key]
// }
engine := NewEngine()
bindings := map[string]any{
"product": dynamicDrop{
Name: "Widget",
dynamic: map[string]any{"price": 9.99, "sku": "W-001"},
},
}
out, err := engine.ParseAndRenderString(`{{ product.Name }} — SKU: {{ product.sku }}, price: {{ product.price }}`, bindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
Output: Widget — SKU: W-001, price: 9.99
type DropRenderContext ¶
type DropRenderContext = values.ContextAccess
DropRenderContext is the rendering context injected into drops that implement ContextDrop. It provides read/write access to the current rendering scope.
This mirrors Ruby Liquid's Context object (scoped to variable bindings).
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
An Engine parses template source into renderable text.
An engine can be configured with additional filters and tags.
Configuration methods (RegisterTag, RegisterFilter, SetGlobals, etc.) must be called before the engine is first used for parsing or rendering. Calling them after the first parse panics with a clear message, preventing data races on the shared grammar and filter maps.
func NewBasicEngine ¶
func NewBasicEngine() *Engine
NewBasicEngine returns a new Engine without the standard filters or tags.
func (*Engine) Analyze ¶
func (e *Engine) Analyze(t *Template) (*StaticAnalysis, error)
Analyze performs a full static analysis of the template and returns a StaticAnalysis with variables (all and global), locally-defined names, and tag names used.
func (*Engine) ClearCache ¶
func (e *Engine) ClearCache()
ClearCache evicts all entries from the template cache. Has no effect if the cache is not enabled.
func (*Engine) Delims ¶
Delims sets the action delimiters to the specified strings, to be used in subsequent calls to ParseTemplate, ParseTemplateLocation, ParseAndRender, or ParseAndRenderString. An empty delimiter stands for the corresponding default: objectLeft = {{, objectRight = }}, tagLeft = {% , tagRight = %}
func (*Engine) EnableCache ¶
func (e *Engine) EnableCache()
EnableCache enables a simple in-memory template cache keyed by source string. When enabled, ParseString (and the convenience methods ParseAndRenderString, ParseAndRender, ParseAndFRender) will return cached *Template values for source strings that have been parsed before, avoiding redundant parsing.
The cache is unbounded; call ClearCache to release cached templates. Useful for hot-path rendering where the same template source is used many times.
func (*Engine) EnableJekyllExtensions ¶
func (e *Engine) EnableJekyllExtensions()
EnableJekyllExtensions enables Jekyll-specific extensions to Liquid. This includes support for dot notation in assign tags (e.g., {% assign page.canonical_url = value %}). Note: This is not part of the Shopify Liquid standard but is used in Jekyll and Gojekyll.
func (*Engine) FullVariables ¶
FullVariables returns all variable references with full path and source location. The Global field on each Variable indicates whether it comes from the outer scope.
func (*Engine) GetGlobals ¶
GetGlobals returns the engine-level global variables.
func (*Engine) GlobalFullVariables ¶
GlobalFullVariables returns global variable references with full path and source location.
func (*Engine) GlobalVariableSegments ¶
func (e *Engine) GlobalVariableSegments(t *Template) ([]VariableSegment, error)
GlobalVariableSegments returns paths of variables that are expected from the outer scope (i.e., not defined within the template itself via assign, capture, for, etc.).
For example:
{{ customer.first_name }} {% assign x = "hello" %} {{ order.total }}
→ [["customer", "first_name"], ["order", "total"]]
x does not appear because it is defined within the template.
func (*Engine) GlobalVariables ¶
GlobalVariables returns the unique root names of variables expected from the outer scope, without path details. For example, {{ customer.first_name }} contributes "customer".
func (*Engine) LaxFilters ¶
func (e *Engine) LaxFilters()
LaxFilters causes the renderer to silently pass through the input value when the template contains an undefined filter, matching Shopify Liquid behavior. By default, undefined filters cause an error.
func (*Engine) LaxTags ¶
func (e *Engine) LaxTags()
LaxTags causes unknown {% tag %} names to be silently compiled as no-ops (empty output) instead of producing a parse/compile error. Analogous to Ruby Liquid's error_mode: :lax for tag names. Must be called before ParseTemplate / ParseString.
func (*Engine) ParseAndAnalyze ¶
func (e *Engine) ParseAndAnalyze(source []byte) (*Template, *StaticAnalysis, error)
ParseAndAnalyze parses a template source and performs static analysis in one step. It is equivalent to calling ParseTemplate followed by Analyze.
func (*Engine) ParseAndFRender ¶
func (e *Engine) ParseAndFRender(w io.Writer, source []byte, b Bindings, opts ...RenderOption) SourceError
ParseAndFRender parses and then renders the template into w.
RenderOptions can be passed to override engine-level settings for this call only. See ParseAndRender for details.
func (*Engine) ParseAndRender ¶
func (e *Engine) ParseAndRender(source []byte, b Bindings, opts ...RenderOption) ([]byte, SourceError)
ParseAndRender parses and then renders the template.
RenderOptions can be passed to override engine-level settings for this call only. For example, adding WithStrictVariables() enables strict variable checking even if StrictVariables was not called on the engine.
func (*Engine) ParseAndRenderString ¶
func (e *Engine) ParseAndRenderString(source string, b Bindings, opts ...RenderOption) (string, SourceError)
ParseAndRenderString is a convenience wrapper for ParseAndRender, that takes string input and returns a string.
RenderOptions can be passed to override engine-level settings for this call only. See ParseAndRender for details.
Example ¶
engine := NewEngine()
source := `{{ hello | capitalize | append: " Mundo" }}`
bindings := map[string]any{"hello": "hola"}
out, err := engine.ParseAndRenderString(source, bindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
Output: Hola Mundo
func (*Engine) ParseString ¶
func (e *Engine) ParseString(source string) (*Template, SourceError)
ParseString creates a new Template using the engine configuration. If the template cache is enabled (EnableCache), previously parsed templates are returned from the cache without re-parsing.
func (*Engine) ParseStringAudit ¶ added in v1.1.0
func (e *Engine) ParseStringAudit(source string) *ParseResult
ParseStringAudit is the string-input convenience variant of ParseTemplateAudit.
func (*Engine) ParseTemplate ¶
func (e *Engine) ParseTemplate(source []byte) (*Template, SourceError)
ParseTemplate creates a new Template using the engine configuration.
Example ¶
engine := NewEngine()
source := `{{ hello | capitalize | append: " Mundo" }}`
bindings := map[string]any{"hello": "hola"}
tpl, err := engine.ParseString(source)
if err != nil {
log.Fatalln(err)
}
out, err := tpl.RenderString(bindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
Output: Hola Mundo
func (*Engine) ParseTemplateAndCache ¶
func (e *Engine) ParseTemplateAndCache(source []byte, path string, line int) (*Template, SourceError)
ParseTemplateAndCache is the same as ParseTemplateLocation, except that the source location is used for error reporting and for the {% include %} tag. If parsing is successful, provided source is then cached, and can be retrieved by {% include %} tags, as long as there is not a real file in the provided path.
The path and line number are used for error reporting. The path is also the reference for relative pathnames in the {% include %} tag.
func (*Engine) ParseTemplateAudit ¶ added in v1.1.0
func (e *Engine) ParseTemplateAudit(source []byte) *ParseResult
ParseTemplateAudit parses source in error-recovering mode and returns a *ParseResult containing the compiled template and all parse-time diagnostics.
Unlike ParseTemplate, ParseTemplateAudit never returns a SourceError. All problems are captured as Diagnostic entries in ParseResult.Diagnostics, using the same Diagnostic type used by (*Template).RenderAudit.
ParseResult.Template is non-nil when parsing produced a usable compiled template. Callers should check Template before rendering:
result := eng.ParseTemplateAudit(source)
for _, d := range result.Diagnostics {
log.Printf("%s at line %d: %s", d.Severity, d.Range.Start.Line, d.Message)
}
if result.Template != nil {
output, err := result.Template.RenderString(binds)
_ = output; _ = err
}
Diagnostics that may appear:
- "unclosed-tag" (error): a block tag was opened but never closed; ParseResult.Template is nil when this occurs.
- "unexpected-tag" (error): a closing or clause tag appeared without a matching open block; ParseResult.Template is nil when this occurs.
- "syntax-error" (error): invalid expression inside {{ }} or tag args.
- "undefined-filter" (error): a filter name used is not registered.
- "empty-block" (info): a block tag has no content in any branch.
func (*Engine) ParseTemplateLocation ¶
func (e *Engine) ParseTemplateLocation(source []byte, path string, line int) (*Template, SourceError)
ParseTemplateLocation is the same as ParseTemplate, except that the source location is used for error reporting and for the {% include %} tag.
The path and line number are used for error reporting. The path is also the reference for relative pathnames in the {% include %} tag.
func (*Engine) RegisterBlock ¶
RegisterBlock defines a block e.g. {% tag %}…{% endtag %}.
Example ¶
engine := NewEngine()
engine.RegisterBlock("length", func(c render.Context) (string, error) {
s, err := c.InnerString()
if err != nil {
return "", err
}
n := len(s)
return strconv.Itoa(n), nil
})
template := `{% length %}abc{% endlength %}`
out, err := engine.ParseAndRenderString(template, emptyBindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
Output: 3
func (*Engine) RegisterBlockAnalyzer ¶
func (e *Engine) RegisterBlockAnalyzer(name string, a render.BlockAnalyzer)
RegisterBlockAnalyzer registers a static analysis function for a block tag previously registered with RegisterBlock. The analyzer is invoked during static analysis.
func (*Engine) RegisterFilter ¶
RegisterFilter defines a Liquid filter, for use as `{{ value | my_filter }}` or `{{ value | my_filter: arg }}`.
A filter is a function that takes at least one input, and returns one or two outputs. If it returns two outputs, the second must have type error.
Examples:
* https://github.com/joaqu1m/liquid/blob/main/filters/standard_filters.go
* https://github.com/osteele/gojekyll/blob/master/filters/filters.go
Example ¶
engine := NewEngine()
engine.RegisterFilter("has_prefix", strings.HasPrefix)
template := `{{ title | has_prefix: "Intro" }}`
bindings := map[string]any{
"title": "Introduction",
}
out, err := engine.ParseAndRenderString(template, bindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
Output: true
Example (Optional_argument) ¶
engine := NewEngine()
// func(a, b int) int) would default the second argument to zero.
// Then we can't tell the difference between {{ n | inc }} and
// {{ n | inc: 0 }}. A function in the parameter list has a special
// meaning as a default parameter.
engine.RegisterFilter("inc", func(a int, b func(int) int) int {
return a + b(1)
})
template := `10 + 1 = {{ m | inc }}; 20 + 5 = {{ n | inc: 5 }}`
bindings := map[string]any{
"m": 10,
"n": "20",
}
out, err := engine.ParseAndRenderString(template, bindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
Output: 10 + 1 = 11; 20 + 5 = 25
func (*Engine) RegisterTag ¶
RegisterTag defines a tag e.g. {% tag %}.
Further examples are in https://github.com/osteele/gojekyll/blob/master/tags/tags.go
Example ¶
engine := NewEngine()
engine.RegisterTag("echo", func(c render.Context) (string, error) {
return c.TagArgs(), nil
})
template := `{% echo hello world %}`
out, err := engine.ParseAndRenderString(template, emptyBindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
Output: hello world
func (*Engine) RegisterTagAnalyzer ¶
func (e *Engine) RegisterTagAnalyzer(name string, a render.TagAnalyzer)
RegisterTagAnalyzer registers a static analysis function for a simple tag previously registered with RegisterTag. The analyzer is invoked during static analysis to determine which variables the tag reads and which it defines in scope.
Use render.NodeAnalysis.Arguments to declare variable expressions the tag reads, and render.NodeAnalysis.LocalScope to declare variable names the tag defines.
func (*Engine) RegisterTemplateStore ¶
func (e *Engine) RegisterTemplateStore(templateStore render.TemplateStore)
func (*Engine) SetAutoEscapeReplacer ¶
SetAutoEscapeReplacer enables auto-escape functionality where the output of expression blocks ({{ ... }}) is passed though a render.Replacer during rendering, unless it's been marked as safe by applying the 'safe' filter. This filter is automatically registered when this method is called. The filter must be applied last. A replacer is provided for escaping HTML (see render.HtmlEscaper).
func (*Engine) SetExceptionHandler ¶
SetExceptionHandler registers a function that is called when a render-time error occurs instead of stopping the render. The handler receives the error and returns a string that is written to the output in place of the failing node. Rendering continues with the next node after the handler returns.
This sets an engine-level default; individual render calls can override it with WithErrorHandler.
Analogous to Ruby Liquid's exception_renderer option.
func (*Engine) SetGlobalFilter ¶
SetGlobalFilter sets a function that is applied to the evaluated value of every {{ expression }} before it is written to the output. This is analogous to Ruby Liquid's global_filter option.
The function receives the evaluated Liquid value (string, int, float64, bool, nil, etc.) and returns a transformed value or an error.
Example:
engine.SetGlobalFilter(func(v any) (any, error) {
if s, ok := v.(string); ok {
return strings.ToLower(s), nil
}
return v, nil
})
func (*Engine) SetGlobals ¶
SetGlobals sets variables that are accessible in every rendering context, including isolated sub-contexts created by the {% render %} tag. Scope bindings passed to Render take priority over globals when keys conflict.
func (*Engine) SetGreedy ¶
SetGreedy controls whether whitespace trimming removes all consecutive blank characters including newlines (true, the default), or only trims inline blanks (space/tab) plus at most one newline (false).
func (*Engine) SetTrimOutputLeft ¶
SetTrimOutputLeft controls whether whitespace to the left of every {{ output }} is automatically trimmed, equivalent to adding {{- to every output expression.
func (*Engine) SetTrimOutputRight ¶
SetTrimOutputRight controls whether whitespace to the right of every {{ output }} is automatically trimmed, equivalent to adding -}} to every output expression.
func (*Engine) SetTrimTagLeft ¶
SetTrimTagLeft controls whether whitespace to the left of every {% tag %} is automatically trimmed, equivalent to adding {%- to every tag.
func (*Engine) SetTrimTagRight ¶
SetTrimTagRight controls whether whitespace to the right of every {% tag %} is automatically trimmed, equivalent to adding -%} to every tag.
func (*Engine) StrictNestedVariables ¶ added in v1.1.1
func (e *Engine) StrictNestedVariables()
StrictNestedVariables extends strict variable checking to also error when a multi-segment dotted path (e.g. {{ customer.invalid }}) evaluates to nil. StrictVariables should also be enabled for root-variable checking.
func (*Engine) StrictVariables ¶
func (e *Engine) StrictVariables()
StrictVariables causes the renderer to error when the template contains an undefined variable.
func (*Engine) UnregisterTag ¶
UnregisterTag removes the named tag definition from the engine's configuration. After calling UnregisterTag the tag will no longer be recognized by subsequent parsing or rendering operations. The call is idempotent — unregistering a tag that is not registered is a no-op.
Note: UnregisterTag is intentionally excluded from the frozen-engine guard. It is designed to be called after testing or hot-reload scenarios where the engine may have already been used. Callers are responsible for ensuring no concurrent renders are in progress when calling this method.
func (*Engine) VariableSegments ¶
func (e *Engine) VariableSegments(t *Template) ([]VariableSegment, error)
VariableSegments returns paths of all variables referenced in the template, including those defined locally by assign, capture, for, etc.
type Expression ¶
type Expression struct {
Source string `json:"source"`
Range Range `json:"range"`
Kind ExpressionKind `json:"kind"`
// Depth is the block-nesting depth at which this expression was evaluated.
// 0 = top level, 1 = inside one {% if %} or {% for %}, etc.
Depth int `json:"depth"`
// Error is populated when this expression caused a runtime error.
// The same error also appears in AuditResult.Diagnostics.
Error *Diagnostic `json:"error,omitempty"`
Variable *VariableTrace `json:"variable,omitempty"`
Condition *ConditionTrace `json:"condition,omitempty"`
Iteration *IterationTrace `json:"iteration,omitempty"`
Assignment *AssignmentTrace `json:"assignment,omitempty"`
Capture *CaptureTrace `json:"capture,omitempty"`
}
Expression represents a single Liquid construct visited during rendering. Exactly one of the optional trace fields is populated, selected by Kind.
type ExpressionKind ¶
type ExpressionKind string
ExpressionKind is the discriminator for an Expression.
const ( KindVariable ExpressionKind = "variable" KindCondition ExpressionKind = "condition" KindIteration ExpressionKind = "iteration" KindAssignment ExpressionKind = "assignment" KindCapture ExpressionKind = "capture" )
type FilterStep ¶
type FilterStep = render.FilterStep
FilterStep records a single filter application in a pipeline.
type GroupTrace ¶
type GroupTrace struct {
Operator string `json:"operator"` // "and" | "or"
Result bool `json:"result"`
Items []ConditionItem `json:"items"`
}
GroupTrace represents a logical and/or operator with its operands.
type IterationTrace ¶
type IterationTrace struct {
Variable string `json:"variable"`
Collection string `json:"collection"`
Length int `json:"length"`
Limit *int `json:"limit,omitempty"`
Offset *int `json:"offset,omitempty"`
Reversed bool `json:"reversed,omitempty"`
Truncated bool `json:"truncated,omitempty"`
TracedCount int `json:"traced_count"`
}
IterationTrace is produced by {% for %} or {% tablerow %}.
type ParseResult ¶ added in v1.1.0
type ParseResult struct {
// Template is the compiled template. Non-nil unless a fatal structural
// error made compilation impossible.
Template *Template `json:"template,omitempty"`
// Diagnostics contains every parse-time error and static analysis warning,
// in source order. Never nil (empty slice when no issues).
Diagnostics []Diagnostic `json:"diagnostics"`
}
ParseResult is the result of ParseTemplateAudit.
Template is non-nil when parsing produced a usable compiled template. Template is nil only when a structural (fatal) error prevented compilation (unclosed-tag or unexpected-tag).
Diagnostics is always non-nil; it is empty when there are no issues. It uses the same Diagnostic type as RenderAudit, making the two phases fully uniform.
type RelatedInfo ¶
RelatedInfo is supplementary information for a Diagnostic (e.g. where a matching opening tag is located when reporting an unclosed-tag error).
type RenderOption ¶
RenderOption is a functional option that overrides engine-level configuration for a single Render or FRender call.
Create options with WithStrictVariables, WithLaxFilters, or WithGlobals.
func WithContext ¶
func WithContext(ctx context.Context) RenderOption
WithContext sets the context for this render call. When the context is cancelled or its deadline is exceeded, rendering stops and the context error is returned. Use this for time-based render limits.
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() out, err := tpl.RenderString(vars, WithContext(ctx))
func WithErrorHandler ¶
func WithErrorHandler(fn func(error) string) RenderOption
WithErrorHandler registers a function that is called when a render-time error occurs instead of stopping the render. The handler receives the error and returns a string that is written to the output in place of the failing node. Rendering continues with the next node after the handler returns.
This mirrors Ruby Liquid's exception_renderer option.
To collect errors without stopping render:
var errs []error
out, _ := tpl.RenderString(vars, WithErrorHandler(func(err error) string {
errs = append(errs, err)
return "" // or some placeholder
}))
func WithGlobalFilter ¶
func WithGlobalFilter(fn func(any) (any, error)) RenderOption
WithGlobalFilter registers a function that is applied to the evaluated value of every {{ expression }} for this render call, overriding any engine-level global filter set via Engine.SetGlobalFilter.
This mirrors Ruby Liquid's global_filter: render option.
Example:
out, err := tpl.RenderString(vars, WithGlobalFilter(func(v any) (any, error) {
if s, ok := v.(string); ok {
return strings.ToUpper(s), nil
}
return v, nil
}))
func WithGlobals ¶
func WithGlobals(globals map[string]any) RenderOption
WithGlobals merges the provided map into the globals for this render call. Per-call globals are merged on top of any engine-level globals set via Engine.SetGlobals; both are superseded by the scope bindings passed to Render.
This mirrors the `globals` render option in LiquidJS.
func WithLaxFilters ¶
func WithLaxFilters() RenderOption
WithLaxFilters causes this render call to silently pass the input value through when the template references an undefined filter, regardless of the engine-level setting.
func WithSizeLimit ¶
func WithSizeLimit(n int64) RenderOption
WithSizeLimit limits the total number of bytes written to the output during this render call. Rendering is aborted with an error when the limit is exceeded.
func WithStrictVariables ¶
func WithStrictVariables() RenderOption
WithStrictVariables causes this render call to error when the template references an undefined variable, regardless of the engine-level setting.
type Renderer ¶
A Renderer returns the rendered string for a block. This is the type of a tag definition.
See the examples at Engine.RegisterTag and Engine.RegisterBlock.
type SourceError ¶
SourceError records an error with a source location and optional cause.
SourceError does not depend on, but is compatible with, the causer interface of https://github.com/pkg/errors.
type StaticAnalysis ¶
type StaticAnalysis struct {
// Variables contains all variable references found in the template,
// including locally-defined ones (assign, capture, for loop variables, etc.).
Variables []Variable
// Globals contains only the variable references that are expected from the
// outer scope — not defined within the template.
Globals []Variable
// Locals contains the names of variables defined within the template via
// assign, capture, for, tablerow, etc.
Locals []string
// Tags contains the unique names of tags used in the template,
// e.g. ["assign", "if", "for"].
Tags []string
// Filters is reserved for future use; currently always nil.
Filters []string
}
StaticAnalysis is the rich result of statically analyzing a Liquid template.
type Template ¶
type Template struct {
// contains filtered or unexported fields
}
A Template is a compiled Liquid template. It knows how to evaluate itself within a variable binding environment, to create a rendered byte slice.
Use Engine.ParseTemplate to create a template.
func (*Template) Analyze ¶
func (t *Template) Analyze() (*StaticAnalysis, error)
Analyze performs a full static analysis of the template.
func (*Template) FRender ¶
func (t *Template) FRender(w io.Writer, vars Bindings, opts ...RenderOption) SourceError
FRender executes the template with the specified variable bindings and renders it into w.
RenderOptions can be passed to override engine-level settings for this call only. See Render for details.
func (*Template) FullVariables ¶
FullVariables returns all variable references with full path and source location.
func (*Template) GetRoot ¶
GetRoot returns the root node of the abstract syntax tree (AST) representing the parsed template.
func (*Template) GlobalFullVariables ¶
GlobalFullVariables returns global variable references with full path and source location.
func (*Template) GlobalVariableSegments ¶
func (t *Template) GlobalVariableSegments() ([]VariableSegment, error)
GlobalVariableSegments returns paths of variables expected from the outer scope. It is a convenience method that delegates to Engine.GlobalVariableSegments.
func (*Template) GlobalVariables ¶
GlobalVariables returns the unique root names of global variables.
func (*Template) ParseTree ¶
func (t *Template) ParseTree() *TemplateNode
ParseTree returns the root of the template's parse tree as a *TemplateNode with all Children populated. The returned tree is a snapshot that can be inspected independently of the live template.
The root node is always of kind TemplateNodeBlock with an empty TagName; it represents the top-level sequence of the template.
func (*Template) Render ¶
func (t *Template) Render(vars Bindings, opts ...RenderOption) ([]byte, SourceError)
Render executes the template with the specified variable bindings.
RenderOptions can be passed to override engine-level settings for this call only. For example, adding WithStrictVariables() enables strict variable checking even if StrictVariables was not called on the engine.
func (*Template) RenderAudit ¶
func (t *Template) RenderAudit(vars Bindings, opts AuditOptions, renderOpts ...RenderOption) (*AuditResult, *AuditError)
RenderAudit renders the template with vars and returns a structured trace of the entire execution alongside any errors that occurred.
Unlike Render, RenderAudit does not stop at the first error — it accumulates all errors into the returned *AuditError while the render continues, producing as much output as possible. AuditResult is always non-nil; AuditResult.Output contains the (possibly partial) rendered string.
*AuditError is nil when the render completed without errors. When non-nil, each individual error can be inspected with errors.As:
auditResult, auditErr := tpl.RenderAudit(vars, opts)
if auditErr != nil {
for _, e := range auditErr.Errors() {
var undVar *UndefinedVariableError
var argErr *ArgumentError
var renderErr *RenderError
switch {
case errors.As(e, &undVar):
fmt.Printf("undefined variable %q at line %d\n", undVar.Variable, undVar.LineNumber())
case errors.As(e, &argErr):
fmt.Printf("argument error: %s\n", argErr.Error())
case errors.As(e, &renderErr):
fmt.Printf("render error at line %d: %s\n", renderErr.LineNumber(), renderErr.Message())
}
}
}
The same errors are also available as Diagnostic entries in AuditResult.Diagnostics, with machine-readable codes and LSP-compatible ranges. Diagnostics that may appear during rendering:
- "argument-error" (error): a filter received invalid arguments (e.g. divided_by: 0). The corresponding AuditError entry wraps *ArgumentError.
- "undefined-variable" (warning): a variable was not found in bindings. Only emitted when WithStrictVariables() is active. Wraps *UndefinedVariableError.
- "type-mismatch" (warning): a comparison between incompatible types (e.g. string vs int); Liquid evaluates it as false but it is likely a bug.
- "not-iterable" (warning): a {% for %} loop over a non-iterable value (int, bool, string); Liquid iterates zero times silently.
- "nil-dereference" (warning): a chained property access where an intermediate node in the path is nil (e.g. customer.address.city when address is nil); the expression renders as empty string.
opts controls what the trace collects (variables, conditions, iterations, assignments). renderOpts accepts the same options as Render — WithStrictVariables(), WithLaxFilters(), WithGlobals(), etc. — with identical semantics. RenderAudit never renders differently from Render given the same renderOpts.
func (*Template) RenderString ¶
func (t *Template) RenderString(b Bindings, opts ...RenderOption) (string, SourceError)
RenderString is a convenience wrapper for Render, that has string input and output.
RenderOptions can be passed to override engine-level settings for this call only. See Render for details.
func (*Template) Validate ¶
func (t *Template) Validate() (*AuditResult, error)
Validate performs static analysis on the compiled template AST and returns any diagnostics found. It does not execute the template.
Note: fatal parse errors (unclosed tags, syntax errors) are caught at Engine.ParseTemplate time and will never appear here. Validate reports structural patterns that are valid syntax but likely bugs, such as empty blocks.
func (*Template) VariableSegments ¶
func (t *Template) VariableSegments() ([]VariableSegment, error)
VariableSegments returns paths of all variables referenced in the template. It is a convenience method that delegates to Engine.VariableSegments.
func (*Template) Variables ¶
Variables returns the unique root names of all variables referenced in the template.
func (*Template) Walk ¶
Walk traverses the template parse tree in depth-first, pre-order, calling fn for each node. If fn returns false for a given node, its children are skipped.
Tags are visited in document order. Block clauses (e.g. elsif, else, for-else) appear as direct children of their enclosing block node.
type TemplateNode ¶
type TemplateNode struct {
// Kind identifies the type of this node.
Kind TemplateNodeKind
// TagName is non-empty for Tag and Block nodes; it holds the tag name (e.g. "if", "for").
// It is empty for Text and Output nodes.
TagName string
// Location is the source location of this node in the template source.
Location parser.SourceLoc
// Children contains child nodes for Block nodes (body nodes followed by clause nodes).
// For Text and Output nodes, Children is nil.
Children []*TemplateNode
}
TemplateNode is a public representation of a single node in the template parse tree. It provides a lightweight, stable view over the internal AST for tree inspection and custom traversal.
Clause nodes (e.g. elsif, else, when) appear as children of their containing block.
type TemplateNodeKind ¶
type TemplateNodeKind int
TemplateNodeKind identifies the kind of a node in the template parse tree.
const ( // TemplateNodeText represents a literal text chunk rendered verbatim. TemplateNodeText TemplateNodeKind = iota // TemplateNodeOutput represents an {{ expr }} output expression. TemplateNodeOutput // TemplateNodeTag represents a simple {% tag %} with no body. TemplateNodeTag // TemplateNodeBlock represents a block tag with a body, e.g. {% if %}...{% endif %}. TemplateNodeBlock )
type Variable ¶
type Variable struct {
// Segments is the full path to the variable, e.g. ["customer", "first_name"].
Segments []string
// Location is the source location where this variable reference appears.
Location parser.SourceLoc
// Global is true when the variable is not defined within the template itself
// (i.e., it is expected to be provided by the caller).
Global bool
}
Variable represents a reference to a Liquid variable, including its source location and whether it comes from the outer (global) scope.
type VariableSegment ¶
type VariableSegment = []string
VariableSegment is a path to a variable, represented as a slice of string segments. For example, the expression {{ customer.first_name }} produces ["customer", "first_name"].
type VariableTrace ¶
type VariableTrace struct {
Name string `json:"name"`
Parts []string `json:"parts"`
Value any `json:"value"`
Pipeline []FilterStep `json:"pipeline"`
}
VariableTrace is produced by {{ expr }}.
type WalkFunc ¶
type WalkFunc func(node *TemplateNode) bool
WalkFunc is a callback invoked for each TemplateNode during a tree walk. Returning false prevents descending into that node's children; returning true continues the traversal into children.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
liquid
command
Package main defines a command-line interface to the Liquid engine.
|
Package main defines a command-line interface to the Liquid engine. |
|
Package evaluator is an interim internal package that forwards to package values.
|
Package evaluator is an interim internal package that forwards to package values. |
|
Package expressions is an internal package that parses and evaluates the expression language.
|
Package expressions is an internal package that parses and evaluates the expression language. |
|
Package filters is an internal package that defines the standard Liquid filters.
|
Package filters is an internal package that defines the standard Liquid filters. |
|
Package parser is an internal package that parses template source into an abstract syntax tree.
|
Package parser is an internal package that parses template source into an abstract syntax tree. |
|
Package render is an internal package that renders a compiled template parse tree.
|
Package render is an internal package that renders a compiled template parse tree. |
|
Package tags is an internal package that defines the standard Liquid tags.
|
Package tags is an internal package that defines the standard Liquid tags. |
|
Package values is an internal package that defines methods such as sorting, comparison, and type conversion, that apply to interface types.
|
Package values is an internal package that defines methods such as sorting, comparison, and type conversion, that apply to interface types. |