package liquid import ( "io" "github.com/osteele/liquid/filters" "github.com/osteele/liquid/render" "github.com/osteele/liquid/tags" ) // An Engine parses template source into renderable text. // // An engine can be configured with additional filters and tags. type Engine struct{ cfg render.Config } // NewEngine returns a new Engine. func NewEngine() *Engine { e := Engine{render.NewConfig()} filters.AddStandardFilters(&e.cfg) tags.AddStandardTags(e.cfg) return &e } // RegisterBlock defines a block e.g. {% tag %}…{% endtag %}. func (e *Engine) RegisterBlock(name string, td Renderer) { e.cfg.AddBlock(name).Renderer(func(w io.Writer, ctx render.Context) error { s, err := td(ctx) if err != nil { return err } _, err = io.WriteString(w, s) return err }) } // 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/osteele/liquid/blob/master/filters/filters.go // // * https://github.com/osteele/gojekyll/blob/master/filters/filters.go // func (e *Engine) RegisterFilter(name string, fn interface{}) { e.cfg.AddFilter(name, fn) } // RegisterTag defines a tag e.g. {% tag %}. // // Further examples are in https://github.com/osteele/gojekyll/blob/master/tags/tags.go func (e *Engine) RegisterTag(name string, td Renderer) { // For simplicity, don't expose the two stage parsing/rendering process to clients. // Client tags do everything at runtime. e.cfg.AddTag(name, func(_ string) (func(io.Writer, render.Context) error, error) { return func(w io.Writer, ctx render.Context) error { s, err := td(ctx) if err != nil { return err } _, err = io.WriteString(w, s) return err }, nil }) } // ParseTemplate creates a new Template using the engine configuration. func (e *Engine) ParseTemplate(source []byte) (*Template, SourceError) { return newTemplate(&e.cfg, source, "", 0) } // ParseString creates a new Template using the engine configuration. func (e *Engine) ParseString(source string) (*Template, SourceError) { return e.ParseTemplate([]byte(source)) } // 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 (e *Engine) ParseTemplateLocation(source []byte, path string, line int) (*Template, SourceError) { return newTemplate(&e.cfg, source, path, line) } // ParseAndRender parses and then renders the template. func (e *Engine) ParseAndRender(source []byte, b Bindings) ([]byte, SourceError) { tpl, err := e.ParseTemplate(source) if err != nil { return nil, err } return tpl.Render(b) } // ParseAndRenderString is a convenience wrapper for ParseAndRender, that takes string input and returns a string. func (e *Engine) ParseAndRenderString(source string, b Bindings) (string, SourceError) { bs, err := e.ParseAndRender([]byte(source), b) if err != nil { return "", err } return string(bs), nil } // 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 (e *Engine) Delims(objectLeft, objectRight, tagLeft, tagRight string) *Engine { e.cfg.Delims = []string{objectLeft, objectRight, tagLeft, tagRight} return e }