Skip to content

Commit

Permalink
Tags are called within a RenderContext
Browse files Browse the repository at this point in the history
  • Loading branch information
osteele committed Jun 30, 2017
1 parent 25e97ed commit 41da3f9
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 122 deletions.
4 changes: 2 additions & 2 deletions chunks/ast.go
Expand Up @@ -25,7 +25,7 @@ type ASTSeq struct {
// ASTFunctional renders itself via a render function that is created during parsing.
type ASTFunctional struct {
Chunk
render func(io.Writer, Context) error
render func(io.Writer, RenderContext) error
}

// ASTText is a text chunk, that is rendered verbatim.
Expand All @@ -42,7 +42,7 @@ type ASTObject struct {
// ASTControlTag is a control tag.
type ASTControlTag struct {
Chunk
renderer func(io.Writer, Context) error
renderer func(io.Writer, RenderContext) error
cd *controlTagDefinition
Body []ASTNode
Branches []*ASTControlTag
Expand Down
17 changes: 0 additions & 17 deletions chunks/combinators.go

This file was deleted.

49 changes: 0 additions & 49 deletions chunks/context.go
@@ -1,8 +1,6 @@
package chunks

import (
"fmt"

"github.com/osteele/liquid/expressions"
)

Expand All @@ -22,22 +20,6 @@ func NewContext(scope map[string]interface{}) Context {
return Context{vars}
}

// GetVariableMap returns the variable map. This is required by some tangled code
// in Jekyll includes, that should hopefully get better.
func (c Context) GetVariableMap() map[string]interface{} {
return c.vars
}

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

// Set sets a variable value from an evaluation context.
func (c Context) Set(name string, value interface{}) {
c.vars[name] = value
}

// Evaluate evaluates an expression within the template context.
func (c Context) Evaluate(expr expressions.Expression) (out interface{}, err error) {
defer func() {
Expand All @@ -53,34 +35,3 @@ func (c Context) Evaluate(expr expressions.Expression) (out interface{}, err err
}()
return expr.Evaluate(expressions.NewContext(c.vars))
}

// EvaluateString evaluates an expression within the template context.
func (c Context) EvaluateString(source string) (out interface{}, err error) {
defer func() {
if r := recover(); r != nil {
switch e := r.(type) {
case expressions.InterpreterError:
err = e
default:
// fmt.Println(string(debug.Stack()))
panic(fmt.Errorf("%s during evaluation of %s", e, source))
}
}
}()
return expressions.EvaluateString(source, expressions.NewContext(c.vars))
}

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

// MakeExpressionValueFn parses source into an evaluation function
func MakeExpressionValueFn(source string) (func(Context) (interface{}, error), error) {
expr, err := expressions.Parse(source)
if err != nil {
return nil, err
}
return func(ctx Context) (interface{}, error) {
return expr.Evaluate(expressions.NewContext(ctx.vars))
}, nil
}
10 changes: 5 additions & 5 deletions chunks/control_tags.go
Expand Up @@ -5,12 +5,12 @@ import (
"io"
)

// ControlTagParser builds a renderer for the tag instance.
type ControlTagParser func(ASTControlTag) (func(io.Writer, RenderContext) error, error)

// controlTagDefinitions is a map of tag names to control tag definitions.
var controlTagDefinitions = map[string]*controlTagDefinition{}

// ControlTagParser builds a renderer for the tag instance.
type ControlTagParser func(ASTControlTag) (func(io.Writer, Context) error, error)

// controlTagDefinition tells the parser how to parse control tags.
type controlTagDefinition struct {
name string
Expand Down Expand Up @@ -85,8 +85,8 @@ func (b tagBuilder) Parser(fn ControlTagParser) {
}

// Renderer sets the render action for a control tag definition.
func (b tagBuilder) Renderer(fn func(io.Writer, Context) error) {
b.tag.parser = func(node ASTControlTag) (func(io.Writer, Context) error, error) {
func (b tagBuilder) Renderer(fn func(io.Writer, RenderContext) error) {
b.tag.parser = func(node ASTControlTag) (func(io.Writer, RenderContext) error, error) {
// TODO parse error if there are arguments?
return fn, nil
}
Expand Down
8 changes: 4 additions & 4 deletions chunks/definitions.go
Expand Up @@ -6,12 +6,12 @@ import (

// TagDefinition is a function that parses the tag arguments, and returns a renderer.
// TODO instead of using the bare function definition, use a structure that defines how to parse
type TagDefinition func(expr string) (func(io.Writer, Context) error, error)
type TagDefinition func(expr string) (func(io.Writer, RenderContext) error, error)

// TODO parse during definition stage, not rendering stage
func assignTagDef(source string) (func(io.Writer, Context) error, error) {
return func(w io.Writer, ctx Context) error {
_, err := ctx.evaluateStatement("assign", source)
func assignTagDef(source string) (func(io.Writer, RenderContext) error, error) {
return func(w io.Writer, ctx RenderContext) error {
_, err := ctx.EvaluateStatement("assign", source)
return err
}, nil
}
Expand Down
16 changes: 8 additions & 8 deletions chunks/render.go
Expand Up @@ -18,7 +18,7 @@ func (n *ASTSeq) Render(w io.Writer, ctx Context) error {

// Render is in the ASTNode interface.
func (n *ASTFunctional) Render(w io.Writer, ctx Context) error {
err := n.render(w, ctx)
err := n.render(w, renderContext{ctx, n, nil})
// TODO restore something like this
// if err != nil {
// fmt.Println("while parsing", n.Source)
Expand Down Expand Up @@ -53,7 +53,7 @@ func (n *ASTControlTag) Render(w io.Writer, ctx Context) error {
if renderer == nil {
panic(fmt.Errorf("unset renderer for %v", n))
}
return renderer(w, ctx)
return renderer(w, renderContext{ctx, nil, n})
}

// Render is in the ASTNode interface.
Expand All @@ -62,21 +62,21 @@ func (n *ASTObject) Render(w io.Writer, ctx Context) error {
if err != nil {
return fmt.Errorf("%s in %s", err, n.Source)
}
return writeValue(value, w)
return writeObject(value, w)
}

func writeValue(value interface{}, w io.Writer) error {
// writeObject writes a value used in an object node
func writeObject(value interface{}, w io.Writer) error {
if value == nil {
return nil
}
rt := reflect.ValueOf(value)
switch rt.Kind() {
// TODO test case for this
case reflect.Array, reflect.Slice:
for i := 0; i < rt.Len(); i++ {
item := rt.Index(i)
if item.IsValid() {
if err := writeValue(item.Interface(), w); err != nil {
if err := writeObject(item.Interface(), w); err != nil {
return err
}
}
Expand All @@ -89,9 +89,9 @@ func writeValue(value interface{}, w io.Writer) error {
}

// RenderASTSequence renders a sequence of nodes.
func (ctx Context) RenderASTSequence(w io.Writer, seq []ASTNode) error {
func (c Context) RenderASTSequence(w io.Writer, seq []ASTNode) error {
for _, n := range seq {
if err := n.Render(w, ctx); err != nil {
if err := n.Render(w, c); err != nil {
return err
}
}
Expand Down
90 changes: 90 additions & 0 deletions chunks/render_context.go
@@ -0,0 +1,90 @@
package chunks

import (
"bytes"
"fmt"
"io"

"github.com/osteele/liquid/expressions"
)

// RenderContext provides the rendering context for a tag renderer.
type RenderContext interface {
Get(name string) interface{}
Set(name string, value interface{})
GetVariableMap() map[string]interface{}
Evaluate(expr expressions.Expression) (interface{}, error)
EvaluateString(source string) (interface{}, error)
EvaluateStatement(tag, source string) (interface{}, error)
RenderBranch(io.Writer, *ASTControlTag) error
RenderChildren(io.Writer) error
InnerString() (string, error)
}

type renderContext struct {
ctx Context
node *ASTFunctional
cn *ASTControlTag
}

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

// EvaluateString evaluates an expression within the template context.
func (c renderContext) EvaluateString(source string) (out interface{}, err error) {
defer func() {
if r := recover(); r != nil {
switch e := r.(type) {
case expressions.InterpreterError:
err = e
default:
// fmt.Println(string(debug.Stack()))
panic(fmt.Errorf("%s during evaluation of %s", e, source))
}
}
}()
return expressions.EvaluateString(source, expressions.NewContext(c.ctx.vars))
}

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

// GetVariableMap returns the variable map. This is required by some tangled code
// in Jekyll includes, that should hopefully get better.
func (c renderContext) GetVariableMap() map[string]interface{} {
return c.ctx.vars
}

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

// Set sets a variable value from an evaluation context.
func (c renderContext) Set(name string, value interface{}) {
c.ctx.vars[name] = value
}

// RenderChildren renders the current node's children.
func (c renderContext) RenderChildren(w io.Writer) error {
if c.cn == nil {
return nil
}
return c.ctx.RenderASTSequence(w, c.cn.Body)
}

func (c renderContext) RenderBranch(w io.Writer, b *ASTControlTag) error {
return c.ctx.RenderASTSequence(w, b.Body)
}

// InnerString renders the children to a string.
func (c renderContext) InnerString() (string, error) {
buf := new(bytes.Buffer)
if err := c.RenderChildren(buf); err != nil {
return "", err
}
return buf.String(), nil
}
4 changes: 2 additions & 2 deletions engine.go
Expand Up @@ -29,8 +29,8 @@ func NewEngine() Engine {

// DefineStartTag is in the Engine interface.
func (e engine) DefineStartTag(name string, td TagDefinition) {
chunks.DefineStartTag(name).Parser(func(c chunks.ASTControlTag) (func(io.Writer, chunks.Context) error, error) {
return func(io.Writer, chunks.Context) error {
chunks.DefineStartTag(name).Parser(func(c chunks.ASTControlTag) (func(io.Writer, chunks.RenderContext) error, error) {
return func(io.Writer, chunks.RenderContext) error {
fmt.Println("unimplemented tag:", name)
return nil
}, nil
Expand Down
31 changes: 31 additions & 0 deletions expressions/functional.go
@@ -0,0 +1,31 @@
package expressions

type wrapper struct {
fn func(ctx Context) (interface{}, error)
}

func (w wrapper) Evaluate(ctx Context) (interface{}, error) {
return w.fn(ctx)
}

// True returns the same value each time.
func Constant(k interface{}) Expression {
return wrapper{
func(_ Context) (interface{}, error) {
return k, nil
},
}
}

// Negate negates its argument.
func Negate(e Expression) Expression {
return wrapper{
func(ctx Context) (interface{}, error) {
value, err := e.Evaluate(ctx)
if err != nil {
return nil, err
}
return (value == nil || value == false), nil
},
}
}
2 changes: 1 addition & 1 deletion interface.go
Expand Up @@ -45,4 +45,4 @@ type Renderer func(io.Writer, chunks.Context) error

// TagDefinition is the type of a function that parses the argument string "args" from a tag "{% tagname args %}",
// and returns a renderer.
type TagDefinition func(parameters string) (func(io.Writer, chunks.Context) error, error)
type TagDefinition func(parameters string) (func(io.Writer, chunks.RenderContext) error, error)
14 changes: 7 additions & 7 deletions tags/loop.go
Expand Up @@ -12,14 +12,14 @@ import (
var errLoopContinueLoop = fmt.Errorf("continue outside a loop")
var errLoopBreak = fmt.Errorf("break outside a loop")

func breakTag(parameters string) (func(io.Writer, chunks.Context) error, error) {
return func(io.Writer, chunks.Context) error {
func breakTag(parameters string) (func(io.Writer, chunks.RenderContext) error, error) {
return func(io.Writer, chunks.RenderContext) error {
return errLoopBreak
}, nil
}

func continueTag(parameters string) (func(io.Writer, chunks.Context) error, error) {
return func(io.Writer, chunks.Context) error {
func continueTag(parameters string) (func(io.Writer, chunks.RenderContext) error, error) {
return func(io.Writer, chunks.RenderContext) error {
return errLoopContinueLoop
}, nil
}
Expand All @@ -32,12 +32,12 @@ func parseLoopExpression(source string) (expressions.Expression, error) {
return expr, nil
}

func loopTagParser(node chunks.ASTControlTag) (func(io.Writer, chunks.Context) error, error) {
func loopTagParser(node chunks.ASTControlTag) (func(io.Writer, chunks.RenderContext) error, error) {
expr, err := parseLoopExpression(node.Parameters)
if err != nil {
return nil, err
}
return func(w io.Writer, ctx chunks.Context) error {
return func(w io.Writer, ctx chunks.RenderContext) error {
val, err := ctx.Evaluate(expr)
if err != nil {
return err
Expand Down Expand Up @@ -81,7 +81,7 @@ func loopTagParser(node chunks.ASTControlTag) (func(io.Writer, chunks.Context) e
"rindex0": length - i - 1,
"length": length,
})
err := ctx.RenderASTSequence(w, node.Body)
err := ctx.RenderChildren(w)
if err == errLoopBreak {
break
}
Expand Down

0 comments on commit 41da3f9

Please sign in to comment.