1 |
yakumo_izuru |
1.1 |
package amber |
2 |
|
|
|
3 |
|
|
import ( |
4 |
|
|
"bytes" |
5 |
|
|
"container/list" |
6 |
|
|
"errors" |
7 |
|
|
"fmt" |
8 |
|
|
"go/ast" |
9 |
|
|
gp "go/parser" |
10 |
|
|
gt "go/token" |
11 |
|
|
"html/template" |
12 |
|
|
"io" |
13 |
|
|
"net/http" |
14 |
|
|
"os" |
15 |
|
|
"path/filepath" |
16 |
|
|
"reflect" |
17 |
|
|
"regexp" |
18 |
|
|
"sort" |
19 |
|
|
"strconv" |
20 |
|
|
"strings" |
21 |
|
|
|
22 |
|
|
"github.com/eknkc/amber/parser" |
23 |
|
|
) |
24 |
|
|
|
25 |
|
|
var builtinFunctions = [...]string{ |
26 |
|
|
"len", |
27 |
|
|
"print", |
28 |
|
|
"printf", |
29 |
|
|
"println", |
30 |
|
|
"urlquery", |
31 |
|
|
"js", |
32 |
|
|
"json", |
33 |
|
|
"index", |
34 |
|
|
"html", |
35 |
|
|
"unescaped", |
36 |
|
|
} |
37 |
|
|
|
38 |
|
|
const ( |
39 |
|
|
dollar = "__DOLLAR__" |
40 |
|
|
) |
41 |
|
|
|
42 |
|
|
// Compiler is the main interface of Amber Template Engine. |
43 |
|
|
// In order to use an Amber template, it is required to create a Compiler and |
44 |
|
|
// compile an Amber source to native Go template. |
45 |
|
|
// compiler := amber.New() |
46 |
|
|
// // Parse the input file |
47 |
|
|
// err := compiler.ParseFile("./input.amber") |
48 |
|
|
// if err == nil { |
49 |
|
|
// // Compile input file to Go template |
50 |
|
|
// tpl, err := compiler.Compile() |
51 |
|
|
// if err == nil { |
52 |
|
|
// // Check built in html/template documentation for further details |
53 |
|
|
// tpl.Execute(os.Stdout, somedata) |
54 |
|
|
// } |
55 |
|
|
// } |
56 |
|
|
type Compiler struct { |
57 |
|
|
// Compiler options |
58 |
|
|
Options |
59 |
|
|
filename string |
60 |
|
|
node parser.Node |
61 |
|
|
indentLevel int |
62 |
|
|
newline bool |
63 |
|
|
buffer *bytes.Buffer |
64 |
|
|
tempvarIndex int |
65 |
|
|
mixins map[string]*parser.Mixin |
66 |
|
|
} |
67 |
|
|
|
68 |
|
|
// New creates and initialize a new Compiler. |
69 |
|
|
func New() *Compiler { |
70 |
|
|
compiler := new(Compiler) |
71 |
|
|
compiler.filename = "" |
72 |
|
|
compiler.tempvarIndex = 0 |
73 |
|
|
compiler.PrettyPrint = true |
74 |
|
|
compiler.Options = DefaultOptions |
75 |
|
|
compiler.mixins = make(map[string]*parser.Mixin) |
76 |
|
|
|
77 |
|
|
return compiler |
78 |
|
|
} |
79 |
|
|
|
80 |
|
|
// Options defines template output behavior. |
81 |
|
|
type Options struct { |
82 |
|
|
// Setting if pretty printing is enabled. |
83 |
|
|
// Pretty printing ensures that the output html is properly indented and in human readable form. |
84 |
|
|
// If disabled, produced HTML is compact. This might be more suitable in production environments. |
85 |
|
|
// Default: true |
86 |
|
|
PrettyPrint bool |
87 |
|
|
// Setting if line number emitting is enabled |
88 |
|
|
// In this form, Amber emits line number comments in the output template. It is usable in debugging environments. |
89 |
|
|
// Default: false |
90 |
|
|
LineNumbers bool |
91 |
|
|
// Setting the virtual filesystem to use |
92 |
|
|
// If set, will attempt to use a virtual filesystem provided instead of os. |
93 |
|
|
// Default: nil |
94 |
|
|
VirtualFilesystem http.FileSystem |
95 |
|
|
} |
96 |
|
|
|
97 |
|
|
// DirOptions is used to provide options to directory compilation. |
98 |
|
|
type DirOptions struct { |
99 |
|
|
// File extension to match for compilation |
100 |
|
|
Ext string |
101 |
|
|
// Whether or not to walk subdirectories |
102 |
|
|
Recursive bool |
103 |
|
|
} |
104 |
|
|
|
105 |
|
|
// DefaultOptions sets pretty-printing to true and line numbering to false. |
106 |
|
|
var DefaultOptions = Options{true, false, nil} |
107 |
|
|
|
108 |
|
|
// DefaultDirOptions sets expected file extension to ".amber" and recursive search for templates within a directory to true. |
109 |
|
|
var DefaultDirOptions = DirOptions{".amber", true} |
110 |
|
|
|
111 |
|
|
// Compile parses and compiles the supplied amber template string. Returns corresponding Go Template (html/templates) instance. |
112 |
|
|
// Necessary runtime functions will be injected and the template will be ready to be executed. |
113 |
|
|
func Compile(input string, options Options) (*template.Template, error) { |
114 |
|
|
comp := New() |
115 |
|
|
comp.Options = options |
116 |
|
|
|
117 |
|
|
err := comp.Parse(input) |
118 |
|
|
if err != nil { |
119 |
|
|
return nil, err |
120 |
|
|
} |
121 |
|
|
|
122 |
|
|
return comp.Compile() |
123 |
|
|
} |
124 |
|
|
|
125 |
|
|
// Compile parses and compiles the supplied amber template []byte. |
126 |
|
|
// Returns corresponding Go Template (html/templates) instance. |
127 |
|
|
// Necessary runtime functions will be injected and the template will be ready to be executed. |
128 |
|
|
func CompileData(input []byte, filename string, options Options) (*template.Template, error) { |
129 |
|
|
comp := New() |
130 |
|
|
comp.Options = options |
131 |
|
|
|
132 |
|
|
err := comp.ParseData(input, filename) |
133 |
|
|
if err != nil { |
134 |
|
|
return nil, err |
135 |
|
|
} |
136 |
|
|
|
137 |
|
|
return comp.Compile() |
138 |
|
|
} |
139 |
|
|
|
140 |
|
|
// MustCompile is the same as Compile, except the input is assumed error free. If else, panic. |
141 |
|
|
func MustCompile(input string, options Options) *template.Template { |
142 |
|
|
t, err := Compile(input, options) |
143 |
|
|
if err != nil { |
144 |
|
|
panic(err) |
145 |
|
|
} |
146 |
|
|
return t |
147 |
|
|
} |
148 |
|
|
|
149 |
|
|
// CompileFile parses and compiles the contents of supplied filename. Returns corresponding Go Template (html/templates) instance. |
150 |
|
|
// Necessary runtime functions will be injected and the template will be ready to be executed. |
151 |
|
|
func CompileFile(filename string, options Options) (*template.Template, error) { |
152 |
|
|
comp := New() |
153 |
|
|
comp.Options = options |
154 |
|
|
|
155 |
|
|
err := comp.ParseFile(filename) |
156 |
|
|
if err != nil { |
157 |
|
|
return nil, err |
158 |
|
|
} |
159 |
|
|
|
160 |
|
|
return comp.Compile() |
161 |
|
|
} |
162 |
|
|
|
163 |
|
|
// MustCompileFile is the same as CompileFile, except the input is assumed error free. If else, panic. |
164 |
|
|
func MustCompileFile(filename string, options Options) *template.Template { |
165 |
|
|
t, err := CompileFile(filename, options) |
166 |
|
|
if err != nil { |
167 |
|
|
panic(err) |
168 |
|
|
} |
169 |
|
|
return t |
170 |
|
|
} |
171 |
|
|
|
172 |
|
|
// CompileDir parses and compiles the contents of a supplied directory path, with options. |
173 |
|
|
// Returns a map of a template identifier (key) to a Go Template instance. |
174 |
|
|
// Ex: if the dirname="templates/" had a file "index.amber" the key would be "index" |
175 |
|
|
// If option for recursive is True, this parses every file of relevant extension |
176 |
|
|
// in all subdirectories. The key then is the path e.g: "layouts/layout" |
177 |
|
|
func CompileDir(dirname string, dopt DirOptions, opt Options) (map[string]*template.Template, error) { |
178 |
|
|
dir, err := os.Open(dirname) |
179 |
|
|
if err != nil && opt.VirtualFilesystem != nil { |
180 |
|
|
vdir, err := opt.VirtualFilesystem.Open(dirname) |
181 |
|
|
if err != nil { |
182 |
|
|
return nil, err |
183 |
|
|
} |
184 |
|
|
dir = vdir.(*os.File) |
185 |
|
|
} else if err != nil { |
186 |
|
|
return nil, err |
187 |
|
|
} |
188 |
|
|
defer dir.Close() |
189 |
|
|
|
190 |
|
|
files, err := dir.Readdir(0) |
191 |
|
|
if err != nil { |
192 |
|
|
return nil, err |
193 |
|
|
} |
194 |
|
|
|
195 |
|
|
compiled := make(map[string]*template.Template) |
196 |
|
|
for _, file := range files { |
197 |
|
|
// filename is for example "index.amber" |
198 |
|
|
filename := file.Name() |
199 |
|
|
fileext := filepath.Ext(filename) |
200 |
|
|
|
201 |
|
|
// If recursive is true and there's a subdirectory, recurse |
202 |
|
|
if dopt.Recursive && file.IsDir() { |
203 |
|
|
dirpath := filepath.Join(dirname, filename) |
204 |
|
|
subcompiled, err := CompileDir(dirpath, dopt, opt) |
205 |
|
|
if err != nil { |
206 |
|
|
return nil, err |
207 |
|
|
} |
208 |
|
|
// Copy templates from subdirectory into parent template mapping |
209 |
|
|
for k, v := range subcompiled { |
210 |
|
|
// Concat with parent directory name for unique paths |
211 |
|
|
key := filepath.Join(filename, k) |
212 |
|
|
compiled[key] = v |
213 |
|
|
} |
214 |
|
|
} else if fileext == dopt.Ext { |
215 |
|
|
// Otherwise compile the file and add to mapping |
216 |
|
|
fullpath := filepath.Join(dirname, filename) |
217 |
|
|
tmpl, err := CompileFile(fullpath, opt) |
218 |
|
|
if err != nil { |
219 |
|
|
return nil, err |
220 |
|
|
} |
221 |
|
|
// Strip extension |
222 |
|
|
key := filename[0 : len(filename)-len(fileext)] |
223 |
|
|
compiled[key] = tmpl |
224 |
|
|
} |
225 |
|
|
} |
226 |
|
|
|
227 |
|
|
return compiled, nil |
228 |
|
|
} |
229 |
|
|
|
230 |
|
|
// MustCompileDir is the same as CompileDir, except input is assumed error free. If else, panic. |
231 |
|
|
func MustCompileDir(dirname string, dopt DirOptions, opt Options) map[string]*template.Template { |
232 |
|
|
m, err := CompileDir(dirname, dopt, opt) |
233 |
|
|
if err != nil { |
234 |
|
|
panic(err) |
235 |
|
|
} |
236 |
|
|
return m |
237 |
|
|
} |
238 |
|
|
|
239 |
|
|
// Parse given raw amber template string. |
240 |
|
|
func (c *Compiler) Parse(input string) (err error) { |
241 |
|
|
defer func() { |
242 |
|
|
if r := recover(); r != nil { |
243 |
|
|
err = errors.New(r.(string)) |
244 |
|
|
} |
245 |
|
|
}() |
246 |
|
|
|
247 |
|
|
parser, err := parser.StringParser(input) |
248 |
|
|
|
249 |
|
|
if err != nil { |
250 |
|
|
return |
251 |
|
|
} |
252 |
|
|
|
253 |
|
|
c.node = parser.Parse() |
254 |
|
|
return |
255 |
|
|
} |
256 |
|
|
|
257 |
|
|
// Parse given raw amber template bytes, and the filename that belongs with it |
258 |
|
|
func (c *Compiler) ParseData(input []byte, filename string) (err error) { |
259 |
|
|
defer func() { |
260 |
|
|
if r := recover(); r != nil { |
261 |
|
|
err = errors.New(r.(string)) |
262 |
|
|
} |
263 |
|
|
}() |
264 |
|
|
|
265 |
|
|
parser, err := parser.ByteParser(input) |
266 |
|
|
parser.SetFilename(filename) |
267 |
|
|
if c.VirtualFilesystem != nil { |
268 |
|
|
parser.SetVirtualFilesystem(c.VirtualFilesystem) |
269 |
|
|
} |
270 |
|
|
|
271 |
|
|
if err != nil { |
272 |
|
|
return |
273 |
|
|
} |
274 |
|
|
|
275 |
|
|
c.node = parser.Parse() |
276 |
|
|
return |
277 |
|
|
} |
278 |
|
|
|
279 |
|
|
// ParseFile parses the amber template file in given path. |
280 |
|
|
func (c *Compiler) ParseFile(filename string) (err error) { |
281 |
|
|
defer func() { |
282 |
|
|
if r := recover(); r != nil { |
283 |
|
|
err = errors.New(r.(string)) |
284 |
|
|
} |
285 |
|
|
}() |
286 |
|
|
|
287 |
|
|
p, err := parser.FileParser(filename) |
288 |
|
|
if err != nil && c.VirtualFilesystem != nil { |
289 |
|
|
p, err = parser.VirtualFileParser(filename, c.VirtualFilesystem) |
290 |
|
|
} |
291 |
|
|
if err != nil { |
292 |
|
|
return |
293 |
|
|
} |
294 |
|
|
|
295 |
|
|
c.node = p.Parse() |
296 |
|
|
c.filename = filename |
297 |
|
|
return |
298 |
|
|
} |
299 |
|
|
|
300 |
|
|
// Compile amber and create a Go Template (html/templates) instance. |
301 |
|
|
// Necessary runtime functions will be injected and the template will be ready to be executed. |
302 |
|
|
func (c *Compiler) Compile() (*template.Template, error) { |
303 |
|
|
return c.CompileWithName(filepath.Base(c.filename)) |
304 |
|
|
} |
305 |
|
|
|
306 |
|
|
// CompileWithName is the same as Compile, but allows to specify a name for the template. |
307 |
|
|
func (c *Compiler) CompileWithName(name string) (*template.Template, error) { |
308 |
|
|
return c.CompileWithTemplate(template.New(name)) |
309 |
|
|
} |
310 |
|
|
|
311 |
|
|
// CompileWithTemplate is the same as Compile but allows to specify a template. |
312 |
|
|
func (c *Compiler) CompileWithTemplate(t *template.Template) (*template.Template, error) { |
313 |
|
|
data, err := c.CompileString() |
314 |
|
|
|
315 |
|
|
if err != nil { |
316 |
|
|
return nil, err |
317 |
|
|
} |
318 |
|
|
|
319 |
|
|
tpl, err := t.Funcs(FuncMap).Parse(data) |
320 |
|
|
|
321 |
|
|
if err != nil { |
322 |
|
|
return nil, err |
323 |
|
|
} |
324 |
|
|
|
325 |
|
|
return tpl, nil |
326 |
|
|
} |
327 |
|
|
|
328 |
|
|
// CompileWriter compiles amber and writes the Go Template source into given io.Writer instance. |
329 |
|
|
// You would not be using this unless debugging / checking the output. Please use Compile |
330 |
|
|
// method to obtain a template instance directly. |
331 |
|
|
func (c *Compiler) CompileWriter(out io.Writer) (err error) { |
332 |
|
|
defer func() { |
333 |
|
|
if r := recover(); r != nil { |
334 |
|
|
err = errors.New(r.(string)) |
335 |
|
|
} |
336 |
|
|
}() |
337 |
|
|
|
338 |
|
|
c.buffer = new(bytes.Buffer) |
339 |
|
|
c.visit(c.node) |
340 |
|
|
|
341 |
|
|
if c.buffer.Len() > 0 { |
342 |
|
|
c.write("\n") |
343 |
|
|
} |
344 |
|
|
|
345 |
|
|
_, err = c.buffer.WriteTo(out) |
346 |
|
|
return |
347 |
|
|
} |
348 |
|
|
|
349 |
|
|
// CompileString compiles the template and returns the Go Template source. |
350 |
|
|
// You would not be using this unless debugging / checking the output. Please use Compile |
351 |
|
|
// method to obtain a template instance directly. |
352 |
|
|
func (c *Compiler) CompileString() (string, error) { |
353 |
|
|
var buf bytes.Buffer |
354 |
|
|
|
355 |
|
|
if err := c.CompileWriter(&buf); err != nil { |
356 |
|
|
return "", err |
357 |
|
|
} |
358 |
|
|
|
359 |
|
|
result := buf.String() |
360 |
|
|
|
361 |
|
|
return result, nil |
362 |
|
|
} |
363 |
|
|
|
364 |
|
|
func (c *Compiler) visit(node parser.Node) { |
365 |
|
|
defer func() { |
366 |
|
|
if r := recover(); r != nil { |
367 |
|
|
if rs, ok := r.(string); ok && rs[:len("Amber Error")] == "Amber Error" { |
368 |
|
|
panic(r) |
369 |
|
|
} |
370 |
|
|
|
371 |
|
|
pos := node.Pos() |
372 |
|
|
|
373 |
|
|
if len(pos.Filename) > 0 { |
374 |
|
|
panic(fmt.Sprintf("Amber Error in <%s>: %v - Line: %d, Column: %d, Length: %d", pos.Filename, r, pos.LineNum, pos.ColNum, pos.TokenLength)) |
375 |
|
|
} else { |
376 |
|
|
panic(fmt.Sprintf("Amber Error: %v - Line: %d, Column: %d, Length: %d", r, pos.LineNum, pos.ColNum, pos.TokenLength)) |
377 |
|
|
} |
378 |
|
|
} |
379 |
|
|
}() |
380 |
|
|
|
381 |
|
|
switch node.(type) { |
382 |
|
|
case *parser.Block: |
383 |
|
|
c.visitBlock(node.(*parser.Block)) |
384 |
|
|
case *parser.Doctype: |
385 |
|
|
c.visitDoctype(node.(*parser.Doctype)) |
386 |
|
|
case *parser.Comment: |
387 |
|
|
c.visitComment(node.(*parser.Comment)) |
388 |
|
|
case *parser.Tag: |
389 |
|
|
c.visitTag(node.(*parser.Tag)) |
390 |
|
|
case *parser.Text: |
391 |
|
|
c.visitText(node.(*parser.Text)) |
392 |
|
|
case *parser.Condition: |
393 |
|
|
c.visitCondition(node.(*parser.Condition)) |
394 |
|
|
case *parser.Each: |
395 |
|
|
c.visitEach(node.(*parser.Each)) |
396 |
|
|
case *parser.Assignment: |
397 |
|
|
c.visitAssignment(node.(*parser.Assignment)) |
398 |
|
|
case *parser.Mixin: |
399 |
|
|
c.visitMixin(node.(*parser.Mixin)) |
400 |
|
|
case *parser.MixinCall: |
401 |
|
|
c.visitMixinCall(node.(*parser.MixinCall)) |
402 |
|
|
} |
403 |
|
|
} |
404 |
|
|
|
405 |
|
|
func (c *Compiler) write(value string) { |
406 |
|
|
c.buffer.WriteString(value) |
407 |
|
|
} |
408 |
|
|
|
409 |
|
|
func (c *Compiler) indent(offset int, newline bool) { |
410 |
|
|
if !c.PrettyPrint { |
411 |
|
|
return |
412 |
|
|
} |
413 |
|
|
|
414 |
|
|
if newline && c.buffer.Len() > 0 { |
415 |
|
|
c.write("\n") |
416 |
|
|
} |
417 |
|
|
|
418 |
|
|
for i := 0; i < c.indentLevel+offset; i++ { |
419 |
|
|
c.write("\t") |
420 |
|
|
} |
421 |
|
|
} |
422 |
|
|
|
423 |
|
|
func (c *Compiler) tempvar() string { |
424 |
|
|
c.tempvarIndex++ |
425 |
|
|
return "$__amber_" + strconv.Itoa(c.tempvarIndex) |
426 |
|
|
} |
427 |
|
|
|
428 |
|
|
func (c *Compiler) escape(input string) string { |
429 |
|
|
return strings.Replace(strings.Replace(input, `\`, `\\`, -1), `"`, `\"`, -1) |
430 |
|
|
} |
431 |
|
|
|
432 |
|
|
func (c *Compiler) visitBlock(block *parser.Block) { |
433 |
|
|
for _, node := range block.Children { |
434 |
|
|
if _, ok := node.(*parser.Text); !block.CanInline() && ok { |
435 |
|
|
c.indent(0, true) |
436 |
|
|
} |
437 |
|
|
|
438 |
|
|
c.visit(node) |
439 |
|
|
} |
440 |
|
|
} |
441 |
|
|
|
442 |
|
|
func (c *Compiler) visitDoctype(doctype *parser.Doctype) { |
443 |
|
|
c.write(doctype.String()) |
444 |
|
|
} |
445 |
|
|
|
446 |
|
|
func (c *Compiler) visitComment(comment *parser.Comment) { |
447 |
|
|
if comment.Silent { |
448 |
|
|
return |
449 |
|
|
} |
450 |
|
|
|
451 |
|
|
c.indent(0, false) |
452 |
|
|
|
453 |
|
|
if comment.Block == nil { |
454 |
|
|
c.write(`{{unescaped "<!-- ` + c.escape(comment.Value) + ` -->"}}`) |
455 |
|
|
} else { |
456 |
|
|
c.write(`<!-- ` + comment.Value) |
457 |
|
|
c.visitBlock(comment.Block) |
458 |
|
|
c.write(` -->`) |
459 |
|
|
} |
460 |
|
|
} |
461 |
|
|
|
462 |
|
|
func (c *Compiler) visitCondition(condition *parser.Condition) { |
463 |
|
|
c.write(`{{if ` + c.visitRawInterpolation(condition.Expression) + `}}`) |
464 |
|
|
c.visitBlock(condition.Positive) |
465 |
|
|
if condition.Negative != nil { |
466 |
|
|
c.write(`{{else}}`) |
467 |
|
|
c.visitBlock(condition.Negative) |
468 |
|
|
} |
469 |
|
|
c.write(`{{end}}`) |
470 |
|
|
} |
471 |
|
|
|
472 |
|
|
func (c *Compiler) visitEach(each *parser.Each) { |
473 |
|
|
if each.Block == nil { |
474 |
|
|
return |
475 |
|
|
} |
476 |
|
|
|
477 |
|
|
if len(each.Y) == 0 { |
478 |
|
|
c.write(`{{range ` + each.X + ` := ` + c.visitRawInterpolation(each.Expression) + `}}`) |
479 |
|
|
} else { |
480 |
|
|
c.write(`{{range ` + each.X + `, ` + each.Y + ` := ` + c.visitRawInterpolation(each.Expression) + `}}`) |
481 |
|
|
} |
482 |
|
|
c.visitBlock(each.Block) |
483 |
|
|
c.write(`{{end}}`) |
484 |
|
|
} |
485 |
|
|
|
486 |
|
|
func (c *Compiler) visitAssignment(assgn *parser.Assignment) { |
487 |
|
|
c.write(`{{` + assgn.X + ` := ` + c.visitRawInterpolation(assgn.Expression) + `}}`) |
488 |
|
|
} |
489 |
|
|
|
490 |
|
|
func (c *Compiler) visitTag(tag *parser.Tag) { |
491 |
|
|
type attrib struct { |
492 |
|
|
name string |
493 |
|
|
value func() string |
494 |
|
|
condition string |
495 |
|
|
} |
496 |
|
|
|
497 |
|
|
attribs := make(map[string]*attrib) |
498 |
|
|
|
499 |
|
|
for _, item := range tag.Attributes { |
500 |
|
|
attritem := item |
501 |
|
|
attr := new(attrib) |
502 |
|
|
attr.name = item.Name |
503 |
|
|
|
504 |
|
|
attr.value = func() string { |
505 |
|
|
if !attritem.IsRaw { |
506 |
|
|
return c.visitInterpolation(attritem.Value) |
507 |
|
|
} else if attritem.Value == "" { |
508 |
|
|
return "" |
509 |
|
|
} else { |
510 |
|
|
return attritem.Value |
511 |
|
|
} |
512 |
|
|
} |
513 |
|
|
|
514 |
|
|
if len(attritem.Condition) != 0 { |
515 |
|
|
attr.condition = c.visitRawInterpolation(attritem.Condition) |
516 |
|
|
} |
517 |
|
|
|
518 |
|
|
if attr.name == "class" && attribs["class"] != nil { |
519 |
|
|
prevclass := attribs["class"] |
520 |
|
|
prevvalue := prevclass.value |
521 |
|
|
|
522 |
|
|
prevclass.value = func() string { |
523 |
|
|
aval := attr.value() |
524 |
|
|
|
525 |
|
|
if len(attr.condition) > 0 { |
526 |
|
|
aval = `{{if ` + attr.condition + `}}` + aval + `{{end}}` |
527 |
|
|
} |
528 |
|
|
|
529 |
|
|
if len(prevclass.condition) > 0 { |
530 |
|
|
return `{{if ` + prevclass.condition + `}}` + prevvalue() + `{{end}} ` + aval |
531 |
|
|
} |
532 |
|
|
|
533 |
|
|
return prevvalue() + " " + aval |
534 |
|
|
} |
535 |
|
|
} else { |
536 |
|
|
attribs[attritem.Name] = attr |
537 |
|
|
} |
538 |
|
|
} |
539 |
|
|
|
540 |
|
|
keys := make([]string, 0, len(attribs)) |
541 |
|
|
for key := range attribs { |
542 |
|
|
keys = append(keys, key) |
543 |
|
|
} |
544 |
|
|
sort.Strings(keys) |
545 |
|
|
|
546 |
|
|
c.indent(0, true) |
547 |
|
|
c.write("<" + tag.Name) |
548 |
|
|
|
549 |
|
|
for _, name := range keys { |
550 |
|
|
value := attribs[name] |
551 |
|
|
|
552 |
|
|
if len(value.condition) > 0 { |
553 |
|
|
c.write(`{{if ` + value.condition + `}}`) |
554 |
|
|
} |
555 |
|
|
|
556 |
|
|
val := value.value() |
557 |
|
|
|
558 |
|
|
if val == "" { |
559 |
|
|
c.write(` ` + name) |
560 |
|
|
} else { |
561 |
|
|
c.write(` ` + name + `="` + val + `"`) |
562 |
|
|
} |
563 |
|
|
|
564 |
|
|
if len(value.condition) > 0 { |
565 |
|
|
c.write(`{{end}}`) |
566 |
|
|
} |
567 |
|
|
} |
568 |
|
|
|
569 |
|
|
if tag.IsSelfClosing() { |
570 |
|
|
c.write(` />`) |
571 |
|
|
} else { |
572 |
|
|
c.write(`>`) |
573 |
|
|
|
574 |
|
|
if tag.Block != nil { |
575 |
|
|
if !tag.Block.CanInline() { |
576 |
|
|
c.indentLevel++ |
577 |
|
|
} |
578 |
|
|
|
579 |
|
|
c.visitBlock(tag.Block) |
580 |
|
|
|
581 |
|
|
if !tag.Block.CanInline() { |
582 |
|
|
c.indentLevel-- |
583 |
|
|
c.indent(0, true) |
584 |
|
|
} |
585 |
|
|
} |
586 |
|
|
|
587 |
|
|
c.write(`</` + tag.Name + `>`) |
588 |
|
|
} |
589 |
|
|
} |
590 |
|
|
|
591 |
|
|
var textInterpolateRegexp = regexp.MustCompile(`#\{(.*?)\}`) |
592 |
|
|
var textEscapeRegexp = regexp.MustCompile(`\{\{(.*?)\}\}`) |
593 |
|
|
|
594 |
|
|
func (c *Compiler) visitText(txt *parser.Text) { |
595 |
|
|
value := textEscapeRegexp.ReplaceAllStringFunc(txt.Value, func(value string) string { |
596 |
|
|
return `{{"{{"}}` + value[2:len(value)-2] + `{{"}}"}}` |
597 |
|
|
}) |
598 |
|
|
|
599 |
|
|
value = textInterpolateRegexp.ReplaceAllStringFunc(value, func(value string) string { |
600 |
|
|
return c.visitInterpolation(value[2 : len(value)-1]) |
601 |
|
|
}) |
602 |
|
|
|
603 |
|
|
lines := strings.Split(value, "\n") |
604 |
|
|
for i := 0; i < len(lines); i++ { |
605 |
|
|
c.write(lines[i]) |
606 |
|
|
|
607 |
|
|
if i < len(lines)-1 { |
608 |
|
|
c.write("\n") |
609 |
|
|
c.indent(0, false) |
610 |
|
|
} |
611 |
|
|
} |
612 |
|
|
} |
613 |
|
|
|
614 |
|
|
func (c *Compiler) visitInterpolation(value string) string { |
615 |
|
|
return `{{` + c.visitRawInterpolation(value) + `}}` |
616 |
|
|
} |
617 |
|
|
|
618 |
|
|
func (c *Compiler) visitRawInterpolation(value string) string { |
619 |
|
|
if value == "" { |
620 |
|
|
value = "\"\"" |
621 |
|
|
} |
622 |
|
|
|
623 |
|
|
value = strings.Replace(value, "$", dollar, -1) |
624 |
|
|
expr, err := gp.ParseExpr(value) |
625 |
|
|
if err != nil { |
626 |
|
|
panic("Unable to parse expression.") |
627 |
|
|
} |
628 |
|
|
value = strings.Replace(c.visitExpression(expr), dollar, "$", -1) |
629 |
|
|
return value |
630 |
|
|
} |
631 |
|
|
|
632 |
|
|
func (c *Compiler) visitExpression(outerexpr ast.Expr) string { |
633 |
|
|
stack := list.New() |
634 |
|
|
|
635 |
|
|
pop := func() string { |
636 |
|
|
if stack.Front() == nil { |
637 |
|
|
return "" |
638 |
|
|
} |
639 |
|
|
|
640 |
|
|
val := stack.Front().Value.(string) |
641 |
|
|
stack.Remove(stack.Front()) |
642 |
|
|
return val |
643 |
|
|
} |
644 |
|
|
|
645 |
|
|
var exec func(ast.Expr) |
646 |
|
|
|
647 |
|
|
exec = func(expr ast.Expr) { |
648 |
|
|
switch expr := expr.(type) { |
649 |
|
|
case *ast.BinaryExpr: |
650 |
|
|
{ |
651 |
|
|
be := expr |
652 |
|
|
|
653 |
|
|
exec(be.Y) |
654 |
|
|
exec(be.X) |
655 |
|
|
|
656 |
|
|
negate := false |
657 |
|
|
name := c.tempvar() |
658 |
|
|
c.write(`{{` + name + ` := `) |
659 |
|
|
|
660 |
|
|
switch be.Op { |
661 |
|
|
case gt.ADD: |
662 |
|
|
c.write("__amber_add ") |
663 |
|
|
case gt.SUB: |
664 |
|
|
c.write("__amber_sub ") |
665 |
|
|
case gt.MUL: |
666 |
|
|
c.write("__amber_mul ") |
667 |
|
|
case gt.QUO: |
668 |
|
|
c.write("__amber_quo ") |
669 |
|
|
case gt.REM: |
670 |
|
|
c.write("__amber_rem ") |
671 |
|
|
case gt.LAND: |
672 |
|
|
c.write("and ") |
673 |
|
|
case gt.LOR: |
674 |
|
|
c.write("or ") |
675 |
|
|
case gt.EQL: |
676 |
|
|
c.write("__amber_eql ") |
677 |
|
|
case gt.NEQ: |
678 |
|
|
c.write("__amber_eql ") |
679 |
|
|
negate = true |
680 |
|
|
case gt.LSS: |
681 |
|
|
c.write("__amber_lss ") |
682 |
|
|
case gt.GTR: |
683 |
|
|
c.write("__amber_gtr ") |
684 |
|
|
case gt.LEQ: |
685 |
|
|
c.write("__amber_gtr ") |
686 |
|
|
negate = true |
687 |
|
|
case gt.GEQ: |
688 |
|
|
c.write("__amber_lss ") |
689 |
|
|
negate = true |
690 |
|
|
default: |
691 |
|
|
panic("Unexpected operator!") |
692 |
|
|
} |
693 |
|
|
|
694 |
|
|
c.write(pop() + ` ` + pop() + `}}`) |
695 |
|
|
|
696 |
|
|
if !negate { |
697 |
|
|
stack.PushFront(name) |
698 |
|
|
} else { |
699 |
|
|
negname := c.tempvar() |
700 |
|
|
c.write(`{{` + negname + ` := not ` + name + `}}`) |
701 |
|
|
stack.PushFront(negname) |
702 |
|
|
} |
703 |
|
|
} |
704 |
|
|
case *ast.UnaryExpr: |
705 |
|
|
{ |
706 |
|
|
ue := expr |
707 |
|
|
|
708 |
|
|
exec(ue.X) |
709 |
|
|
|
710 |
|
|
name := c.tempvar() |
711 |
|
|
c.write(`{{` + name + ` := `) |
712 |
|
|
|
713 |
|
|
switch ue.Op { |
714 |
|
|
case gt.SUB: |
715 |
|
|
c.write("__amber_minus ") |
716 |
|
|
case gt.ADD: |
717 |
|
|
c.write("__amber_plus ") |
718 |
|
|
case gt.NOT: |
719 |
|
|
c.write("not ") |
720 |
|
|
default: |
721 |
|
|
panic("Unexpected operator!") |
722 |
|
|
} |
723 |
|
|
|
724 |
|
|
c.write(pop() + `}}`) |
725 |
|
|
stack.PushFront(name) |
726 |
|
|
} |
727 |
|
|
case *ast.ParenExpr: |
728 |
|
|
exec(expr.X) |
729 |
|
|
case *ast.BasicLit: |
730 |
|
|
stack.PushFront(strings.Replace(expr.Value, dollar, "$", -1)) |
731 |
|
|
case *ast.Ident: |
732 |
|
|
name := expr.Name |
733 |
|
|
if len(name) >= len(dollar) && name[:len(dollar)] == dollar { |
734 |
|
|
if name == dollar { |
735 |
|
|
stack.PushFront(`.`) |
736 |
|
|
} else { |
737 |
|
|
stack.PushFront(`$` + expr.Name[len(dollar):]) |
738 |
|
|
} |
739 |
|
|
} else { |
740 |
|
|
stack.PushFront(`.` + expr.Name) |
741 |
|
|
} |
742 |
|
|
case *ast.SelectorExpr: |
743 |
|
|
se := expr |
744 |
|
|
exec(se.X) |
745 |
|
|
x := pop() |
746 |
|
|
|
747 |
|
|
if x == "." { |
748 |
|
|
x = "" |
749 |
|
|
} |
750 |
|
|
|
751 |
|
|
name := c.tempvar() |
752 |
|
|
c.write(`{{` + name + ` := ` + x + `.` + se.Sel.Name + `}}`) |
753 |
|
|
stack.PushFront(name) |
754 |
|
|
case *ast.CallExpr: |
755 |
|
|
ce := expr |
756 |
|
|
|
757 |
|
|
for i := len(ce.Args) - 1; i >= 0; i-- { |
758 |
|
|
exec(ce.Args[i]) |
759 |
|
|
} |
760 |
|
|
|
761 |
|
|
name := c.tempvar() |
762 |
|
|
builtin := false |
763 |
|
|
|
764 |
|
|
if ident, ok := ce.Fun.(*ast.Ident); ok { |
765 |
|
|
for _, fname := range builtinFunctions { |
766 |
|
|
if fname == ident.Name { |
767 |
|
|
builtin = true |
768 |
|
|
break |
769 |
|
|
} |
770 |
|
|
} |
771 |
|
|
for fname, _ := range FuncMap { |
772 |
|
|
if fname == ident.Name { |
773 |
|
|
builtin = true |
774 |
|
|
break |
775 |
|
|
} |
776 |
|
|
} |
777 |
|
|
} |
778 |
|
|
|
779 |
|
|
if builtin { |
780 |
|
|
stack.PushFront(ce.Fun.(*ast.Ident).Name) |
781 |
|
|
c.write(`{{` + name + ` := ` + pop()) |
782 |
|
|
} else if se, ok := ce.Fun.(*ast.SelectorExpr); ok { |
783 |
|
|
exec(se.X) |
784 |
|
|
x := pop() |
785 |
|
|
|
786 |
|
|
if x == "." { |
787 |
|
|
x = "" |
788 |
|
|
} |
789 |
|
|
stack.PushFront(se.Sel.Name) |
790 |
|
|
c.write(`{{` + name + ` := ` + x + `.` + pop()) |
791 |
|
|
} else { |
792 |
|
|
exec(ce.Fun) |
793 |
|
|
c.write(`{{` + name + ` := call ` + pop()) |
794 |
|
|
} |
795 |
|
|
|
796 |
|
|
for i := 0; i < len(ce.Args); i++ { |
797 |
|
|
c.write(` `) |
798 |
|
|
c.write(pop()) |
799 |
|
|
} |
800 |
|
|
|
801 |
|
|
c.write(`}}`) |
802 |
|
|
|
803 |
|
|
stack.PushFront(name) |
804 |
|
|
default: |
805 |
|
|
panic("Unable to parse expression. Unsupported: " + reflect.TypeOf(expr).String()) |
806 |
|
|
} |
807 |
|
|
} |
808 |
|
|
|
809 |
|
|
exec(outerexpr) |
810 |
|
|
return pop() |
811 |
|
|
} |
812 |
|
|
|
813 |
|
|
func (c *Compiler) visitMixin(mixin *parser.Mixin) { |
814 |
|
|
c.mixins[mixin.Name] = mixin |
815 |
|
|
} |
816 |
|
|
|
817 |
|
|
func (c *Compiler) visitMixinCall(mixinCall *parser.MixinCall) { |
818 |
|
|
mixin := c.mixins[mixinCall.Name] |
819 |
|
|
|
820 |
|
|
switch { |
821 |
|
|
case mixin == nil: |
822 |
|
|
panic(fmt.Sprintf("unknown mixin %q", mixinCall.Name)) |
823 |
|
|
|
824 |
|
|
case len(mixinCall.Args) < len(mixin.Args): |
825 |
|
|
panic(fmt.Sprintf( |
826 |
|
|
"not enough arguments in call to mixin %q (have: %d, want: %d)", |
827 |
|
|
mixinCall.Name, |
828 |
|
|
len(mixinCall.Args), |
829 |
|
|
len(mixin.Args), |
830 |
|
|
)) |
831 |
|
|
case len(mixinCall.Args) > len(mixin.Args): |
832 |
|
|
panic(fmt.Sprintf( |
833 |
|
|
"too many arguments in call to mixin %q (have: %d, want: %d)", |
834 |
|
|
mixinCall.Name, |
835 |
|
|
len(mixinCall.Args), |
836 |
|
|
len(mixin.Args), |
837 |
|
|
)) |
838 |
|
|
} |
839 |
|
|
|
840 |
|
|
for i, arg := range mixin.Args { |
841 |
|
|
c.write(fmt.Sprintf(`{{%s := %s}}`, arg, c.visitRawInterpolation(mixinCall.Args[i]))) |
842 |
|
|
} |
843 |
|
|
c.visitBlock(mixin.Block) |
844 |
|
|
} |