forked from osteele/liquid
-
Notifications
You must be signed in to change notification settings - Fork 0
/
render.go
131 lines (120 loc) · 3.13 KB
/
render.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Package render is an internal package that renders a compiled template parse tree.
package render
import (
"fmt"
"io"
"reflect"
"time"
"github.com/osteele/liquid/values"
)
// Render renders the render tree.
func Render(node Node, w io.Writer, vars map[string]interface{}, c Config) Error {
tw := trimWriter{w: w}
if err := node.render(&tw, newNodeContext(vars, c)); err != nil {
return err
}
if err := tw.Flush(); err != nil {
panic(err)
}
return nil
}
// RenderASTSequence renders a sequence of nodes.
func (c nodeContext) RenderSequence(w io.Writer, seq []Node) Error {
tw := trimWriter{w: w}
for _, n := range seq {
if err := n.render(&tw, c); err != nil {
return err
}
}
if err := tw.Flush(); err != nil {
panic(err)
}
return nil
}
func (n *BlockNode) render(w *trimWriter, ctx nodeContext) Error {
cd, ok := ctx.config.findBlockDef(n.Name)
if !ok || cd.parser == nil {
// this should have been detected during compilation; it's an implementation error if it happens here
panic(fmt.Errorf("undefined tag %q", n.Name))
}
renderer := n.renderer
if renderer == nil {
panic(fmt.Errorf("unset renderer for %v", n))
}
err := renderer(w, rendererContext{ctx, nil, n})
return wrapRenderError(err, n)
}
func (n *RawNode) render(w *trimWriter, ctx nodeContext) Error {
for _, s := range n.slices {
_, err := io.WriteString(w, s)
if err != nil {
return wrapRenderError(err, n)
}
}
return nil
}
func (n *ObjectNode) render(w *trimWriter, ctx nodeContext) Error {
w.TrimLeft(n.TrimLeft)
value, err := ctx.Evaluate(n.expr)
if err != nil {
return wrapRenderError(err, n)
}
if err := wrapRenderError(writeObject(w, value), n); err != nil {
return err
}
w.TrimRight(n.TrimRight)
return nil
}
func (n *SeqNode) render(w *trimWriter, ctx nodeContext) Error {
for _, c := range n.Children {
if err := c.render(w, ctx); err != nil {
return err
}
}
return nil
}
func (n *TagNode) render(w *trimWriter, ctx nodeContext) Error {
w.TrimLeft(n.TrimLeft)
err := wrapRenderError(n.renderer(w, rendererContext{ctx, n, nil}), n)
w.TrimRight(n.TrimRight)
return err
}
func (n *TextNode) render(w *trimWriter, ctx nodeContext) Error {
_, err := io.WriteString(w, n.Source)
return wrapRenderError(err, n)
}
// writeObject writes a value used in an object node
func writeObject(w io.Writer, value interface{}) error {
value = values.ToLiquid(value)
if value == nil {
return nil
}
switch value := value.(type) {
case time.Time:
_, err := io.WriteString(w, value.Format("2006-01-02 15:04:05 -0700"))
return err
case []byte:
_, err := w.Write(value)
return err
// there used be a case on fmt.Stringer here, but fmt.Sprint produces better results than obj.Write
// for instances of error and *string
}
rt := reflect.ValueOf(value)
switch rt.Kind() {
case reflect.Array, reflect.Slice:
for i := 0; i < rt.Len(); i++ {
item := rt.Index(i)
if item.IsValid() {
if err := writeObject(w, item.Interface()); err != nil {
return err
}
}
}
return nil
case reflect.Ptr:
return writeObject(w, reflect.ValueOf(value).Elem())
default:
_, err := io.WriteString(w, fmt.Sprint(value))
return err
}
}