liquid

package module
v1.1.1 Latest Latest
Warning

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

Go to latest
Published: Apr 10, 2026 License: MIT Imports: 15 Imported by: 0

README

Liquid Template Parser

go badge Golangci-lint badge Go Report Card badge Go Doc MIT License

liquid is a pure Go implementation of Shopify Liquid templates. It was developed for use in the Gojekyll port of the Jekyll static site generator.

Installation

go get github.com/joaqu1m/liquid # latest version

go get -u github.com/joaqu1m/liquid # development version

Usage

engine := liquid.NewEngine()
template := `<h1>{{ page.title }}</h1>`
bindings := map[string]any{
    "page": map[string]string{
        "title": "Introduction",
    },
}
out, err := engine.ParseAndRenderString(template, bindings)
if err != nil { log.Fatalln(err) }
fmt.Println(out)
// Output: <h1>Introduction</h1>

See the API documentation for additional examples.

Jekyll Compatibility

This library was originally developed for Gojekyll, a Go port of Jekyll. As such, it includes optional Jekyll-specific extensions that are not part of the Shopify Liquid specification.

To enable Jekyll compatibility mode:

engine := liquid.NewEngine()
engine.EnableJekyllExtensions()

Jekyll extensions include:

  • Dot notation in assign tags: {% assign page.canonical_url = "/about/" %}
    • In standard Liquid, this would be a syntax error
    • With Jekyll extensions enabled, this creates or updates nested object properties
    • Intermediate objects are created automatically if they don't exist

Example:

engine := liquid.NewEngine()
engine.EnableJekyllExtensions()  // Enable Jekyll-specific features

template := `{% assign page.meta.author = "John Doe" %}{{ page.meta.author }}`
bindings := map[string]any{
    "page": map[string]any{
        "title": "Home",
    },
}
out, _ := engine.ParseAndRenderString(template, bindings)
// Output: John Doe

Note: Jekyll extensions are disabled by default to maintain compatibility with standard Shopify Liquid.

Command-Line tool

go install github.com/joaqu1m/liquid/cmd/liquid@latest installs a command-line liquid executable. This is intended to make it easier to create test cases for bug reports.

$ liquid --help
usage: liquid [FILE]
$ echo '{{ "Hello World" | downcase | split: " " | first | append: "!"}}' | liquid
hello!

Security

