template_sets.go 8.8 KB

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