-
Notifications
You must be signed in to change notification settings - Fork 13
/
template.go
145 lines (131 loc) · 3.3 KB
/
template.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
// liquid template parser
package liquid
import (
"crypto/sha1"
"fmt"
"github.com/karlseguin/liquid/core"
"io"
"io/ioutil"
)
// A compiled liquid template
type Template struct {
Code []core.Code
}
func (t *Template) AddCode(code core.Code) {
t.Code = append(t.Code, code)
}
func (t *Template) AddSibling(tag core.Tag) error {
return nil
}
func (t *Template) Type() core.TagType {
return core.ContainerTag
}
func (t *Template) Name() string {
return "root"
}
// Parse the bytes into a Liquid template
func Parse(data []byte, config *core.Configuration) (*Template, error) {
if config == nil {
config = defaultConfig
}
cache := config.GetCache()
if cache == nil {
return buildTemplate(data, config)
}
hasher := sha1.New()
hasher.Write(data)
key := fmt.Sprintf("%x", hasher.Sum(nil))
template := cache.Get(key)
if template == nil {
var err error
template, err = buildTemplate(data, config)
if err != nil {
return nil, err
}
cache.Set(key, template)
}
return template.(*Template), nil
}
// Parse the string into a liquid template
func ParseString(data string, config *core.Configuration) (*Template, error) {
return Parse([]byte(data), config)
}
// Turn the contents of the specified file into a liquid template
func ParseFile(path string, config *core.Configuration) (*Template, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return Parse(data, config)
}
func (t *Template) Render(writer io.Writer, data map[string]interface{}) {
if data == nil {
data = make(map[string]interface{})
}
t.Execute(writer, data)
}
func (t *Template) Execute(writer io.Writer, data map[string]interface{}) core.ExecuteState {
for _, code := range t.Code {
if state := code.Execute(writer, data); state == core.Break {
return core.Normal
}
}
return core.Normal
}
func buildTemplate(data []byte, config *core.Configuration) (*Template, error) {
parser := core.NewParser(data)
template := new(Template)
if err := extractTokens(parser, template, config); err != nil {
return nil, err
}
return template, nil
}
func extractTokens(parser *core.Parser, container core.Tag, config *core.Configuration) error {
stack := []core.Tag{container}
preserveWhiteSpace := config.GetPreserveWhitespace()
for parser.HasMore() {
pre, markupType := parser.ToMarkup(preserveWhiteSpace)
if len(pre) > 0 {
container.AddCode(newLiteral(pre))
}
if markupType == core.OutputMarkup {
code, err := newOutput(parser)
if err != nil {
return err
}
if code != nil {
container.AddCode(code)
}
} else if markupType == core.TagMarkup {
tag, err := newTag(parser, config)
if err != nil {
return err
}
switch tag.Type() {
case core.ContainerTag, core.LoopTag:
container.AddCode(tag)
container = tag
stack = append(stack, container)
case core.EndTag:
l := len(stack) - 1
container = stack[l]
if tag.Name() != container.Name() {
return parser.Error(fmt.Sprintf("end tag \"end%s\" cannot terminate %q", tag.Name(), container.Name()))
}
stack = stack[0:l]
container = stack[l-1]
parser.SkipPastTag()
case core.SiblingTag:
if err := stack[len(stack)-1].AddSibling(tag); err != nil {
return err
}
container = tag
case core.StandaloneTag:
container.AddCode(tag)
}
} else {
break
}
}
return nil
}