A complete Liquid template engine in Go

Liquid Template Parser

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 gopkg.in/osteele/liquid.v1 # latest snapshot

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

Usage

engine := liquid.NewEngine()
template := `<h1>{{ page.title }}</h1>`
bindings := map[string]interface{}{
    "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.

Command-Line tool

go install gopkg.in/osteele/liquid.v0/cmd/liquid 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!

Documentation

Status

These features of Shopify Liquid aren't implemented:

  • Warn and lax error modes.
  • Non-strict filters. An undefined filter is currently an error.
  • Strict variables. An undefined variable is not an error.

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() interface{}, that returns a proxy object. Conventionally, the proxy is a map or struct that defines the exposed properties. See http://godoc.org/github.com/osteele/liquid#Drop for additional information.

Value Types

Render and friends take a Bindings parameter. This is a map of string to interface{}, 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.

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

💻 ⚠️

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

Owner
Oliver Steele
Prof @ NYU Shanghai. Formerly @ Olin College, Nest, Apple, AOL, lotsa startups. Older projects: Apple Dylan, Quickdraw GX, Laszlo Presentation Server, PyWordNet
Oliver Steele
Comments
  • Properly handle implicit conversion to integer types.

    Properly handle implicit conversion to integer types.

    From the commit message:

    Go's reflect API is very picky about arguments to Call method. You can't just pass in the "int" when function expects "int64", even on 64 bit machines. This patch solves this problem by performing conversions properly. Truncation problems however, as well as negative to uint issues are completely ignored. Which is perhaps not ideal, but still better than returning "int" when "int32" or "int64" is requested.

    I did split the Kind switch cases and moved value -> int and value -> float conversions to separate functions. An alternative to that would be using the reflection API, which might be less performant. Not that it matters much, but this solution is correct, even though looks a bit copy & pasty.

    Oh and of course all "uint" types were completely missing in the function and they are now added.

    While this commit fixes existing behaviour, i.e. implicit conversions from strings to ints. What worries me is that errors from strconv.ParseInt(value, 10, 64) and strconv.ParseFloat(value, 64) are returned directly here. I didn't change that behaviour in this patch, but perhaps it would be wise to do so. What do you think, @osteele? Perhaps it would be better to convert those errors into TypeError, so that they are properly caught in func (e expression) Evaluate(ctx Context), instead of causing unnecessary panics?

    Anyways, I think I will submit a follow up pull request in a sec if you think it might be a good idea.

  • Add setting to customise delimiters

    Add setting to customise delimiters

    I would like to be able to customise the delimiters I use in templating (this is intended as an analog of https://golang.org/pkg/text/template/#Template.Delims).

    This PR has the following problems

    1. Delimiters is a byte array to limit them to single ASCII characters
    2. The tag delimiters are formed using the objectLeft and objectRight argument, I would like to specify all 4 combinations completely, e.g., "<<", ">>", "<%", "%>".
    3. I think moving compilation of the tokenMatcher regex into the function call will dramatically slow things down.
    4. There are no tests. I did it like this so I didn't have to change the existing code much.

    I am happy to continue work on this, but I'm lodging a PR to see if you're willing to accept this sort of functionality, if I'm going about implementing in the right way, and to check if I haven't missed some other locations where the delimiters are hardcoded.

  • Cannot compile on armv7

    Cannot compile on armv7

    Checklist

    • [x] I have searched the issue list
    • [ ] I have tested my example against Shopify Liquid. (This isn't necessary if the actual behavior is a panic, or an error for which IsTemplateError returns false.)

    Expected Behavior

    I can compile for armv7

    Actual Behavior

    Throws error: yaccpar:452: constant 4294967295 overflows int

    Detailed Description

    It appears somewhere there's a constant which is too large for anything but a 64bit environment.

    Possible Solution

    Would be good to add some build tags to have a different value for other environments.

  • Example: List variables used in template

    Example: List variables used in template

    Add a godoc example that demonstrates how to list variable names used.

    I realized when I wrote this example that it works great for us since we don't use expressions in templates like {{ myvar | capitalize }}. It doesn't work for expressions but it could if I exported a few more values so that I can get the bindings used in the expression context.

    I can submit a PR to add a few more getter functions to make this example work for more templates if that's something you would consider merging?

    Below is a screenshot of the example rendered in godoc Screen Shot 2022-02-13 at 9 08 59 PM

    Checklist

    • [x] I have read the contribution guidelines.
    • [x] make test passes.
    • [x] make lint passes.
    • [x] New and changed code is covered by tests.
    • [ ] Performance improvements include benchmarks. (NA)
    • [ ] Changes match the documented (not just the implemented) behavior of Shopify. (NA)
  • 🐛 Fix MaxUint32 assignment to platform int

    🐛 Fix MaxUint32 assignment to platform int

    This fixes an issue where math.MaxUint32 is assigned to a platform dependent int type. This works on 64-bit platforms without issue due to there being plenty of space. On 32-bit platforms this is wrong and will not compile as math.MaxUint32 > math.MaxInt32.

    Checklist

    • [x] I have read the contribution guidelines.
    • [x] make test passes.
    • [x] make lint passes.
    • [x] New and changed code is covered by tests.
    • [x] Performance improvements include benchmarks.
    • [x] Changes match the documented (not just the implemented) behavior of Shopify.
  • floor and ceil should return integers

    floor and ceil should return integers

    See

    • https://shopify.github.io/liquid/filters/floor/
    • https://shopify.github.io/liquid/filters/ceil/

    Checklist

    • [x] I have read the contribution guidelines.
    • [x] make test passes.
    • [x] make lint passes.
    • [x] New and changed code is covered by tests.
    • [x] Performance improvements include benchmarks.
    • [x] Changes match the documented (not just the implemented) behavior of Shopify.
  • Return errors applying filters as render errors

    Return errors applying filters as render errors

    When a Filter returns an error as its second argument it generates a panic with the error.

    I have changed this behaviour so that the error is returned from the RenderAndPass function.

    For example, if there is a filter called toTitle that returns "test error" as its error, the error returned will be 'Liquid error: error applying filter "toTitle" ("test error") in {{ "test message" | toTitle }}'.

    Apologies if panicing in this case is the expected behaviour and this was done intentionally, I can always just catch the panic when I call the Render step, I just think this way is more elegant.

  • Extend the Bindings parameter to work with structs

    Extend the Bindings parameter to work with structs

    Currently

    	params := map[string]interface{}{
    		"message": Message{
    			Text: "hello",
    		},
    	}
    	engine := liquid.NewEngine()
    	template := "{{ message.Text }}"
    	str, err := engine.ParseAndRenderString(template, params)
    	log.Printf("Err: %v\n%v", err, str)
    

    doesn't work as expected, but

    	params := map[string]interface{}{
    		"message": map[string]interface{}{
    			"Text": "hello",
    		},
    	}
    	engine := liquid.NewEngine()
    	template := "{{ message.Text }}"
    	str, err := engine.ParseAndRenderString(template, params)
    	log.Printf("Err: %v\n%v", err, str)
    

    does. The text/template and html/template libraries in the go standard library allow you to traverse through any objects (maps, structs, slices and pointers to these) and use them in the templating engine, as in my first example.

    A decent workaround for this is the github.com/fatih/structs Map() function, which converts your struct structure into a map based structure, but this has two problems in my use case. 1. My data struct is very large and this is a lot of processing, and 2. it preserves elements of my data struct that are *map[string]interface{} (which need to be like this to work with the rest of my program), which as far as I can tell

    	params := map[string]interface{}{
    		"message": &map[string]interface{}{
    			"Text": "hello",
    		},
    	}
    	engine := liquid.NewEngine()
    	template := "{{ message.Text }}"
    	str, err := engine.ParseAndRenderString(template, params)
    	log.Printf("Err: %v\n%v", err, str)
    

    also doesn't work correctly.

    Are you planning on extending to copy this behaviour (from text/template or even https://github.com/acstech/liquid) in your current library, or do you consider it out of scope?

  • Expose the template ast

    Expose the template ast

    Sometimes a consumer of a template needs to know what objects were used. In my case, a template can reference secret values from a secret store vault and instead of passing all possible secrets to the template only to render two of them, we use the ast to determine which are used and only retrieve those values from the vault before rendering the template.

    Exposing the ast allows us to use the liquid APIs just like normal, without having to jump through hoops to build the ast ourselves using the other types exported in this library.

    Checklist

    • [x] I have read the contribution guidelines.
    • [x] make test passes.
    • [x] make lint passes.
    • [x] New and changed code is covered by tests.
    • [ ] Performance improvements include benchmarks. N/A
    • [ ] Changes match the documented (not just the implemented) behavior of Shopify. N/A
  • Properly handle variadic functions.

    Properly handle variadic functions.

    This commit addresses two issues:

    1. For variadic functions 'convertCallArguments' was allocating exactly 'len(args)' arguments, which might be less than required, as a result zero/default filling loop would panic with out of bounds error.

    2. Even if we correctly allocate a proper amount of arguments, zero/default filling loop doesn't handle special variadic function type case, when last argument has a slice type and reflect API expects plain values as arguments. But actually we don't need that, because it's okay to omit variadic values altogether, hence the total amount of allocated arguments is max(len(args), rt.NumIn()-1).

    Checklist

    • [x] I have read the contribution guidelines.
    • [x] make test passes.
    • [x] make lint passes.
    • [x] New and changed code is covered by tests.
    • [x] Performance improvements include benchmarks.
    • [x] Changes match the documented (not just the implemented) behavior of Shopify.
  • Port to Windows

    Port to Windows

  • Add support for strict variables

    Add support for strict variables

    The Ruby implementation has support for erroring when a template has an undefined variable. This is implemented by passing an option to the render() method.

    As this version doesn't expose render.Config in the engine, a StrictVariables() method is provided on the engine to enable it. This differs from the Ruby version in that it's on for the entire engine, rather than enabled for each call to Render(). This is more similar to the Ruby version's render!() method as it immediately errors, rather than storing a stack of errors which can be accessed later.

    Refs. #8

    Checklist

    • [x] I have read the contribution guidelines.
    • [x] make test passes.
    • [x] make lint passes.
    • [x] New and changed code is covered by tests.
    • [x] Changes match the documented (not just the implemented) behavior of Shopify.
  • Fix slice

    Fix slice

    slice was broken with an offset or length above 1000. Shopify documents that slice supports arrays in addition to strings; I added support for working on slices.

    Checklist

    • [x] I have read the contribution guidelines.
    • [x] make test passes.
    • [x] make lint passes. (at least on modified files)
    • [x] New and changed code is covered by tests.
    • [ ] Performance improvements include benchmarks.
    • [x] Changes match the documented (not just the implemented) behavior of Shopify.
  • Fix map filter with slice of structs

    Fix map filter with slice of structs

    The previous map implementation worked only for slices of maps. By using the library's values package this implementation should work with all compatible types.

    Checklist

    • [x] I have read the contribution guidelines.
    • [x] make test passes.
    • [x] make lint passes. (at least on modified files)
    • [x] New and changed code is covered by tests.
    • [ ] Performance improvements include benchmarks.
    • [x] Changes match the documented (not just the implemented) behavior of Shopify.
  • Implemented auto-escape functionality and provided a default escape replacer for HTML.

    Implemented auto-escape functionality and provided a default escape replacer for HTML.

    This pull request adds auto-escape functionality which is similar to implicitly applying a filter to the output of an expression block, but done in a slightly efficient way (using a mechanism similar to strings.Replacer which allows for streaming processing without excessive memory allocations).

    I know this functionality is not part of liquid (there is an open issue which doesn't have any responses at the time of writing), but I think this is an extremely useful feature and it does not break any existing functionality.

    Checklist

    • [x] I have read the contribution guidelines.
    • [x] make test passes.
    • [x] make lint passes.
    • [x] New and changed code is covered by tests.
    • [x] Performance improvements include benchmarks.
    • [x] Changes match the documented (not just the implemented) behavior of Shopify.
  • Fix example filter URL in documentation

    Fix example filter URL in documentation

    Checklist

    • [x] I have read the contribution guidelines.
    • [x] make test passes.
    • [x] make lint passes.
    • [x] New and changed code is covered by tests.
    • [x] Performance improvements include benchmarks.
    • [x] Changes match the documented (not just the implemented) behavior of Shopify.
  • Allow unless to have an else clause

    Allow unless to have an else clause

    This PR adds the else clause to unless

    Checklist

    • [x] I have read the contribution guidelines.
    • [x] make test passes.
    • [x] make lint passes.
    • [x] New and changed code is covered by tests.
    • [ ] Performance improvements include benchmarks.
    • [x] Changes match the documented (not just the implemented) behavior of Shopify.
Wrapper package for Go's template/html to allow for easy file-based template inheritance.

Extemplate Extemplate is a small wrapper package around html/template to allow for easy file-based template inheritance. File: templates/parent.tmpl <

Dec 6, 2022
Goview is a lightweight, minimalist and idiomatic template library based on golang html/template for building Go web application.

goview Goview is a lightweight, minimalist and idiomatic template library based on golang html/template for building Go web application. Contents Inst

Dec 25, 2022
Simple system for writing HTML/XML as Go code. Better-performing replacement for html/template and text/template

Simple system for writing HTML as Go code. Use normal Go conditionals, loops and functions. Benefit from typing and code analysis. Better performance than templating. Tiny and dependency-free.

Dec 5, 2022
A template to build dynamic web apps quickly using Go, html/template and javascript
A template to build dynamic web apps quickly using Go, html/template and javascript

gomodest-template A modest template to build dynamic web apps in Go, HTML and sprinkles and spots of javascript. Why ? Build dynamic websites using th

Dec 29, 2022
Made from template temporalio/money-transfer-project-template-go
Made from template temporalio/money-transfer-project-template-go

Temporal Go Project Template This is a simple project for demonstrating Temporal with the Go SDK. The full 20 minute guide is here: https://docs.tempo

Jan 6, 2022
Go-project-template - Template for a golang project

This is a template repository for golang project Usage Go to github: https://git

Oct 25, 2022
Go-api-template - A rough template to give you a starting point for your API

Golang API Template This is only a rough template to give you a starting point f

Jan 14, 2022
Api-go-template - A simple Go API template that uses a controller-service based model to build its routes

api-go-template This is a simple Go API template that uses a controller-service

Feb 18, 2022
HTML template engine for Go

Ace - HTML template engine for Go Overview Ace is an HTML template engine for Go. This is inspired by Slim and Jade. This is a refinement of Gold. Exa

Jan 4, 2023
Simple and fast template engine for Go

fasttemplate Simple and fast template engine for Go. Fasttemplate performs only a single task - it substitutes template placeholders with user-defined

Dec 30, 2022
A handy, fast and powerful go template engine.
A handy, fast and powerful go template engine.

Hero Hero is a handy, fast and powerful go template engine, which pre-compiles the html templates to go code. It has been used in production environme

Dec 27, 2022
Jet template engine

Jet Template Engine for Go Jet is a template engine developed to be easy to use, powerful, dynamic, yet secure and very fast. simple and familiar synt

Jan 4, 2023
gtpl is a template engine for glang

gtpl 使用必读 gtpl is a HTML template engine for golang gtpl 是一个 go 语言模板引擎,它能以极快的速度进行模板语法分析。相比 go 语言官方库 html/template,gtpl 的语法有着简练、灵活、易用的特点。

Nov 28, 2022
The world’s most powerful template engine and Go embeddable interpreter.
The world’s most powerful template engine and Go embeddable interpreter.

The world’s most powerful template engine and Go embeddable interpreter

Dec 23, 2022
This my project template for making fiber with SSR taste by empowered mustache engine.

SSR-FIBER-TEMPLATE This my project template for making fiber with SSR taste by empowered mustache engine. Folder Hierarchy Name Description configs Co

May 9, 2022
Package damsel provides html outlining via css-selectors and common template functionality.

Damsel Markup language featuring html outlining via css-selectors, extensible via pkg html/template and others. Library This package expects to exist

Oct 23, 2022
The mustache template language in Go

Overview mustache.go is an implementation of the mustache template language in Go. It is better suited for website templates than Go's native pkg/temp

Dec 22, 2022
Useful template functions for Go templates.

Sprig: Template functions for Go templates The Go language comes with a built-in template language, but not very many template functions. Sprig is a l

Jan 4, 2023
The powerful template system that Go needs

Plush Plush is the templating system that Go both needs and deserves. Powerful, flexible, and extendable, Plush is there to make writing your template

Dec 29, 2022