liquid

package module
v0.0.0-...-5bcb5c9 Latest Latest
Warning

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

Go to latest
Published: Mar 16, 2017 License: MIT Imports: 9 Imported by: 0

README

Liquid Templates For Go Build Status Coverage Status GoDoc

template, err := liquid.ParseString("hello {{ name | upcase }}", nil)
if err != nil { panic(err) }
data := map[string]interface{}{
  "name": "leto",
}
writer := new(bytes.Buffer)
template.Render(writer, data)
return writer.String()

Given a file path, liquid can also ParseFile. Give a []byte it can also simply Parse.

What's Missing

The following filters are missing:

  • map

The following tags are missing:

  • cycle

As per the original Liquid template, there's no automatic protection against XSS. This might change if I decide to turn this into something more than just a clone

Template Cache

By default the templates are cached in a pretty dumb cache. That is, once in the cache, items stay in the cache (there's no expiry). The cache can be disabled, on a per-template basis, via:

template, _ := liquid.Parse(someByteTemplate, liquid.Configure().Cache(nil))
//OR
template, _ := liquid.ParseString(someStringTemplate, liquid.NoCache)

Alternatively, you can provide your own core.Cache implementation which could implement expiry and other custom features.

Configuration

As seen above, a configuration can be provided when generating a template. Configuration is achieved via a fluent-interface. Configurable options are:

  • Cache(cache core.Cache): the caching implementation to use. This defaults to liquid.SimpleCache, which is thread-safe.
  • IncludeHandler(handler core.IncludeHandler): the callback used for handling includes. By default, includes are ignored. See below for more information.
  • PreserveWhitespace(): By default, Liquid will slightly compact whitespace around tags. It doesn't do a perfect job, but it does reduce the whitespace noise. This method lets you skip this whitespace compaction

Global Configuration

A few global configuration methods are available:

  • SetInternalBuffer(count, size int): the number of internal buffers and the maximum size of each buffer to use. Defaults to 512 and 4KB. This is currently only used for the capture tag. If you need to capture more than 4KB, increase the 2nd value.

Data Binding

The template's Render method takes a map[string]interface{} as its argument. Beyond that, Render works on all built-in types, and will also reflect the exported fields of a struct.

type User struct {
  Name  string
  Manager *User
}

t, _ := liquid.ParseString("{{ user.manager.name }}", nil)
t.Render(os.Stdout, map[string]interface{}{
  "user": &User{"Duncan", &User{"Leto", nil}},
})

or can be a map,

t, _ := liquid.ParseString("{{ user.manager.name }}", nil)
t.Render(os.Stdout, map[string]interface{}{
	"user": map[string]interface{}{
		"manager": map[string]interface{}{"name": "Leto"} ,
		},
})

Notice that the template fields aren't case sensitive. If you're exporting fields such as FirstName and Firstname then shame on you. Make sure to downcase map keys.

Complex objects should implement the fmt.Stringer interface (which is Go's toString() equivalent):

func (u *User) String() string {
  return u.Name
}

t, _ := liquid.ParseString("{{ user.manager }}", nil)

Failing this, fmt.Sprintf("%v") is used to generate a value. At this point, it's really more for debugging purposes.

Filters

You can add custom filters by calling core.RegisterFilter. The filter lookup is not thread safe; it is expected that you'll add filters on init and then leave it alone.

It's best to look at the existing filters for ideas on how to proceed. Briefly, there are two types of filters: those with parameters and those without. To support both from a single interface, each filter has a factory. For filters without parameters, the factory is simple:

func UpcaseFactory(parameters []string) core.Filter {
  return Upcase
}
func Upcase(input interface{}, data map[string]interface{}) interface{} {
  //todo
}

For filters that expect parameters, a little more work is needed:

func JoinFactory(parameters []core.Value) core.Filter {
  if len(parameters) == 0 {
    return defaultJoin.Join
  }
  return (&JoinFilter{parameters[0]}).Join
}
type JoinFilter struct {
  glue core.Value
}
func (f *JoinFilter) Join(input interface{}, data map[string]interface{}) interface{} {

}

It's a good idea to provide default values for parameters!

Finally, do note that Filters work with and return interface{}. Consider using a type switch with a default case which returns the input.

If you're filter works with string or []byte, you should handle both string and []byte types as you don't know what the user will provide nor what transformation previous filters might apply. Similarly, if you're expecting an array, you should handle both arrays and slices.

Again, look at existing filters for more insight.

Include

The include tag is supported by configuring a custom IncludeHandler. The handler is responsible for resolving the include. This provides the greatest amount of flexibility: included templates can be loaded from the file system, a database or some other location.

For example, an include handler which loads templates from the filsystem, might look like:

var config = liquid.Configure().IncludeHandler(includeHandler)

func main {
  template, err := liquid.ParseString(..., config)
  //...
}

// name is equal to the paramter passed to include
// data is the data available to the template
func includeHandler(name string, writer io.Writer, data map[string]interface{}) {
  // not sure if this is good enough, but do be mindful of directory traversal attacks
  fileName := path.Join("./templates", string.Replace(name, "..", ""))
  template, _ := liquid.ParseFile(fileName, config)
  template.Render(writer, data)
}

Errors

Render shouldn't fail, but it doesn't always stay silent about mistakes. For the sake of helping debug issues, it can inject data within the template. For example, using the output tag such as {{ user.name.first | upcase }} when user.name.first doesn't map to valid data will result in the literal "{{user.name.first}}"" being injecting in the template.

Parse and its variants will return an error if the template is not valid. The error message is meant to be helpful and can be shown as-is to users.

Documentation

Overview

liquid template parser

Index

Constants

This section is empty.

Variables

View Source
var (

	//A Configuration with caching disabled
	NoCache = Configure().Cache(nil)
)
View Source
var Tags = map[string]TagFactory{
	"comment":    tags.CommentFactory,
	"endcomment": tags.EndCommentFactory,
	"raw":        tags.RawFactory,
	"endraw":     tags.EndRawFactory,
	"assign":     tags.AssignFactory,
	"capture":    tags.CaptureFactory,
	"endcapture": tags.EndCaptureFactory,
	"if":         tags.IfFactory,
	"elseif":     tags.ElseIfFactory,
	"else":       tags.ElseFactory,
	"endif":      tags.EndIfFactory,
	"unless":     tags.UnlessFactory,
	"endunless":  tags.EndUnlessFactory,
	"case":       tags.CaseFactory,
	"when":       tags.WhenFactory,
	"endcase":    tags.EndCaseFactory,
	"include":    tags.IncludeFactory,
	"for":        tags.ForFactory,
	"endfor":     tags.EndForFactory,
	"break":      tags.BreakFactory,
	"continue":   tags.ContinueFactory,
}
View Source
var TemplateCache = &SimpleCache{lookup: make(map[string]core.Code)}

Functions

func Configure

func Configure() *core.Configuration

Entry into the fluent-configuration

func SetInternalBuffer

func SetInternalBuffer(count, size int)

Set's the count and size of the internal bytepool

Types

type Literal

type Literal struct {
	Value []byte
}

func (*Literal) Execute

func (l *Literal) Execute(writer io.Writer, data map[string]interface{}) core.ExecuteState

type Output

type Output struct {
	Value   core.Value
	Filters []core.Filter
}

func (*Output) Execute

func (o *Output) Execute(writer io.Writer, data map[string]interface{}) core.ExecuteState

type SimpleCache

type SimpleCache struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

func (*SimpleCache) Clear

func (c *SimpleCache) Clear()

func (*SimpleCache) Get

func (c *SimpleCache) Get(key string) core.Code

func (*SimpleCache) Set

func (c *SimpleCache) Set(key string, template core.Code)

type TagFactory

type TagFactory func(*core.Parser, *core.Configuration) (core.Tag, error)

type Template

type Template struct {
	Code []core.Code
}

A compiled liquid template

func Parse

func Parse(data []byte, config *core.Configuration) (*Template, error)

Parse the bytes into a Liquid template

func ParseFile

func ParseFile(path string, config *core.Configuration) (*Template, error)

Turn the contents of the specified file into a liquid template

func ParseString

func ParseString(data string, config *core.Configuration) (*Template, error)

Parse the string into a liquid template

func (*Template) AddCode

func (t *Template) AddCode(code core.Code)

func (*Template) AddSibling

func (t *Template) AddSibling(tag core.Tag) error

func (*Template) Execute

func (t *Template) Execute(writer io.Writer, data map[string]interface{}) core.ExecuteState

func (*Template) LastSibling

func (t *Template) LastSibling() core.Tag

func (*Template) Name

func (t *Template) Name() string

func (*Template) Render

func (t *Template) Render(writer io.Writer, data map[string]interface{})

func (*Template) Type

func (t *Template) Type() core.TagType

Directories

Path Synopsis
shared interfaces and utility functions
shared interfaces and utility functions

Jump to

Keyboard shortcuts

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