template_sets.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. package pongo2
  2. import (
  3. "fmt"
  4. "io"
  5. "io/ioutil"
  6. "log"
  7. "os"
  8. "sync"
  9. "errors"
  10. )
  11. // TemplateLoader allows to implement a virtual file system.
  12. type TemplateLoader interface {
  13. // Abs calculates the path to a given template. Whenever a path must be resolved
  14. // due to an import from another template, the base equals the parent template's path.
  15. Abs(base, name string) string
  16. // Get returns an io.Reader where the template's content can be read from.
  17. Get(path string) (io.Reader, error)
  18. }
  19. // TemplateSet allows you to create your own group of templates with their own
  20. // global context (which is shared among all members of the set) and their own
  21. // configuration.
  22. // It's useful for a separation of different kind of templates
  23. // (e. g. web templates vs. mail templates).
  24. type TemplateSet struct {
  25. name string
  26. loaders []TemplateLoader
  27. // Globals will be provided to all templates created within this template set
  28. Globals Context
  29. // If debug is true (default false), ExecutionContext.Logf() will work and output
  30. // to STDOUT. Furthermore, FromCache() won't cache the templates.
  31. // Make sure to synchronize the access to it in case you're changing this
  32. // variable during program execution (and template compilation/execution).
  33. Debug bool
  34. // Options allow you to change the behavior of template-engine.
  35. // You can change the options before calling the Execute method.
  36. Options *Options
  37. // Sandbox features
  38. // - Disallow access to specific tags and/or filters (using BanTag() and BanFilter())
  39. //
  40. // For efficiency reasons you can ban tags/filters only *before* you have
  41. // added your first template to the set (restrictions are statically checked).
  42. // After you added one, it's not possible anymore (for your personal security).
  43. firstTemplateCreated bool
  44. bannedTags map[string]bool
  45. bannedFilters map[string]bool
  46. // Template cache (for FromCache())
  47. templateCache map[string]*Template
  48. templateCacheMutex sync.Mutex
  49. }
  50. // NewSet can be used to create sets with different kind of templates
  51. // (e. g. web from mail templates), with different globals or
  52. // other configurations.
  53. func NewSet(name string, loaders ...TemplateLoader) *TemplateSet {
  54. if len(loaders) == 0 {
  55. panic(fmt.Errorf("at least one template loader must be specified"))
  56. }
  57. return &TemplateSet{
  58. name: name,
  59. loaders: loaders,
  60. Globals: make(Context),
  61. bannedTags: make(map[string]bool),
  62. bannedFilters: make(map[string]bool),
  63. templateCache: make(map[string]*Template),
  64. Options: newOptions(),
  65. }
  66. }
  67. func (set *TemplateSet) AddLoader(loaders ...TemplateLoader) {
  68. set.loaders = append(set.loaders, loaders...)
  69. }
  70. func (set *TemplateSet) resolveFilename(tpl *Template, path string) string {
  71. return set.resolveFilenameForLoader(set.loaders[0], tpl, path)
  72. }
  73. func (set *TemplateSet) resolveFilenameForLoader(loader TemplateLoader, tpl *Template, path string) string {
  74. name := ""
  75. if tpl != nil && tpl.isTplString {
  76. return path
  77. }
  78. if tpl != nil {
  79. name = tpl.name
  80. }
  81. return loader.Abs(name, path)
  82. }
  83. // BanTag bans a specific tag for this template set. See more in the documentation for TemplateSet.
  84. func (set *TemplateSet) BanTag(name string) error {
  85. _, has := tags[name]
  86. if !has {
  87. return fmt.Errorf("tag '%s' not found", name)
  88. }
  89. if set.firstTemplateCreated {
  90. return errors.New("you cannot ban any tags after you've added your first template to your template set")
  91. }
  92. _, has = set.bannedTags[name]
  93. if has {
  94. return fmt.Errorf("tag '%s' is already banned", name)
  95. }
  96. set.bannedTags[name] = true
  97. return nil
  98. }
  99. // BanFilter bans a specific filter for this template set. See more in the documentation for TemplateSet.
  100. func (set *TemplateSet) BanFilter(name string) error {
  101. _, has := filters[name]
  102. if !has {
  103. return fmt.Errorf("filter '%s' not found", name)
  104. }
  105. if set.firstTemplateCreated {
  106. return errors.New("you cannot ban any filters after you've added your first template to your template set")
  107. }
  108. _, has = set.bannedFilters[name]
  109. if has {
  110. return fmt.Errorf("filter '%s' is already banned", name)
  111. }
  112. set.bannedFilters[name] = true
  113. return nil
  114. }
  115. func (set *TemplateSet) resolveTemplate(tpl *Template, path string) (name string, loader TemplateLoader, fd io.Reader, err error) {
  116. // iterate over loaders until we appear to have a valid template
  117. for _, loader = range set.loaders {
  118. name = set.resolveFilenameForLoader(loader, tpl, path)
  119. fd, err = loader.Get(name)
  120. if err == nil {
  121. return
  122. }
  123. }
  124. return path, nil, nil, fmt.Errorf("unable to resolve template")
  125. }
  126. // CleanCache cleans the template cache. If filenames is not empty,
  127. // it will remove the template caches of those filenames.
  128. // Or it will empty the whole template cache. It is thread-safe.
  129. func (set *TemplateSet) CleanCache(filenames ...string) {
  130. set.templateCacheMutex.Lock()
  131. defer set.templateCacheMutex.Unlock()
  132. if len(filenames) == 0 {
  133. set.templateCache = make(map[string]*Template, len(set.templateCache))
  134. }
  135. for _, filename := range filenames {
  136. delete(set.templateCache, set.resolveFilename(nil, filename))
  137. }
  138. }
  139. // FromCache is a convenient method to cache templates. It is thread-safe
  140. // and will only compile the template associated with a filename once.
  141. // If TemplateSet.Debug is true (for example during development phase),
  142. // FromCache() will not cache the template and instead recompile it on any
  143. // call (to make changes to a template live instantaneously).
  144. func (set *TemplateSet) FromCache(filename string) (*Template, error) {
  145. if set.Debug {
  146. // Recompile on any request
  147. return set.FromFile(filename)
  148. }
  149. // Cache the template
  150. cleanedFilename := set.resolveFilename(nil, filename)
  151. set.templateCacheMutex.Lock()
  152. defer set.templateCacheMutex.Unlock()
  153. tpl, has := set.templateCache[cleanedFilename]
  154. // Cache miss
  155. if !has {
  156. tpl, err := set.FromFile(cleanedFilename)
  157. if err != nil {
  158. return nil, err
  159. }
  160. set.templateCache[cleanedFilename] = tpl
  161. return tpl, nil
  162. }
  163. // Cache hit
  164. return tpl, nil
  165. }
  166. // FromString loads a template from string and returns a Template instance.
  167. func (set *TemplateSet) FromString(tpl string) (*Template, error) {
  168. set.firstTemplateCreated = true
  169. return newTemplateString(set, []byte(tpl))
  170. }
  171. // FromBytes loads a template from bytes and returns a Template instance.
  172. func (set *TemplateSet) FromBytes(tpl []byte) (*Template, error) {
  173. set.firstTemplateCreated = true
  174. return newTemplateString(set, tpl)
  175. }
  176. // FromFile loads a template from a filename and returns a Template instance.
  177. func (set *TemplateSet) FromFile(filename string) (*Template, error) {
  178. set.firstTemplateCreated = true
  179. _, _, fd, err := set.resolveTemplate(nil, filename)
  180. if err != nil {
  181. return nil, &Error{
  182. Filename: filename,
  183. Sender: "fromfile",
  184. OrigError: err,
  185. }
  186. }
  187. buf, err := ioutil.ReadAll(fd)
  188. if err != nil {
  189. return nil, &Error{
  190. Filename: filename,
  191. Sender: "fromfile",
  192. OrigError: err,
  193. }
  194. }
  195. return newTemplate(set, filename, false, buf)
  196. }
  197. // RenderTemplateString is a shortcut and renders a template string directly.
  198. func (set *TemplateSet) RenderTemplateString(s string, ctx Context) (string, error) {
  199. set.firstTemplateCreated = true
  200. tpl := Must(set.FromString(s))
  201. result, err := tpl.Execute(ctx)
  202. if err != nil {
  203. return "", err
  204. }
  205. return result, nil
  206. }
  207. // RenderTemplateBytes is a shortcut and renders template bytes directly.
  208. func (set *TemplateSet) RenderTemplateBytes(b []byte, ctx Context) (string, error) {
  209. set.firstTemplateCreated = true
  210. tpl := Must(set.FromBytes(b))
  211. result, err := tpl.Execute(ctx)
  212. if err != nil {
  213. return "", err
  214. }
  215. return result, nil
  216. }
  217. // RenderTemplateFile is a shortcut and renders a template file directly.
  218. func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) (string, error) {
  219. set.firstTemplateCreated = true
  220. tpl := Must(set.FromFile(fn))
  221. result, err := tpl.Execute(ctx)
  222. if err != nil {
  223. return "", err
  224. }
  225. return result, nil
  226. }
  227. func (set *TemplateSet) logf(format string, args ...interface{}) {
  228. if set.Debug {
  229. logger.Printf(fmt.Sprintf("[template set: %s] %s", set.name, format), args...)
  230. }
  231. }
  232. // Logging function (internally used)
  233. func logf(format string, items ...interface{}) {
  234. if debug {
  235. logger.Printf(format, items...)
  236. }
  237. }
  238. var (
  239. debug bool // internal debugging
  240. logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags|log.Lshortfile)
  241. // DefaultLoader allows the default un-sandboxed access to the local file
  242. // system and is being used by the DefaultSet.
  243. DefaultLoader = MustNewLocalFileSystemLoader("")
  244. // DefaultSet is a set created for you for convinience reasons.
  245. DefaultSet = NewSet("default", DefaultLoader)
  246. // Methods on the default set
  247. FromString = DefaultSet.FromString
  248. FromBytes = DefaultSet.FromBytes
  249. FromFile = DefaultSet.FromFile
  250. FromCache = DefaultSet.FromCache
  251. RenderTemplateString = DefaultSet.RenderTemplateString
  252. RenderTemplateFile = DefaultSet.RenderTemplateFile
  253. // Globals for the default set
  254. Globals = DefaultSet.Globals
  255. )