Skip to content

Commit

Permalink
Add Template.SetSourcePath
Browse files Browse the repository at this point in the history
  • Loading branch information
osteele committed Jul 4, 2017
1 parent d31fe04 commit 5425668
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 80 deletions.
2 changes: 1 addition & 1 deletion engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type engine struct{ settings render.Config }
// NewEngine returns a new template engine.
func NewEngine() Engine {
e := engine{render.NewConfig()}
filters.AddStandardFilters(e.settings.ExpressionConfig)
filters.AddStandardFilters(e.settings.Config)
tags.AddStandardTags(e.settings)
return e
}
Expand Down
27 changes: 8 additions & 19 deletions engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/stretchr/testify/require"
)

var emptyBindings = map[string]interface{}{}

// There's a lot more tests in the filters and tags sub-packages.
// This collects a minimal set for testing end-to-end.
var liquidTests = []struct{ in, expected string }{
Expand All @@ -26,7 +28,7 @@ var testBindings = map[string]interface{}{
},
}

func TestLiquid(t *testing.T) {
func TestEngine_ParseAndRenderString(t *testing.T) {
engine := NewEngine()
for i, test := range liquidTests {
t.Run(fmt.Sprint(i+1), func(t *testing.T) {
Expand All @@ -37,15 +39,6 @@ func TestLiquid(t *testing.T) {
}
}

func TestTemplateRenderString(t *testing.T) {
engine := NewEngine()
template, err := engine.ParseTemplate([]byte(`{{ "hello world" | capitalize }}`))
require.NoError(t, err)
out, err := template.RenderString(testBindings)
require.NoError(t, err)
require.Equal(t, "Hello world", out)
}

func Example() {
engine := NewEngine()
template := `<h1>{{ page.title }}</h1>`
Expand All @@ -62,11 +55,10 @@ func Example() {
// Output: <h1>Introduction</h1>
}

func Example_register_filter() {
func ExampleEngine_RegisterFilter() {
engine := NewEngine()
engine.RegisterFilter("has_prefix", strings.HasPrefix)
template := `{{ title | has_prefix: "Intro" }}`

bindings := map[string]interface{}{
"title": "Introduction",
}
Expand All @@ -78,23 +70,21 @@ func Example_register_filter() {
// Output: true
}

func Example_register_tag() {
func ExampleEngine_RegisterTag() {
engine := NewEngine()
engine.RegisterTag("echo", func(c render.Context) (string, error) {
return c.TagArgs(), nil
})

template := `{% echo hello world %}`
bindings := map[string]interface{}{}
out, err := engine.ParseAndRenderString(template, bindings)
out, err := engine.ParseAndRenderString(template, emptyBindings)
if err != nil {
log.Fatalln(err)
}
fmt.Println(out)
// Output: hello world
}

func Example_register_block() {
func ExampleEngine_RegisterBlock() {
engine := NewEngine()
engine.RegisterBlock("length", func(c render.Context) (string, error) {
s, err := c.InnerString()
Expand All @@ -106,8 +96,7 @@ func Example_register_block() {
})

template := `{% length %}abc{% endlength %}`
bindings := map[string]interface{}{}
out, err := engine.ParseAndRenderString(template, bindings)
out, err := engine.ParseAndRenderString(template, emptyBindings)
if err != nil {
log.Fatalln(err)
}
Expand Down
9 changes: 4 additions & 5 deletions liquid.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/osteele/liquid/render"
)

// Engine parses template source into renderable text.
// An Engine parses template source into renderable text.
//
// An engine can be configured with additional filters and tags.
//
Expand All @@ -33,14 +33,13 @@ type Engine interface {
ParseAndRenderString(string, Bindings) (string, error)
}

// Template renders a template according to scope.
//
// Bindings is a map of liquid variable names to objects.
// A Template renders a template according to scope.
type Template interface {
// Render executes the template with the specified bindings.
Render(Bindings) ([]byte, error)
// RenderString is a convenience wrapper for Render, that has string input and output.
RenderString(Bindings) (string, error)
SetSourcePath(string)
}

// Bindings is a map of variable names to values.
Expand All @@ -50,5 +49,5 @@ type Bindings map[string]interface{}
// and returns a renderer.
// type TagParser func(chunks.RenderContext) (string, error)

// Renderer returns the rendered string for a block.
// A Renderer returns the rendered string for a block.
type Renderer func(render.Context) (string, error)
6 changes: 3 additions & 3 deletions render/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ func (c *blockDef) isStartTag() bool {
}

func (s Config) addBlockDef(ct *blockDef) {
s.controlTags[ct.name] = ct
s.blockDefs[ct.name] = ct
}

func (s Config) findBlockDef(name string) (*blockDef, bool) {
ct, found := s.controlTags[name]
ct, found := s.blockDefs[name]
return ct, found
}

Expand Down Expand Up @@ -71,7 +71,7 @@ func (b blockDefBuilder) Governs(_ []string) blockDefBuilder {

// SameSyntaxAs tells the parser that this tag has the same syntax as the named tag.
func (b blockDefBuilder) SameSyntaxAs(name string) blockDefBuilder {
rt := b.s.controlTags[name]
rt := b.s.blockDefs[name]
if rt == nil {
panic(fmt.Errorf("undefined: %s", name))
}
Expand Down
16 changes: 9 additions & 7 deletions render/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ import "github.com/osteele/liquid/expression"

// Config holds configuration information for parsing and rendering.
type Config struct {
ExpressionConfig expression.Config
tags map[string]TagDefinition
controlTags map[string]*blockDef
// ExpressionConfig expression.Config
expression.Config
Filename string
tags map[string]TagDefinition
blockDefs map[string]*blockDef
}

// NewConfig creates a new Settings.
func NewConfig() Config {
s := Config{
expression.NewConfig(),
map[string]TagDefinition{},
map[string]*blockDef{},
Config: expression.NewConfig(),
tags: map[string]TagDefinition{},
blockDefs: map[string]*blockDef{},
}
return s
}

// AddFilter adds a filter to settings.
func (s Config) AddFilter(name string, fn interface{}) {
s.ExpressionConfig.AddFilter(name, fn)
s.Config.AddFilter(name, fn)
}
73 changes: 35 additions & 38 deletions render/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,19 @@ import (

// Context provides the rendering context for a tag renderer.
type Context interface {
Clone() Context
Get(name string) interface{}
Set(name string, value interface{})
Evaluate(expr expression.Expression) (interface{}, error)
EvaluateString(source string) (interface{}, error)
EvaluateStatement(tag, source string) (interface{}, error)
InnerString() (string, error)
ParseTagArgs() (string, error)
RenderChild(io.Writer, *ASTBlock) error
RenderChildren(io.Writer) error
RenderFile(filename string) (string, error)
RenderFile(string, map[string]interface{}) (string, error)
Set(name string, value interface{})
SourceFile() string
TagArgs() string
TagName() string
UpdateBindings(map[string]interface{})
}

type renderContext struct {
Expand All @@ -34,15 +33,15 @@ type renderContext struct {
cn *ASTBlock
}

func (c renderContext) Clone() Context {
return renderContext{c.ctx.Clone(), c.node, c.cn}
}

// Evaluate evaluates an expression within the template context.
func (c renderContext) Evaluate(expr expression.Expression) (out interface{}, err error) {
return c.ctx.Evaluate(expr)
}

func (c renderContext) EvaluateStatement(tag, source string) (interface{}, error) {
return c.EvaluateString(fmt.Sprintf("%%%s %s", tag, source))
}

// EvaluateString evaluates an expression within the template context.
func (c renderContext) EvaluateString(source string) (out interface{}, err error) {
defer func() {
Expand All @@ -56,21 +55,29 @@ func (c renderContext) EvaluateString(source string) (out interface{}, err error
}
}
}()
return expression.EvaluateString(source, expression.NewContext(c.ctx.bindings, c.ctx.settings.ExpressionConfig))
}

func (c renderContext) EvaluateStatement(tag, source string) (interface{}, error) {
return c.EvaluateString(fmt.Sprintf("%%%s %s", tag, source))
return expression.EvaluateString(source, expression.NewContext(c.ctx.bindings, c.ctx.config.Config))
}

// Get gets a variable value within an evaluation context.
func (c renderContext) Get(name string) interface{} {
return c.ctx.bindings[name]
}

// Set sets a variable value from an evaluation context.
func (c renderContext) Set(name string, value interface{}) {
c.ctx.bindings[name] = value
func (c renderContext) ParseTagArgs() (string, error) {
args := c.TagArgs()
if strings.Contains(args, "{{") {
p, err := c.ctx.config.Parse(args)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
err = renderNode(p, buf, c.ctx)
if err != nil {
return "", err
}
return buf.String(), nil
}
return args, nil
}

// RenderChild renders a node.
Expand All @@ -86,17 +93,21 @@ func (c renderContext) RenderChildren(w io.Writer) error {
return c.ctx.RenderASTSequence(w, c.cn.Body)
}

func (c renderContext) RenderFile(filename string) (string, error) {
func (c renderContext) RenderFile(filename string, b map[string]interface{}) (string, error) {
source, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
ast, err := c.ctx.settings.Parse(string(source))
ast, err := c.ctx.config.Parse(string(source))
if err != nil {
return "", err
}
nc := c.ctx.Clone()
for k, v := range b {
c.ctx.bindings[k] = v
}
buf := new(bytes.Buffer)
if err := renderNode(ast, buf, c.ctx); err != nil {
if err := renderNode(ast, buf, nc); err != nil {
return "", err
}
return buf.String(), nil
Expand All @@ -111,27 +122,13 @@ func (c renderContext) InnerString() (string, error) {
return buf.String(), nil
}

func (c renderContext) ParseTagArgs() (string, error) {
args := c.TagArgs()
if strings.Contains(args, "{{") {
p, err := c.ctx.settings.Parse(args)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
err = renderNode(p, buf, c.ctx)
if err != nil {
return "", err
}
return buf.String(), nil
}
return args, nil
// Set sets a variable value from an evaluation context.
func (c renderContext) Set(name string, value interface{}) {
c.ctx.bindings[name] = value
}

func (c renderContext) UpdateBindings(bindings map[string]interface{}) {
for k, v := range bindings {
c.ctx.bindings[k] = v
}
func (c renderContext) SourceFile() string {
return c.ctx.config.Filename
}

func (c renderContext) TagArgs() string {
Expand Down
6 changes: 3 additions & 3 deletions render/node_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
// nodeContext is the evaluation context for chunk AST rendering.
type nodeContext struct {
bindings map[string]interface{}
settings Config
config Config
}

// newNodeContext creates a new evaluation context.
Expand All @@ -27,7 +27,7 @@ func (c nodeContext) Clone() nodeContext {
for k, v := range c.bindings {
bindings[k] = v
}
return nodeContext{bindings, c.settings}
return nodeContext{bindings, c.config}
}

// Evaluate evaluates an expression within the template context.
Expand All @@ -43,5 +43,5 @@ func (c nodeContext) Evaluate(expr expression.Expression) (out interface{}, err
}
}
}()
return expr.Evaluate(expression.NewContext(c.bindings, c.settings.ExpressionConfig))
return expr.Evaluate(expression.NewContext(c.bindings, c.config.Config))
}
2 changes: 1 addition & 1 deletion render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func renderNode(node ASTNode, w io.Writer, ctx nodeContext) error { // nolint: g
}
}
case *ASTBlock:
cd, ok := ctx.settings.findBlockDef(n.Name)
cd, ok := ctx.config.findBlockDef(n.Name)
if !ok || cd.parser == nil {
return fmt.Errorf("unknown tag: %s", n.Name)
}
Expand Down
10 changes: 7 additions & 3 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import (
)

type template struct {
ast render.ASTNode
settings render.Config
ast render.ASTNode
config render.Config
}

// Render executes the template within the bindings environment.
func (t *template) Render(b Bindings) ([]byte, error) {
buf := new(bytes.Buffer)
err := render.Render(t.ast, buf, b, t.settings)
err := render.Render(t.ast, buf, b, t.config)
if err != nil {
return nil, err
}
Expand All @@ -29,3 +29,7 @@ func (t *template) RenderString(b Bindings) (string, error) {
}
return string(bs), nil
}

func (t *template) SetSourcePath(filename string) {
t.config.Filename = filename
}
Loading

0 comments on commit 5425668

Please sign in to comment.