You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

782 lines
18 KiB

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