amber.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. package view
  2. import (
  3. "fmt"
  4. "html/template"
  5. "io"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "sync"
  10. "github.com/eknkc/amber"
  11. )
  12. // AmberEngine contains the amber view engine structure.
  13. type AmberEngine struct {
  14. // files configuration
  15. directory string
  16. extension string
  17. assetFn func(name string) ([]byte, error) // for embedded, in combination with directory & extension
  18. namesFn func() []string // for embedded, in combination with directory & extension
  19. reload bool
  20. //
  21. rmu sync.RWMutex // locks for `ExecuteWiter` when `reload` is true.
  22. funcs map[string]interface{}
  23. templateCache map[string]*template.Template
  24. }
  25. var (
  26. _ Engine = (*AmberEngine)(nil)
  27. _ EngineFuncer = (*AmberEngine)(nil)
  28. )
  29. // Amber creates and returns a new amber view engine.
  30. // The given "extension" MUST begin with a dot.
  31. func Amber(directory, extension string) *AmberEngine {
  32. s := &AmberEngine{
  33. directory: directory,
  34. extension: extension,
  35. templateCache: make(map[string]*template.Template),
  36. funcs: make(map[string]interface{}),
  37. }
  38. return s
  39. }
  40. // Ext returns the file extension which this view engine is responsible to render.
  41. func (s *AmberEngine) Ext() string {
  42. return s.extension
  43. }
  44. // Binary optionally, use it when template files are distributed
  45. // inside the app executable (.go generated files).
  46. //
  47. // The assetFn and namesFn can come from the go-bindata library.
  48. func (s *AmberEngine) Binary(assetFn func(name string) ([]byte, error), namesFn func() []string) *AmberEngine {
  49. s.assetFn, s.namesFn = assetFn, namesFn
  50. return s
  51. }
  52. // Reload if set to true the templates are reloading on each render,
  53. // use it when you're in development and you're boring of restarting
  54. // the whole app when you edit a template file.
  55. //
  56. // Note that if `true` is passed then only one `View -> ExecuteWriter` will be render each time,
  57. // no concurrent access across clients, use it only on development status.
  58. // It's good to be used side by side with the https://github.com/kataras/rizla reloader for go source files.
  59. func (s *AmberEngine) Reload(developmentMode bool) *AmberEngine {
  60. s.reload = developmentMode
  61. return s
  62. }
  63. // AddFunc adds the function to the template's function map.
  64. // It is legal to overwrite elements of the default actions:
  65. // - url func(routeName string, args ...string) string
  66. // - urlpath func(routeName string, args ...string) string
  67. // - render func(fullPartialName string) (template.HTML, error).
  68. func (s *AmberEngine) AddFunc(funcName string, funcBody interface{}) {
  69. s.rmu.Lock()
  70. s.funcs[funcName] = funcBody
  71. s.rmu.Unlock()
  72. }
  73. // Load parses the templates to the engine.
  74. // It is responsible to add the necessary global functions.
  75. //
  76. // Returns an error if something bad happens, user is responsible to catch it.
  77. func (s *AmberEngine) Load() error {
  78. if s.assetFn != nil && s.namesFn != nil {
  79. // embedded
  80. return s.loadAssets()
  81. }
  82. // load from directory, make the dir absolute here too.
  83. dir, err := filepath.Abs(s.directory)
  84. if err != nil {
  85. return err
  86. }
  87. if _, err := os.Stat(dir); os.IsNotExist(err) {
  88. return err
  89. }
  90. // change the directory field configuration, load happens after directory has been set, so we will not have any problems here.
  91. s.directory = dir
  92. return s.loadDirectory()
  93. }
  94. // loadDirectory loads the amber templates from directory.
  95. func (s *AmberEngine) loadDirectory() error {
  96. dir, extension := s.directory, s.extension
  97. opt := amber.DirOptions{}
  98. opt.Recursive = true
  99. // prepare the global amber funcs
  100. funcs := template.FuncMap{}
  101. for k, v := range amber.FuncMap { // add the amber's default funcs
  102. funcs[k] = v
  103. }
  104. for k, v := range s.funcs {
  105. funcs[k] = v
  106. }
  107. amber.FuncMap = funcs // set the funcs
  108. opt.Ext = extension
  109. templates, err := amber.CompileDir(dir, opt, amber.DefaultOptions) // this returns the map with stripped extension, we want extension so we copy the map
  110. if err == nil {
  111. s.templateCache = make(map[string]*template.Template)
  112. for k, v := range templates {
  113. name := filepath.ToSlash(k + opt.Ext)
  114. s.templateCache[name] = v
  115. delete(templates, k)
  116. }
  117. }
  118. return err
  119. }
  120. // loadAssets builds the templates by binary, embedded.
  121. func (s *AmberEngine) loadAssets() error {
  122. virtualDirectory, virtualExtension := s.directory, s.extension
  123. assetFn, namesFn := s.assetFn, s.namesFn
  124. // prepare the global amber funcs
  125. funcs := template.FuncMap{}
  126. for k, v := range amber.FuncMap { // add the amber's default funcs
  127. funcs[k] = v
  128. }
  129. for k, v := range s.funcs {
  130. funcs[k] = v
  131. }
  132. if len(virtualDirectory) > 0 {
  133. if virtualDirectory[0] == '.' { // first check for .wrong
  134. virtualDirectory = virtualDirectory[1:]
  135. }
  136. if virtualDirectory[0] == '/' || virtualDirectory[0] == filepath.Separator { // second check for /something, (or ./something if we had dot on 0 it will be removed
  137. virtualDirectory = virtualDirectory[1:]
  138. }
  139. }
  140. amber.FuncMap = funcs // set the funcs
  141. names := namesFn()
  142. for _, path := range names {
  143. if !strings.HasPrefix(path, virtualDirectory) {
  144. continue
  145. }
  146. ext := filepath.Ext(path)
  147. if ext == virtualExtension {
  148. rel, err := filepath.Rel(virtualDirectory, path)
  149. if err != nil {
  150. return err
  151. }
  152. buf, err := assetFn(path)
  153. if err != nil {
  154. return err
  155. }
  156. name := filepath.ToSlash(rel)
  157. tmpl, err := amber.CompileData(buf, name, amber.DefaultOptions)
  158. if err != nil {
  159. return err
  160. }
  161. s.templateCache[name] = tmpl
  162. }
  163. }
  164. return nil
  165. }
  166. func (s *AmberEngine) fromCache(relativeName string) *template.Template {
  167. tmpl, ok := s.templateCache[relativeName]
  168. if ok {
  169. return tmpl
  170. }
  171. return nil
  172. }
  173. // ExecuteWriter executes a template and writes its result to the w writer.
  174. // layout here is useless.
  175. func (s *AmberEngine) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error {
  176. // re-parse the templates if reload is enabled.
  177. if s.reload {
  178. // locks to fix #872, it's the simplest solution and the most correct,
  179. // to execute writers with "wait list", one at a time.
  180. s.rmu.Lock()
  181. defer s.rmu.Unlock()
  182. if err := s.Load(); err != nil {
  183. return err
  184. }
  185. }
  186. if tmpl := s.fromCache(filename); tmpl != nil {
  187. return tmpl.Execute(w, bindingData)
  188. }
  189. return fmt.Errorf("Template with name: %s does not exist in the dir: %s", filename, s.directory)
  190. }