Important: If you plan to process untrusted templates (templates authored by users you don't fully trust), please review the Security Policy documentation.

Key security considerations:

  • Sandboxed Execution: Templates cannot execute arbitrary code or access filesystem/network resources (by default)
  • DoS Vulnerabilities: The engine is vulnerable to denial-of-service attacks via infinite loops and memory exhaustion when processing untrusted templates
  • Resource Limiting via FRender: Use the FRender method with custom writers to implement timeouts and output size limits for untrusted templates
  • Third-Party Extensions: Custom filters and tags execute arbitrary Go code and should be carefully audited

For detailed information about security guarantees, limitations, and production deployment recommendations, see SECURITY.md. For implementing resource limits, see the FRender documentation.

Documentation

This section provides a comprehensive guide to using and extending the Liquid template engine. Documentation is organized by topic:

Getting Started
Core Concepts
  • Value Types - How Go values map to Liquid types
  • Drops - Custom types in templates
  • Status - Feature compatibility with Shopify Liquid
Advanced Usage
Security & Performance
Internals
Contributing

Status

These features of Shopify Liquid aren't implemented:

  • Filter keyword parameters, for example {{ image | img_url: '580x', scale: 2 }}. [Issue #42]
  • Warn and lax error modes.
    • Note: Engine.LaxFilters() enables Shopify-compatible behavior for undefined filters (silently pass through).
Drops

Drops have a different design from the Shopify (Ruby) implementation. A Ruby drop sets liquid_attributes to a list of attributes that are exposed to Liquid. A Go drop implements ToLiquid() any, that returns a proxy object. Conventionally, the proxy is a map or struct that defines the exposed properties. See http://godoc.org/github.com/joaqu1m/liquid#Drop for additional information.

Value Types

Render and friends take a Bindings parameter. This is a map of string to any, that associates template variable names with Go values.

Any Go value can be used as a variable value. These values have special meaning:

  • false and nil
    • These, and no other values, are recognized as false by and, or, {% if %}, {% elsif %}, and {% case %}.
  • Integers
    • (Only) integers can be used as array indices: array[1]; array[n], where array has an array value and n has an integer value.
    • (Only) integers can be used as the endpoints of a range: {% for item in (1..5) %}, {% for item in (start..end) %} where start and end have integer values.
  • Integers and floats
    • Integers and floats are converted to their join type for comparison: 1 == 1.0 evaluates to true. Similarly, int8(1), int16(1), uint8(1) etc. are all ==.
    • [There is currently no special treatment of complex numbers.]
  • Integers, floats, and strings
    • Integers, floats, and strings can be used in comparisons <, >, <=, >=. Integers and floats can be usefully compared with each other. Strings can be usefully compared with each other, but not with other values. Any other comparison, e.g. 1 < "one", 1 > "one", is always false.
  • Arrays (and slices)
    • An array can be indexed by integer value: array[1]; array[n] where n has an integer value.
    • Arrays have first, last, and size properties: array.first == array[0], array[array.size-1] == array.last (where array.size > 0)
  • Maps
    • A map can be indexed by a string: hash["key"]; hash[s] where s has a string value
    • A map can be accessed using property syntax hash.key
    • Maps have a special size property, that returns the size of the map.
  • Drops
    • A value value of a type that implements the Drop interface acts as the value value.ToLiquid(). There is no guarantee about how many times ToLiquid will be called. [This is in contrast to Shopify Liquid, which both uses a different interface for drops, and makes stronger guarantees.]
  • Structs
    • A public field of a struct can be accessed by its name: value.FieldName, value["fieldName"].
      • A field tagged e.g. liquid:”name” is accessed as value.name instead.
      • If the value of the field is a function that takes no arguments and returns either one or two arguments, accessing it invokes the function, and the value of the property is its first return value.
      • If the second return value is non-nil, accessing the field panics instead.
    • A function defined on a struct can be accessed by function name e.g. value.Func, value["Func"].
      • The same rules apply as to accessing a func-valued public field.
    • Note that despite being array- and map-like, structs do not have a special value.size property.
  • []byte
    • A value of type []byte is rendered as the corresponding string, and presented as a string to filters that expect one. A []byte is not (currently) equivalent to a string for all uses; for example, a < b, a contains b, hash[b] will not behave as expected where a or b is a []byte.
  • MapSlice
    • An instance of yaml.MapSlice acts as a map. It implements m.key, m[key], and m.size.
Template Store

The template store allows for usage of varying template storage implementations (embedded file system, database, service, etc). In order to use:

  1. Create a struct that implements TemplateStore
    type TemplateStore interface {
          ReadTemplate(templatename string) ([]byte, error)
    }
    
  2. Register with the engine
    engine.RegisterTemplateStore(myTemplateStore)
    

FileTemplateStore is the default mechanism for backwards compatibility.

Refer to example for an example implementation.

Advanced Rendering
Custom Writers (FRender)

For advanced use cases like streaming to files, implementing timeouts, or limiting output size, use the FRender method to render directly to any io.Writer:

var buf bytes.Buffer
err := template.FRender(&buf, bindings)

This is particularly useful for:

  • Rendering large templates without buffering in memory
  • Implementing cancellation via context
  • Limiting output size from untrusted templates
  • Custom output transformation

See the FRender documentation for detailed examples and security best practices.

References

Contributing

Bug reports, test cases, and code contributions are more than welcome. Please refer to the contribution guidelines.

Contributors

Thanks goes to these wonderful people (emoji key):


Oliver Steele

💻 📖 🤔 🚇 👀 ⚠️

James Littlejohn

💻 📖 ⚠️

nsf

💻 ⚠️

Tobias Salzmann

💻

Ben Doerr

💻

Daniil Gentili

💻

Carolyn Van Slyck

💻

Kimmo Lehto

💻

Victor "Vito" Gama

💻

Utpal Sarkar

💻 ⚠️

Misko Lee

💻

Andre Lehmann

💻

James O'Gorman

💻 🐛

Olivier Favre

💻

Peter Aba

📖

Christopher Hill

💻 🐛

Steve Atkins

💻 🐛

Preston Price

💻

jamslinger

💻 🐛

Andreas Deininger

💻

Matteo Agius-D'Arrigo

💻

Cody Krieger

💻

Stéphane JAIS

💻

James Newman

💻 🐛

chris

💻

Dmitry Panov

💻

Gauthier Hacout

🐛

Jaime Amate

💻

This project follows the all-contributors specification. Contributions of any kind welcome!

Attribution
Package Author Description License
Ragel Adrian Thurston scanning expressions MIT
gopkg.in/yaml.v2 Canonical MapSlice Apache License 2.0

Michael Hamrah's Lexing with Ragel and Parsing with Yacc using Go was essential to understanding go yacc.

The original Liquid engine, of course, for the design and documentation of the Liquid template language. Many of the tag and filter test cases are taken directly from the Liquid documentation.

Other Implementations

Go
Other Languages

See Shopify's ports of Liquid to other environments.

License

MIT License

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

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func FromDrop

func FromDrop(object any) any

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

type Bindings map[string]any

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

type CaptureTrace struct {
	Variable string `json:"variable"`
	Value    string `json:"value"`
}

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

type DropMethodMissing interface {
	MissingMethod(key string) any
}

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 NewEngine

func NewEngine() *Engine

NewEngine returns a new Engine.

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

func (e *Engine) Delims(objectLeft, objectRight, tagLeft, tagRight string) *Engine

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

func (e *Engine) FullVariables(t *Template) ([]Variable, error)

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

func (e *Engine) GetGlobals() map[string]any

GetGlobals returns the engine-level global variables.

func (*Engine) GlobalFullVariables

func (e *Engine) GlobalFullVariables(t *Template) ([]Variable, error)

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

func (e *Engine) GlobalVariables(t *Template) ([]string, error)

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

func (e *Engine) RegisterBlock(name string, td Renderer)

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

func (e *Engine) RegisterFilter(name string, fn any)

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

func (e *Engine) RegisterTag(name string, td Renderer)

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

func (e *Engine) SetAutoEscapeReplacer(replacer render.Replacer)

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

func (e *Engine) SetExceptionHandler(fn func(error) string)

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

func (e *Engine) SetGlobalFilter(fn func(any) (any, error))

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

func (e *Engine) SetGlobals(globals map[string]any)

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

func (e *Engine) SetGreedy(v bool)

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

func (e *Engine) SetTrimOutputLeft(v bool)

SetTrimOutputLeft controls whether whitespace to the left of every {{ output }} is automatically trimmed, equivalent to adding {{- to every output expression.

func (*Engine) SetTrimOutputRight

func (e *Engine) SetTrimOutputRight(v bool)

SetTrimOutputRight controls whether whitespace to the right of every {{ output }} is automatically trimmed, equivalent to adding -}} to every output expression.

func (*Engine) SetTrimTagLeft

func (e *Engine) SetTrimTagLeft(v bool)

SetTrimTagLeft controls whether whitespace to the left of every {% tag %} is automatically trimmed, equivalent to adding {%- to every tag.

func (*Engine) SetTrimTagRight

func (e *Engine) SetTrimTagRight(v bool)

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

func (e *Engine) UnregisterTag(name string)

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.

func (*Engine) Variables

func (e *Engine) Variables(t *Template) ([]string, error)

Variables returns the unique root names of all variables referenced in the template, including locally-defined ones. For example, {{ x.a }} and {{ x.b }} both contribute "x".

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 Position

type Position struct {
	Line   int `json:"line"`
	Column int `json:"column"`
}

Position represents a point in the source (1-based, LSP-compatible).

type Range

type Range struct {
	Start Position `json:"start"`
	End   Position `json:"end"`
}

Range is a span in the source from Start to End (End exclusive).

type RelatedInfo

type RelatedInfo struct {
	Range   Range  `json:"range"`
	Message string `json:"message"`
}

RelatedInfo is supplementary information for a Diagnostic (e.g. where a matching opening tag is located when reporting an unclosed-tag error).

type RenderOption

type RenderOption func(*render.Config)

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

type Renderer func(render.Context) (string, error)

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

type SourceError interface {
	error
	Cause() error
	Path() string
	LineNumber() int
}

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

func (t *Template) FullVariables() ([]Variable, error)

FullVariables returns all variable references with full path and source location.

func (*Template) GetRoot

func (t *Template) GetRoot() render.Node

GetRoot returns the root node of the abstract syntax tree (AST) representing the parsed template.

func (*Template) GlobalFullVariables

func (t *Template) GlobalFullVariables() ([]Variable, error)

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

func (t *Template) GlobalVariables() ([]string, error)

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

func (t *Template) Variables() ([]string, error)

Variables returns the unique root names of all variables referenced in the template.

func (*Template) Walk

func (t *Template) Walk(fn WalkFunc)

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.

func (Variable) String

func (v Variable) String() string

String returns the dot-joined path, e.g. "customer.first_name".

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.

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.

Jump to

Keyboard shortcuts

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