1 package exec
2
3 import (
4 "strings"
5
6 "github.com/pkg/errors"
7
8 "github.com/noirbizarre/gonja/nodes"
9 )
10
11
12 type TrimState struct {
13 Should bool
14 ShouldBlock bool
15 Buffer *strings.Builder
16 }
17
18 func (ts *TrimState) TrimBlocks(r rune) bool {
19 if ts.ShouldBlock {
20 switch r {
21 case '\n':
22 ts.ShouldBlock = false
23 return true
24 case ' ', '\t':
25 return true
26 default:
27 return false
28 }
29 }
30 return false
31 }
32
33
34 type Renderer struct {
35 *EvalConfig
36 Ctx *Context
37 Template *Template
38 Root *nodes.Template
39 Out *strings.Builder
40 Trim *TrimState
41 }
42
43
44 func NewRenderer(ctx *Context, out *strings.Builder, cfg *EvalConfig, tpl *Template) *Renderer {
45 var buffer strings.Builder
46 r := &Renderer{
47 EvalConfig: cfg,
48 Ctx: ctx,
49 Template: tpl,
50 Root: tpl.Root,
51 Out: out,
52 Trim: &TrimState{Buffer: &buffer},
53 }
54 r.Ctx.Set("self", Self(r))
55 return r
56 }
57
58
59 func (r *Renderer) Inherit() *Renderer {
60 sub := &Renderer{
61 EvalConfig: r.EvalConfig.Inherit(),
62 Ctx: r.Ctx.Inherit(),
63 Template: r.Template,
64 Root: r.Root,
65 Out: r.Out,
66 Trim: r.Trim,
67 }
68 return sub
69 }
70
71 func (r *Renderer) Flush(lstrip bool) {
72 r.FlushAndTrim(false, lstrip)
73 }
74
75 func (r *Renderer) FlushAndTrim(trim, lstrip bool) {
76 txt := r.Trim.Buffer.String()
77 if r.Config.LstripBlocks && !lstrip {
78 lines := strings.Split(txt, "\n")
79 last := lines[len(lines)-1]
80 lines[len(lines)-1] = strings.TrimLeft(last, " \t")
81 txt = strings.Join(lines, "\n")
82 }
83 if trim {
84 txt = strings.TrimRight(txt, " \t\n")
85 }
86 r.Out.WriteString(txt)
87 r.Trim.Buffer.Reset()
88 }
89
90
91 func (r *Renderer) WriteString(txt string) (int, error) {
92 if r.Config.TrimBlocks {
93 txt = strings.TrimLeftFunc(txt, r.Trim.TrimBlocks)
94 }
95 if r.Trim.Should {
96 txt = strings.TrimLeft(txt, " \t\n")
97 if len(txt) > 0 {
98 r.Trim.Should = false
99 }
100 }
101 return r.Trim.Buffer.WriteString(txt)
102 }
103
104
105 func (r *Renderer) RenderValue(value *Value) {
106 if r.Autoescape && value.IsString() && !value.Safe {
107 r.WriteString(value.Escaped())
108 } else {
109 r.WriteString(value.String())
110 }
111 }
112
113 func (r *Renderer) StartTag(trim *nodes.Trim, lstrip bool) {
114 if trim == nil {
115 r.Flush(lstrip)
116 } else {
117 r.FlushAndTrim(trim.Left, lstrip)
118 }
119 r.Trim.Should = false
120 }
121
122 func (r *Renderer) EndTag(trim *nodes.Trim) {
123 if trim == nil {
124 return
125 }
126 r.Trim.Should = trim.Right
127 }
128
129 func (r *Renderer) Tag(trim *nodes.Trim, lstrip bool) {
130 r.StartTag(trim, lstrip)
131 r.EndTag(trim)
132 }
133
134
135 func (r *Renderer) Visit(node nodes.Node) (nodes.Visitor, error) {
136 switch n := node.(type) {
137 case *nodes.Comment:
138 r.Tag(n.Trim, false)
139 return nil, nil
140 case *nodes.Data:
141 r.WriteString(n.Data.Val)
142 return nil, nil
143 case *nodes.Output:
144 r.StartTag(n.Trim, false)
145 value := r.Eval(n.Expression)
146 if value.IsError() {
147 return nil, errors.Wrapf(value, `Unable to render expression '%s'`, n.Expression)
148 }
149 r.RenderValue(value)
150 r.EndTag(n.Trim)
151 return nil, nil
152 case *nodes.StatementBlock:
153 r.Tag(n.Trim, n.LStrip)
154 r.Trim.ShouldBlock = r.Config.TrimBlocks
155 stmt, ok := n.Stmt.(Statement)
156 if ok {
157
158
159
160 if err := stmt.Execute(r, n); err != nil {
161 return nil, errors.Wrapf(err, `Unable to execute statement '%s'`, n.Stmt)
162 }
163 }
164 return nil, nil
165 default:
166 return r, nil
167 }
168 }
169
170
171 func (r *Renderer) ExecuteWrapper(wrapper *nodes.Wrapper) error {
172 sub := r.Inherit()
173 err := nodes.Walk(sub, wrapper)
174 sub.Tag(wrapper.Trim, wrapper.LStrip)
175 r.Trim.ShouldBlock = r.Config.TrimBlocks
176 return err
177 }
178
179 func (r *Renderer) LStrip() {
180 }
181
182 func (r *Renderer) Execute() error {
183
184 root := r.Root
185 for root.Parent != nil {
186 root = root.Parent
187 }
188
189 err := nodes.Walk(r, root)
190 if err == nil {
191 r.Flush(false)
192 }
193 return err
194 }
195
196 func (r *Renderer) String() string {
197 r.Flush(false)
198 out := r.Out.String()
199 if !r.Config.KeepTrailingNewline {
200 out = strings.TrimSuffix(out, "\n")
201 }
202 return out
203 }
204
View as plain text