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.
A full-featured regex engine in pure Go based on the .NET engine

regexp2 - full featured regular expressions for Go Regexp2 is a feature-rich RegExp engine for Go. It doesn't have constant time guarantees like the b

Jan 9, 2023
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

Dec 31, 2022
export stripTags from html/template as strip.StripTags

HTML StripTags for Go This is a Go package containing an extracted version of the unexported stripTags function in html/template/html.go. ⚠️ This pack

Dec 4, 2022
Experimental parser Angular template

Experimental parser Angular template This repository only shows what a parser on the Go might look like Benchmark 100k line of template Parser ms @ang

Dec 15, 2021
Enhanced Markdown template processor for golang

emd Enhanced Markdown template processor. See emd README file TOC Install glide

Jan 2, 2022
🌭 The hotdog web browser and browser engine 🌭
🌭 The hotdog web browser and browser engine 🌭

This is the hotdog web browser project. It's a web browser with its own layout and rendering engine, parsers, and UI toolkit! It's made from scratch e

Dec 30, 2022
In-memory, full-text search engine built in Go. For no particular reason.
In-memory, full-text search engine built in Go. For no particular reason.

Motivation I just wanted to learn how to write a search engine from scratch without any prior experience. Features Index content Search content Index

Sep 1, 2022
In-memory, full-text search engine built in Go. For no particular reason.
In-memory, full-text search engine built in Go. For no particular reason.

Motivation I just wanted to learn how to write a search engine from scratch without any prior experience. Features Index content Search content Index

Sep 1, 2022
A complete Liquid template engine in Go
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 sta

Dec 15, 2022
Eth2-MEV project with liquid staking (Flashbots-Lido-Nethermind)
Eth2-MEV project with liquid staking (Flashbots-Lido-Nethermind)

Ray Tracing Eth2-MEV project with liquid staking (Flashbots-Lido-Nethermind). Notes | Slides What you need to setup: Eth2 validator with Rayonism enab

Jan 2, 2023
A simple LCD controller package for raspberry pi liquid crystal IΒ²C displays.

A simple LCD controller package for raspberry pi liquid crystal IΒ²C displays.

Nov 1, 2022
Fast, powerful, yet easy to use template engine for Go. Optimized for speed, zero memory allocations in hot paths. Up to 20x faster than html/template

quicktemplate A fast, powerful, yet easy to use template engine for Go. Inspired by the Mako templates philosophy. Features Extremely fast. Templates

Dec 26, 2022
Template repository for a Go monorepo, complete with CI and automatic docker builds

Utility Warehouse template Go monorepo This repo is an abbreviated copy of one used by one of the teams inside Utility Warehouse. It's been built for

Dec 19, 2022
Expression evaluation engine for Go: fast, non-Turing complete, dynamic typing, static typing
Expression evaluation engine for Go: fast, non-Turing complete, dynamic typing, static typing

Expr Expr package provides an engine that can compile and evaluate expressions. An expression is a one-liner that returns a value (mostly, but not lim

Jan 1, 2023
Expression evaluation engine for Go: fast, non-Turing complete, dynamic typing, static typing
Expression evaluation engine for Go: fast, non-Turing complete, dynamic typing, static typing

Expr Expr package provides an engine that can compile and evaluate expressions. An expression is a one-liner that returns a value (mostly, but not lim

Dec 30, 2022
Expression evaluation engine for Go: fast, non-Turing complete, dynamic typing, static typing
Expression evaluation engine for Go: fast, non-Turing complete, dynamic typing, static typing

Expr Expr package provides an engine that can compile and evaluate expressions. An expression is a one-liner that returns a value (mostly, but not lim

Dec 30, 2022
:zap: boilerplate template manager that generates files or directories from template repositories
:zap: boilerplate template manager that generates files or directories from template repositories

Boilr Are you doing the same steps over and over again every time you start a new programming project? Boilr is here to help you create projects from

Jan 6, 2023
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