-
Notifications
You must be signed in to change notification settings - Fork 54
/
context.go
224 lines (199 loc) · 6.46 KB
/
context.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package render
import (
"bytes"
"io"
"io/ioutil"
"os"
"strings"
"github.com/osteele/liquid/parser"
"github.com/osteele/liquid/expressions"
)
// Context provides the rendering context for a tag renderer.
type Context interface {
// Bindings returns the current lexical environment.
Bindings() map[string]interface{}
// Get retrieves the value of a variable from the current lexical environment.
Get(name string) interface{}
// Errorf creates a SourceError, that includes the source location.
// Use this to distinguish errors in the template from implementation errors
// in the template engine.
Errorf(format string, a ...interface{}) Error
// Evaluate evaluates a compiled expression within the current lexical context.
Evaluate(expressions.Expression) (interface{}, error)
// EvaluateString compiles and evaluates a string expression such as “x”, “x < 10", or “a.b | split | first | default: 10”, within the current lexical context.
EvaluateString(string) (interface{}, error)
// ExpandTagArg renders the current tag argument string as a Liquid template.
// It enables the implementation of tags such as Jekyll's "{% include {{ page.my_variable }} %}" andjekyll-avatar's "{% avatar {{page.author}} %}".
ExpandTagArg() (string, error)
// InnerString is the rendered content of the current block.
// It's used in the implementation of the Liquid "capture" tag and the Jekyll "highlght" tag.
InnerString() (string, error)
// RenderBlock is used in the implementation of the built-in control flow tags.
// It's not guaranteed stable.
RenderBlock(io.Writer, *BlockNode) error
// RenderChildren is used in the implementation of the built-in control flow tags.
// It's not guaranteed stable.
RenderChildren(io.Writer) Error
// RenderFile parses and renders a template. It's used in the implementation of the {% include %} tag.
// RenderFile does not cache the compiled template.
RenderFile(string, map[string]interface{}) (string, error)
// Set updates the value of a variable in the current lexical environment.
// It's used in the implementation of the {% assign %} and {% capture %} tags.
Set(name string, value interface{})
// SourceFile retrieves the value set by template.SetSourcePath.
// It's used in the implementation of the {% include %} tag.
SourceFile() string
// TagArgs returns the text of the current tag, not including its name.
// For example, the arguments to {% my_tag a b c %} would be “a b c”.
TagArgs() string
// TagName returns the name of the current tag; for example "my_tag" for {% my_tag a b c %}.
TagName() string
// WrapError creates a new error that records the source location from the current context.
WrapError(err error) Error
}
type rendererContext struct {
ctx nodeContext
node *TagNode
cn *BlockNode
}
type invalidLocation struct{}
func (i invalidLocation) SourceLocation() parser.SourceLoc {
return parser.SourceLoc{}
}
func (i invalidLocation) SourceText() string {
return ""
}
var invalidLoc parser.Locatable = invalidLocation{}
func (c rendererContext) Errorf(format string, a ...interface{}) Error {
switch {
case c.node != nil:
return renderErrorf(c.node, format, a...)
case c.cn != nil:
return renderErrorf(c.cn, format, a...)
default:
return renderErrorf(invalidLoc, format, a...)
}
}
func (c rendererContext) WrapError(err error) Error {
switch {
case c.node != nil:
return wrapRenderError(err, c.node)
case c.cn != nil:
return wrapRenderError(err, c.cn)
default:
return wrapRenderError(err, invalidLoc)
}
}
func (c rendererContext) Evaluate(expr expressions.Expression) (out interface{}, err error) {
return c.ctx.Evaluate(expr)
}
// EvaluateString evaluates an expression within the template context.
func (c rendererContext) EvaluateString(source string) (out interface{}, err error) {
return expressions.EvaluateString(source, expressions.NewContext(c.ctx.bindings, c.ctx.config.Config.Config))
}
// Bindings returns the current lexical environment.
func (c rendererContext) Bindings() map[string]interface{} {
return c.ctx.bindings
}
// Get gets a variable value within an evaluation context.
func (c rendererContext) Get(name string) interface{} {
return c.ctx.bindings[name]
}
func (c rendererContext) ExpandTagArg() (string, error) {
args := c.TagArgs()
if strings.Contains(args, "{{") {
root, err := c.ctx.config.Compile(args, c.node.SourceLoc)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
err = Render(root, buf, c.ctx.bindings, c.ctx.config)
if err != nil {
return "", err
}
return buf.String(), nil
}
return args, nil
}
// RenderBlock renders a node.
func (c rendererContext) RenderBlock(w io.Writer, b *BlockNode) error {
return c.ctx.RenderSequence(w, b.Body)
}
// RenderChildren renders the current node's children.
func (c rendererContext) RenderChildren(w io.Writer) Error {
if c.cn == nil {
return nil
}
return c.ctx.RenderSequence(w, c.cn.Body)
}
func (c rendererContext) RenderFile(filename string, b map[string]interface{}) (string, error) {
source, err := ioutil.ReadFile(filename)
if err != nil && os.IsNotExist(err) {
// Is it cached?
if cval, ok := c.ctx.config.Cache[filename]; ok {
source = cval
} else {
return "", err
}
} else if err != nil {
return "", err
}
root, err := c.ctx.config.Compile(string(source), c.node.SourceLoc)
if err != nil {
return "", err
}
bindings := map[string]interface{}{}
for k, v := range c.ctx.bindings {
bindings[k] = v
}
for k, v := range b {
bindings[k] = v
}
buf := new(bytes.Buffer)
if err := Render(root, buf, bindings, c.ctx.config); err != nil {
return "", err
}
return buf.String(), nil
}
// InnerString renders the children to a string.
func (c rendererContext) InnerString() (string, error) {
buf := new(bytes.Buffer)
if err := c.RenderChildren(buf); err != nil {
return "", err
}
return buf.String(), nil
}
// Set sets a variable value from an evaluation context.
func (c rendererContext) Set(name string, value interface{}) {
c.ctx.bindings[name] = value
}
func (c rendererContext) SourceFile() string {
switch {
case c.node != nil:
return c.node.SourceLoc.Pathname
case c.cn != nil:
return c.cn.SourceLoc.Pathname
default:
return ""
}
}
func (c rendererContext) TagArgs() string {
switch {
case c.node != nil:
return c.node.Token.Args
case c.cn != nil:
return c.cn.Token.Args
default:
return ""
}
}
func (c rendererContext) TagName() string {
switch {
case c.node != nil:
return c.node.Token.Name
case c.cn != nil:
return c.cn.Token.Name
default:
return ""
}
}