forked from osteele/liquid
-
Notifications
You must be signed in to change notification settings - Fork 0
/
engine.go
141 lines (125 loc) · 4.75 KB
/
engine.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
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/main/filters/standard_filters.go
//
// * https://github.com/osteele/gojekyll/blob/master/filters/filters.go
func (e *Engine) RegisterFilter(name string, fn any) {
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
})
}
// StrictVariables causes the renderer to error when the template contains an undefined variable.
func (e *Engine) StrictVariables() {
e.cfg.StrictVariables = true
}
// 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)
}
// ParseAndFRender parses and then renders the template into w.
func (e *Engine) ParseAndFRender(w io.Writer, source []byte, b Bindings) SourceError {
tpl, err := e.ParseTemplate(source)
if err != nil {
return err
}
return tpl.FRender(w, 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(objectLeft, objectRight, tagLeft, tagRight)
return e
}
// ParseTemplateAndCache is the same as ParseTemplateLocation, except that the
// source location is used for error reporting and for the {% include %} tag.
// If parsing is successful, provided source is then cached, and can be retrieved
// by {% include %} tags, as long as there is not a real file in the provided path.
//
// 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) ParseTemplateAndCache(source []byte, path string, line int) (*Template, SourceError) {
t, err := e.ParseTemplateLocation(source, path, line)
if err != nil {
return t, err
}
e.cfg.Cache[path] = source
return t, err
}