template_sets.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. package pongo2
  2. import (
  3. "fmt"
  4. "io"
  5. "io/ioutil"
  6. "log"
  7. "os"
  8. "sync"
  9. "github.com/juju/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. loader 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. // Sandbox features
  35. // - Disallow access to specific tags and/or filters (using BanTag() and BanFilter())
  36. //
  37. // For efficiency reasons you can ban tags/filters only *before* you have
  38. // added your first template to the set (restrictions are statically checked).
  39. // After you added one, it's not possible anymore (for your personal security).
  40. firstTemplateCreated bool
  41. bannedTags map[string]bool
  42. bannedFilters map[string]bool
  43. // Template cache (for FromCache())
  44. templateCache map[string]*Template
  45. templateCacheMutex sync.Mutex
  46. }
  47. // NewSet can be used to create sets with different kind of templates
  48. // (e. g. web from mail templates), with different globals or
  49. // other configurations.
  50. func NewSet(name string, loader TemplateLoader) *TemplateSet {
  51. return &TemplateSet{
  52. name: name,
  53. loader: loader,
  54. Globals: make(Context),
  55. bannedTags: make(map[string]bool),
  56. bannedFilters: make(map[string]bool),
  57. templateCache: make(map[string]*Template),
  58. }
  59. }
  60. func (set *TemplateSet) resolveFilename(tpl *Template, path string) string {
  61. name := ""
  62. if tpl != nil && tpl.isTplString {
  63. return path
  64. }
  65. if tpl != nil {
  66. name = tpl.name
  67. }
  68. return set.loader.Abs(name, path)
  69. }
  70. // BanTag bans a specific tag for this template set. See more in the documentation for TemplateSet.
  71. func (set *TemplateSet) BanTag(name string) error {
  72. _, has := tags[name]
  73. if !has {
  74. return errors.Errorf("tag '%s' not found", name)
  75. }
  76. if set.firstTemplateCreated {
  77. return errors.New("you cannot ban any tags after you've added your first template to your template set")
  78. }
  79. _, has = set.bannedTags[name]
  80. if has {
  81. return errors.Errorf("tag '%s' is already banned", name)
  82. }
  83. set.bannedTags[name] = true
  84. return nil
  85. }
  86. // BanFilter bans a specific filter for this template set. See more in the documentation for TemplateSet.
  87. func (set *TemplateSet) BanFilter(name string) error {
  88. _, has := filters[name]
  89. if !has {
  90. return errors.Errorf("filter '%s' not found", name)
  91. }
  92. if set.firstTemplateCreated {
  93. return errors.New("you cannot ban any filters after you've added your first template to your template set")
  94. }
  95. _, has = set.bannedFilters[name]
  96. if has {
  97. return errors.Errorf("filter '%s' is already banned", name)
  98. }
  99. set.bannedFilters[name] = true
  100. return nil
  101. }
  102. // FromCache is a convenient method to cache templates. It is thread-safe
  103. // and will only compile the template associated with a filename once.
  104. // If TemplateSet.Debug is true (for example during development phase),
  105. // FromCache() will not cache the template and instead recompile it on any
  106. // call (to make changes to a template live instantaneously).
  107. func (set *TemplateSet) FromCache(filename string) (*Template, error) {
  108. if set.Debug {
  109. // Recompile on any request
  110. return set.FromFile(filename)
  111. }
  112. // Cache the template
  113. cleanedFilename := set.resolveFilename(nil, filename)
  114. set.templateCacheMutex.Lock()
  115. defer set.templateCacheMutex.Unlock()
  116. tpl, has := set.templateCache[cleanedFilename]
  117. // Cache miss
  118. if !has {
  119. tpl, err := set.FromFile(cleanedFilename)
  120. if err != nil {
  121. return nil, err
  122. }
  123. set.templateCache[cleanedFilename] = tpl
  124. return tpl, nil
  125. }
  126. // Cache hit
  127. return tpl, nil
  128. }
  129. // FromString loads a template from string and returns a Template instance.
  130. func (set *TemplateSet) FromString(tpl string) (*Template, error) {
  131. set.firstTemplateCreated = true
  132. return newTemplateString(set, []byte(tpl))
  133. }
  134. // FromBytes loads a template from bytes and returns a Template instance.
  135. func (set *TemplateSet) FromBytes(tpl []byte) (*Template, error) {
  136. set.firstTemplateCreated = true
  137. return newTemplateString(set, tpl)
  138. }
  139. // FromFile loads a template from a filename and returns a Template instance.
  140. func (set *TemplateSet) FromFile(filename string) (*Template, error) {
  141. set.firstTemplateCreated = true
  142. fd, err := set.loader.Get(set.resolveFilename(nil, filename))
  143. if err != nil {
  144. return nil, &Error{
  145. Filename: filename,
  146. Sender: "fromfile",
  147. OrigError: err,
  148. }
  149. }
  150. buf, err := ioutil.ReadAll(fd)
  151. if err != nil {
  152. return nil, &Error{
  153. Filename: filename,
  154. Sender: "fromfile",
  155. OrigError: err,
  156. }
  157. }
  158. return newTemplate(set, filename, false, buf)
  159. }
  160. // RenderTemplateString is a shortcut and renders a template string directly.
  161. func (set *TemplateSet) RenderTemplateString(s string, ctx Context) (string, error) {
  162. set.firstTemplateCreated = true
  163. tpl := Must(set.FromString(s))
  164. result, err := tpl.Execute(ctx)
  165. if err != nil {
  166. return "", err
  167. }
  168. return result, nil
  169. }
  170. // RenderTemplateBytes is a shortcut and renders template bytes directly.
  171. func (set *TemplateSet) RenderTemplateBytes(b []byte, ctx Context) (string, error) {
  172. set.firstTemplateCreated = true
  173. tpl := Must(set.FromBytes(b))
  174. result, err := tpl.Execute(ctx)
  175. if err != nil {
  176. return "", err
  177. }
  178. return result, nil
  179. }
  180. // RenderTemplateFile is a shortcut and renders a template file directly.
  181. func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) (string, error) {
  182. set.firstTemplateCreated = true
  183. tpl := Must(set.FromFile(fn))
  184. result, err := tpl.Execute(ctx)
  185. if err != nil {
  186. return "", err
  187. }
  188. return result, nil
  189. }
  190. func (set *TemplateSet) logf(format string, args ...interface{}) {
  191. if set.Debug {
  192. logger.Printf(fmt.Sprintf("[template set: %s] %s", set.name, format), args...)
  193. }
  194. }
  195. // Logging function (internally used)
  196. func logf(format string, items ...interface{}) {
  197. if debug {
  198. logger.Printf(format, items...)
  199. }
  200. }
  201. var (
  202. debug bool // internal debugging
  203. logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags|log.Lshortfile)
  204. // DefaultLoader allows the default un-sandboxed access to the local file
  205. // system and is being used by the DefaultSet.
  206. DefaultLoader = MustNewLocalFileSystemLoader("")
  207. // DefaultSet is a set created for you for convinience reasons.
  208. DefaultSet = NewSet("default", DefaultLoader)
  209. // Methods on the default set
  210. FromString = DefaultSet.FromString
  211. FromBytes = DefaultSet.FromBytes
  212. FromFile = DefaultSet.FromFile
  213. FromCache = DefaultSet.FromCache
  214. RenderTemplateString = DefaultSet.RenderTemplateString
  215. RenderTemplateFile = DefaultSet.RenderTemplateFile
  216. // Globals for the default set
  217. Globals = DefaultSet.Globals
  218. )