template.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. // Copyright 2016 José Santos <henrique_1609@me.com>
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Jet is a fast and dynamic template engine for the Go programming language, set of features
  15. // includes very fast template execution, a dynamic and flexible language, template inheritance, low number of allocations,
  16. // special interfaces to allow even further optimizations.
  17. package jet
  18. import (
  19. "fmt"
  20. "io"
  21. "io/ioutil"
  22. "path/filepath"
  23. "reflect"
  24. "sync"
  25. "text/template"
  26. )
  27. var defaultExtensions = []string{
  28. "", // in case the path is given with the correct extension already
  29. ".jet",
  30. ".html.jet",
  31. ".jet.html",
  32. }
  33. // Set is responsible to load,invoke parse and cache templates and relations
  34. // every jet template is associated with one set.
  35. // create a set with jet.NewSet(escapeeFn) returns a pointer to the Set
  36. type Set struct {
  37. loader Loader
  38. templates map[string]*Template // parsed templates
  39. escapee SafeWriter // escapee to use at runtime
  40. globals VarMap // global scope for this template set
  41. tmx *sync.RWMutex // template parsing mutex
  42. gmx *sync.RWMutex // global variables map mutex
  43. extensions []string
  44. developmentMode bool
  45. leftDelim string
  46. rightDelim string
  47. }
  48. // SetDevelopmentMode set's development mode on/off, in development mode template will be recompiled on every run
  49. func (s *Set) SetDevelopmentMode(b bool) *Set {
  50. s.developmentMode = b
  51. return s
  52. }
  53. func (s *Set) LookupGlobal(key string) (val interface{}, found bool) {
  54. s.gmx.RLock()
  55. val, found = s.globals[key]
  56. s.gmx.RUnlock()
  57. return
  58. }
  59. // AddGlobal add or set a global variable into the Set
  60. func (s *Set) AddGlobal(key string, i interface{}) *Set {
  61. s.gmx.Lock()
  62. s.globals[key] = reflect.ValueOf(i)
  63. s.gmx.Unlock()
  64. return s
  65. }
  66. func (s *Set) AddGlobalFunc(key string, fn Func) *Set {
  67. return s.AddGlobal(key, fn)
  68. }
  69. // NewSetLoader creates a new set with custom Loader
  70. func NewSetLoader(escapee SafeWriter, loader Loader) *Set {
  71. return &Set{
  72. loader: loader,
  73. templates: map[string]*Template{},
  74. escapee: escapee,
  75. globals: VarMap{},
  76. tmx: &sync.RWMutex{},
  77. gmx: &sync.RWMutex{},
  78. extensions: append([]string{}, defaultExtensions...),
  79. }
  80. }
  81. // NewHTMLSetLoader creates a new set with custom Loader
  82. func NewHTMLSetLoader(loader Loader) *Set {
  83. return NewSetLoader(template.HTMLEscape, loader)
  84. }
  85. // NewSet creates a new set, dirs is a list of directories to be searched for templates
  86. func NewSet(escapee SafeWriter, dir string) *Set {
  87. return NewSetLoader(escapee, &OSFileSystemLoader{dir: dir})
  88. }
  89. // NewHTMLSet creates a new set, dirs is a list of directories to be searched for templates
  90. func NewHTMLSet(dir string) *Set {
  91. return NewSet(template.HTMLEscape, dir)
  92. }
  93. // Delims sets the delimiters to the specified strings. Parsed templates will
  94. // inherit the settings. Not setting them leaves them at the default: {{ or }}.
  95. func (s *Set) Delims(left, right string) {
  96. s.leftDelim = left
  97. s.rightDelim = right
  98. }
  99. func (s *Set) getTemplateFromCache(path string) (t *Template, ok bool) {
  100. // check path with all possible extensions in cache
  101. for _, extension := range s.extensions {
  102. canonicalPath := path + extension
  103. if t, found := s.templates[canonicalPath]; found {
  104. return t, true
  105. }
  106. }
  107. return nil, false
  108. }
  109. func (s *Set) getTemplateFromLoader(path string) (t *Template, err error) {
  110. // check path with all possible extensions in loader
  111. for _, extension := range s.extensions {
  112. canonicalPath := path + extension
  113. if _, found := s.loader.Exists(canonicalPath); found {
  114. return s.loadFromFile(canonicalPath)
  115. }
  116. }
  117. return nil, fmt.Errorf("template %s could not be found", path)
  118. }
  119. // GetTemplate tries to find (and load, if not yet loaded) the template at the specified path.
  120. //
  121. // for example, GetTemplate("catalog/products.list") with extensions set to []string{"", ".html.jet",".jet"}
  122. // will try to look for:
  123. // 1. catalog/products.list
  124. // 2. catalog/products.list.html.jet
  125. // 3. catalog/products.list.jet
  126. // in the set's templates cache, and if it can't find the template it will try to load the same paths via
  127. // the loader, and, if parsed successfully, cache the template.
  128. func (s *Set) GetTemplate(path string) (t *Template, err error) {
  129. if !s.developmentMode {
  130. s.tmx.RLock()
  131. t, found := s.getTemplateFromCache(path)
  132. if found {
  133. s.tmx.RUnlock()
  134. return t, nil
  135. }
  136. s.tmx.RUnlock()
  137. }
  138. t, err = s.getTemplateFromLoader(path)
  139. if err == nil && !s.developmentMode {
  140. // load template into cache
  141. s.tmx.Lock()
  142. s.templates[t.Name] = t
  143. s.tmx.Unlock()
  144. }
  145. return t, err
  146. }
  147. // same as GetTemplate, but assumes the reader already called s.tmx.RLock(), and
  148. // doesn't cache a template when found through the loader
  149. func (s *Set) getTemplate(path string) (t *Template, err error) {
  150. if !s.developmentMode {
  151. t, found := s.getTemplateFromCache(path)
  152. if found {
  153. return t, nil
  154. }
  155. }
  156. return s.getTemplateFromLoader(path)
  157. }
  158. func (s *Set) getSiblingTemplate(path, siblingPath string) (t *Template, err error) {
  159. if !filepath.IsAbs(filepath.Clean(path)) {
  160. siblingDir := filepath.Dir(siblingPath)
  161. path = filepath.Join(siblingDir, path)
  162. }
  163. return s.getTemplate(path)
  164. }
  165. // Parse parses the template without adding it to the set's templates cache.
  166. func (s *Set) Parse(path, content string) (*Template, error) {
  167. s.tmx.RLock()
  168. t, err := s.parse(path, content)
  169. s.tmx.RUnlock()
  170. return t, err
  171. }
  172. func (s *Set) loadFromFile(path string) (template *Template, err error) {
  173. f, err := s.loader.Open(path)
  174. if err != nil {
  175. return nil, err
  176. }
  177. defer f.Close()
  178. content, err := ioutil.ReadAll(f)
  179. if err != nil {
  180. return nil, err
  181. }
  182. return s.parse(path, string(content))
  183. }
  184. func (s *Set) LoadTemplate(path, content string) (*Template, error) {
  185. if s.developmentMode {
  186. s.tmx.RLock()
  187. defer s.tmx.RUnlock()
  188. return s.parse(path, content)
  189. }
  190. // fast path (t from cache)
  191. s.tmx.RLock()
  192. if t, found := s.templates[path]; found {
  193. s.tmx.RUnlock()
  194. return t, nil
  195. }
  196. s.tmx.RUnlock()
  197. // not found, parse and cache
  198. s.tmx.Lock()
  199. defer s.tmx.Unlock()
  200. t, err := s.parse(path, content)
  201. if err == nil {
  202. s.templates[path] = t
  203. }
  204. return t, err
  205. }
  206. // SetExtensions sets extensions.
  207. func (s *Set) SetExtensions(extensions []string) {
  208. s.extensions = extensions
  209. }
  210. func (t *Template) String() (template string) {
  211. if t.extends != nil {
  212. if len(t.Root.Nodes) > 0 && len(t.imports) == 0 {
  213. template += fmt.Sprintf("{{extends %q}}", t.extends.ParseName)
  214. } else {
  215. template += fmt.Sprintf("{{extends %q}}", t.extends.ParseName)
  216. }
  217. }
  218. for k, _import := range t.imports {
  219. if t.extends == nil && k == 0 {
  220. template += fmt.Sprintf("{{import %q}}", _import.ParseName)
  221. } else {
  222. template += fmt.Sprintf("\n{{import %q}}", _import.ParseName)
  223. }
  224. }
  225. if t.extends != nil || len(t.imports) > 0 {
  226. if len(t.Root.Nodes) > 0 {
  227. template += "\n" + t.Root.String()
  228. }
  229. } else {
  230. template += t.Root.String()
  231. }
  232. return
  233. }
  234. func (t *Template) addBlocks(blocks map[string]*BlockNode) {
  235. if len(blocks) == 0 {
  236. return
  237. }
  238. if t.processedBlocks == nil {
  239. t.processedBlocks = make(map[string]*BlockNode)
  240. }
  241. for key, value := range blocks {
  242. t.processedBlocks[key] = value
  243. }
  244. }
  245. type VarMap map[string]reflect.Value
  246. func (scope VarMap) Set(name string, v interface{}) VarMap {
  247. scope[name] = reflect.ValueOf(v)
  248. return scope
  249. }
  250. func (scope VarMap) SetFunc(name string, v Func) VarMap {
  251. scope[name] = reflect.ValueOf(v)
  252. return scope
  253. }
  254. func (scope VarMap) SetWriter(name string, v SafeWriter) VarMap {
  255. scope[name] = reflect.ValueOf(v)
  256. return scope
  257. }
  258. // Execute executes the template in the w Writer
  259. func (t *Template) Execute(w io.Writer, variables VarMap, data interface{}) error {
  260. return t.ExecuteI18N(nil, w, variables, data)
  261. }
  262. type Translator interface {
  263. Msg(key, defaultValue string) string
  264. Trans(format, defaultFormat string, v ...interface{}) string
  265. }
  266. func (t *Template) ExecuteI18N(translator Translator, w io.Writer, variables VarMap, data interface{}) (err error) {
  267. st := pool_State.Get().(*Runtime)
  268. defer st.recover(&err)
  269. st.blocks = t.processedBlocks
  270. st.translator = translator
  271. st.variables = variables
  272. st.set = t.set
  273. st.Writer = w
  274. // resolve extended template
  275. for t.extends != nil {
  276. t = t.extends
  277. }
  278. if data != nil {
  279. st.context = reflect.ValueOf(data)
  280. }
  281. st.executeList(t.Root)
  282. return
  283. }