123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- package pongo2
- import (
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "os"
- "sync"
- "errors"
- )
- // TemplateLoader allows to implement a virtual file system.
- type TemplateLoader interface {
- // Abs calculates the path to a given template. Whenever a path must be resolved
- // due to an import from another template, the base equals the parent template's path.
- Abs(base, name string) string
- // Get returns an io.Reader where the template's content can be read from.
- Get(path string) (io.Reader, error)
- }
- // TemplateSet allows you to create your own group of templates with their own
- // global context (which is shared among all members of the set) and their own
- // configuration.
- // It's useful for a separation of different kind of templates
- // (e. g. web templates vs. mail templates).
- type TemplateSet struct {
- name string
- loaders []TemplateLoader
- // Globals will be provided to all templates created within this template set
- Globals Context
- // If debug is true (default false), ExecutionContext.Logf() will work and output
- // to STDOUT. Furthermore, FromCache() won't cache the templates.
- // Make sure to synchronize the access to it in case you're changing this
- // variable during program execution (and template compilation/execution).
- Debug bool
- // Options allow you to change the behavior of template-engine.
- // You can change the options before calling the Execute method.
- Options *Options
- // Sandbox features
- // - Disallow access to specific tags and/or filters (using BanTag() and BanFilter())
- //
- // For efficiency reasons you can ban tags/filters only *before* you have
- // added your first template to the set (restrictions are statically checked).
- // After you added one, it's not possible anymore (for your personal security).
- firstTemplateCreated bool
- bannedTags map[string]bool
- bannedFilters map[string]bool
- // Template cache (for FromCache())
- templateCache map[string]*Template
- templateCacheMutex sync.Mutex
- }
- // NewSet can be used to create sets with different kind of templates
- // (e. g. web from mail templates), with different globals or
- // other configurations.
- func NewSet(name string, loaders ...TemplateLoader) *TemplateSet {
- if len(loaders) == 0 {
- panic(fmt.Errorf("at least one template loader must be specified"))
- }
- return &TemplateSet{
- name: name,
- loaders: loaders,
- Globals: make(Context),
- bannedTags: make(map[string]bool),
- bannedFilters: make(map[string]bool),
- templateCache: make(map[string]*Template),
- Options: newOptions(),
- }
- }
- func (set *TemplateSet) AddLoader(loaders ...TemplateLoader) {
- set.loaders = append(set.loaders, loaders...)
- }
- func (set *TemplateSet) resolveFilename(tpl *Template, path string) string {
- return set.resolveFilenameForLoader(set.loaders[0], tpl, path)
- }
- func (set *TemplateSet) resolveFilenameForLoader(loader TemplateLoader, tpl *Template, path string) string {
- name := ""
- if tpl != nil && tpl.isTplString {
- return path
- }
- if tpl != nil {
- name = tpl.name
- }
- return loader.Abs(name, path)
- }
- // BanTag bans a specific tag for this template set. See more in the documentation for TemplateSet.
- func (set *TemplateSet) BanTag(name string) error {
- _, has := tags[name]
- if !has {
- return fmt.Errorf("tag '%s' not found", name)
- }
- if set.firstTemplateCreated {
- return errors.New("you cannot ban any tags after you've added your first template to your template set")
- }
- _, has = set.bannedTags[name]
- if has {
- return fmt.Errorf("tag '%s' is already banned", name)
- }
- set.bannedTags[name] = true
- return nil
- }
- // BanFilter bans a specific filter for this template set. See more in the documentation for TemplateSet.
- func (set *TemplateSet) BanFilter(name string) error {
- _, has := filters[name]
- if !has {
- return fmt.Errorf("filter '%s' not found", name)
- }
- if set.firstTemplateCreated {
- return errors.New("you cannot ban any filters after you've added your first template to your template set")
- }
- _, has = set.bannedFilters[name]
- if has {
- return fmt.Errorf("filter '%s' is already banned", name)
- }
- set.bannedFilters[name] = true
- return nil
- }
- func (set *TemplateSet) resolveTemplate(tpl *Template, path string) (name string, loader TemplateLoader, fd io.Reader, err error) {
- // iterate over loaders until we appear to have a valid template
- for _, loader = range set.loaders {
- name = set.resolveFilenameForLoader(loader, tpl, path)
- fd, err = loader.Get(name)
- if err == nil {
- return
- }
- }
- return path, nil, nil, fmt.Errorf("unable to resolve template")
- }
- // CleanCache cleans the template cache. If filenames is not empty,
- // it will remove the template caches of those filenames.
- // Or it will empty the whole template cache. It is thread-safe.
- func (set *TemplateSet) CleanCache(filenames ...string) {
- set.templateCacheMutex.Lock()
- defer set.templateCacheMutex.Unlock()
- if len(filenames) == 0 {
- set.templateCache = make(map[string]*Template, len(set.templateCache))
- }
- for _, filename := range filenames {
- delete(set.templateCache, set.resolveFilename(nil, filename))
- }
- }
- // FromCache is a convenient method to cache templates. It is thread-safe
- // and will only compile the template associated with a filename once.
- // If TemplateSet.Debug is true (for example during development phase),
- // FromCache() will not cache the template and instead recompile it on any
- // call (to make changes to a template live instantaneously).
- func (set *TemplateSet) FromCache(filename string) (*Template, error) {
- if set.Debug {
- // Recompile on any request
- return set.FromFile(filename)
- }
- // Cache the template
- cleanedFilename := set.resolveFilename(nil, filename)
- set.templateCacheMutex.Lock()
- defer set.templateCacheMutex.Unlock()
- tpl, has := set.templateCache[cleanedFilename]
- // Cache miss
- if !has {
- tpl, err := set.FromFile(cleanedFilename)
- if err != nil {
- return nil, err
- }
- set.templateCache[cleanedFilename] = tpl
- return tpl, nil
- }
- // Cache hit
- return tpl, nil
- }
- // FromString loads a template from string and returns a Template instance.
- func (set *TemplateSet) FromString(tpl string) (*Template, error) {
- set.firstTemplateCreated = true
- return newTemplateString(set, []byte(tpl))
- }
- // FromBytes loads a template from bytes and returns a Template instance.
- func (set *TemplateSet) FromBytes(tpl []byte) (*Template, error) {
- set.firstTemplateCreated = true
- return newTemplateString(set, tpl)
- }
- // FromFile loads a template from a filename and returns a Template instance.
- func (set *TemplateSet) FromFile(filename string) (*Template, error) {
- set.firstTemplateCreated = true
- _, _, fd, err := set.resolveTemplate(nil, filename)
- if err != nil {
- return nil, &Error{
- Filename: filename,
- Sender: "fromfile",
- OrigError: err,
- }
- }
- buf, err := ioutil.ReadAll(fd)
- if err != nil {
- return nil, &Error{
- Filename: filename,
- Sender: "fromfile",
- OrigError: err,
- }
- }
- return newTemplate(set, filename, false, buf)
- }
- // RenderTemplateString is a shortcut and renders a template string directly.
- func (set *TemplateSet) RenderTemplateString(s string, ctx Context) (string, error) {
- set.firstTemplateCreated = true
- tpl := Must(set.FromString(s))
- result, err := tpl.Execute(ctx)
- if err != nil {
- return "", err
- }
- return result, nil
- }
- // RenderTemplateBytes is a shortcut and renders template bytes directly.
- func (set *TemplateSet) RenderTemplateBytes(b []byte, ctx Context) (string, error) {
- set.firstTemplateCreated = true
- tpl := Must(set.FromBytes(b))
- result, err := tpl.Execute(ctx)
- if err != nil {
- return "", err
- }
- return result, nil
- }
- // RenderTemplateFile is a shortcut and renders a template file directly.
- func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) (string, error) {
- set.firstTemplateCreated = true
- tpl := Must(set.FromFile(fn))
- result, err := tpl.Execute(ctx)
- if err != nil {
- return "", err
- }
- return result, nil
- }
- func (set *TemplateSet) logf(format string, args ...interface{}) {
- if set.Debug {
- logger.Printf(fmt.Sprintf("[template set: %s] %s", set.name, format), args...)
- }
- }
- // Logging function (internally used)
- func logf(format string, items ...interface{}) {
- if debug {
- logger.Printf(format, items...)
- }
- }
- var (
- debug bool // internal debugging
- logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags|log.Lshortfile)
- // DefaultLoader allows the default un-sandboxed access to the local file
- // system and is being used by the DefaultSet.
- DefaultLoader = MustNewLocalFileSystemLoader("")
- // DefaultSet is a set created for you for convinience reasons.
- DefaultSet = NewSet("default", DefaultLoader)
- // Methods on the default set
- FromString = DefaultSet.FromString
- FromBytes = DefaultSet.FromBytes
- FromFile = DefaultSet.FromFile
- FromCache = DefaultSet.FromCache
- RenderTemplateString = DefaultSet.RenderTemplateString
- RenderTemplateFile = DefaultSet.RenderTemplateFile
- // Globals for the default set
- Globals = DefaultSet.Globals
- )
|