2015-07-30 15:43:22 +00:00
|
|
|
package pongo2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2017-12-28 01:56:23 +00:00
|
|
|
"io"
|
2015-07-30 15:43:22 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"sync"
|
2017-12-28 01:56:23 +00:00
|
|
|
|
|
|
|
"github.com/juju/errors"
|
2015-07-30 15:43:22 +00:00
|
|
|
)
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// 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).
|
2015-07-30 15:43:22 +00:00
|
|
|
type TemplateSet struct {
|
2017-12-28 01:56:23 +00:00
|
|
|
name string
|
|
|
|
loader TemplateLoader
|
2015-07-30 15:43:22 +00:00
|
|
|
|
|
|
|
// Globals will be provided to all templates created within this template set
|
|
|
|
Globals Context
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// 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
|
2015-07-30 15:43:22 +00:00
|
|
|
// variable during program execution (and template compilation/execution).
|
|
|
|
Debug bool
|
|
|
|
|
|
|
|
// Sandbox features
|
|
|
|
// - Disallow access to specific tags and/or filters (using BanTag() and BanFilter())
|
|
|
|
//
|
2017-12-28 01:56:23 +00:00
|
|
|
// 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).
|
2015-07-30 15:43:22 +00:00
|
|
|
firstTemplateCreated bool
|
|
|
|
bannedTags map[string]bool
|
|
|
|
bannedFilters map[string]bool
|
|
|
|
|
|
|
|
// Template cache (for FromCache())
|
|
|
|
templateCache map[string]*Template
|
|
|
|
templateCacheMutex sync.Mutex
|
|
|
|
}
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// 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, loader TemplateLoader) *TemplateSet {
|
2015-07-30 15:43:22 +00:00
|
|
|
return &TemplateSet{
|
|
|
|
name: name,
|
2017-12-28 01:56:23 +00:00
|
|
|
loader: loader,
|
2015-07-30 15:43:22 +00:00
|
|
|
Globals: make(Context),
|
|
|
|
bannedTags: make(map[string]bool),
|
|
|
|
bannedFilters: make(map[string]bool),
|
|
|
|
templateCache: make(map[string]*Template),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
func (set *TemplateSet) resolveFilename(tpl *Template, path string) string {
|
|
|
|
name := ""
|
|
|
|
if tpl != nil && tpl.isTplString {
|
|
|
|
return path
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
2017-12-28 01:56:23 +00:00
|
|
|
if tpl != nil {
|
|
|
|
name = tpl.name
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
2017-12-28 01:56:23 +00:00
|
|
|
return set.loader.Abs(name, path)
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// BanTag bans a specific tag for this template set. See more in the documentation for TemplateSet.
|
|
|
|
func (set *TemplateSet) BanTag(name string) error {
|
2015-07-30 15:43:22 +00:00
|
|
|
_, has := tags[name]
|
|
|
|
if !has {
|
2017-12-28 01:56:23 +00:00
|
|
|
return errors.Errorf("tag '%s' not found", name)
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
if set.firstTemplateCreated {
|
2017-12-28 01:56:23 +00:00
|
|
|
return errors.New("you cannot ban any tags after you've added your first template to your template set")
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
_, has = set.bannedTags[name]
|
|
|
|
if has {
|
2017-12-28 01:56:23 +00:00
|
|
|
return errors.Errorf("tag '%s' is already banned", name)
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
set.bannedTags[name] = true
|
2017-12-28 01:56:23 +00:00
|
|
|
|
|
|
|
return nil
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// BanFilter bans a specific filter for this template set. See more in the documentation for TemplateSet.
|
|
|
|
func (set *TemplateSet) BanFilter(name string) error {
|
2015-07-30 15:43:22 +00:00
|
|
|
_, has := filters[name]
|
|
|
|
if !has {
|
2017-12-28 01:56:23 +00:00
|
|
|
return errors.Errorf("filter '%s' not found", name)
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
if set.firstTemplateCreated {
|
2017-12-28 01:56:23 +00:00
|
|
|
return errors.New("you cannot ban any filters after you've added your first template to your template set")
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
_, has = set.bannedFilters[name]
|
|
|
|
if has {
|
2017-12-28 01:56:23 +00:00
|
|
|
return errors.Errorf("filter '%s' is already banned", name)
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
set.bannedFilters[name] = true
|
2017-12-28 01:56:23 +00:00
|
|
|
|
|
|
|
return nil
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// FromCache is a convenient method to cache templates. It is thread-safe
|
2015-07-30 15:43:22 +00:00
|
|
|
// 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)
|
2017-12-28 01:56:23 +00:00
|
|
|
}
|
|
|
|
// Cache the template
|
|
|
|
cleanedFilename := set.resolveFilename(nil, filename)
|
|
|
|
|
|
|
|
set.templateCacheMutex.Lock()
|
|
|
|
defer set.templateCacheMutex.Unlock()
|
|
|
|
|
|
|
|
tpl, has := set.templateCache[cleanedFilename]
|
2015-07-30 15:43:22 +00:00
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// Cache miss
|
|
|
|
if !has {
|
|
|
|
tpl, err := set.FromFile(cleanedFilename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
set.templateCache[cleanedFilename] = tpl
|
2015-07-30 15:43:22 +00:00
|
|
|
return tpl, nil
|
|
|
|
}
|
2017-12-28 01:56:23 +00:00
|
|
|
|
|
|
|
// Cache hit
|
|
|
|
return tpl, nil
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// FromString loads a template from string and returns a Template instance.
|
2015-07-30 15:43:22 +00:00
|
|
|
func (set *TemplateSet) FromString(tpl string) (*Template, error) {
|
|
|
|
set.firstTemplateCreated = true
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
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
|
|
|
|
|
2015-07-30 15:43:22 +00:00
|
|
|
return newTemplateString(set, tpl)
|
|
|
|
}
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// FromFile loads a template from a filename and returns a Template instance.
|
2015-07-30 15:43:22 +00:00
|
|
|
func (set *TemplateSet) FromFile(filename string) (*Template, error) {
|
|
|
|
set.firstTemplateCreated = true
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
fd, err := set.loader.Get(set.resolveFilename(nil, filename))
|
2015-07-30 15:43:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, &Error{
|
2017-12-28 01:56:23 +00:00
|
|
|
Filename: filename,
|
|
|
|
Sender: "fromfile",
|
|
|
|
OrigError: err,
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-28 01:56:23 +00:00
|
|
|
buf, err := ioutil.ReadAll(fd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &Error{
|
|
|
|
Filename: filename,
|
|
|
|
Sender: "fromfile",
|
|
|
|
OrigError: err,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return newTemplate(set, filename, false, buf)
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// RenderTemplateString is a shortcut and renders a template string directly.
|
|
|
|
func (set *TemplateSet) RenderTemplateString(s string, ctx Context) (string, error) {
|
2015-07-30 15:43:22 +00:00
|
|
|
set.firstTemplateCreated = true
|
|
|
|
|
|
|
|
tpl := Must(set.FromString(s))
|
|
|
|
result, err := tpl.Execute(ctx)
|
|
|
|
if err != nil {
|
2017-12-28 01:56:23 +00:00
|
|
|
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
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
2017-12-28 01:56:23 +00:00
|
|
|
return result, nil
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// RenderTemplateFile is a shortcut and renders a template file directly.
|
|
|
|
func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) (string, error) {
|
2015-07-30 15:43:22 +00:00
|
|
|
set.firstTemplateCreated = true
|
|
|
|
|
|
|
|
tpl := Must(set.FromFile(fn))
|
|
|
|
result, err := tpl.Execute(ctx)
|
|
|
|
if err != nil {
|
2017-12-28 01:56:23 +00:00
|
|
|
return "", err
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
2017-12-28 01:56:23 +00:00
|
|
|
return result, nil
|
2015-07-30 15:43:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2017-12-28 01:56:23 +00:00
|
|
|
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("")
|
2015-07-30 15:43:22 +00:00
|
|
|
|
2017-12-28 01:56:23 +00:00
|
|
|
// DefaultSet is a set created for you for convinience reasons.
|
|
|
|
DefaultSet = NewSet("default", DefaultLoader)
|
2015-07-30 15:43:22 +00:00
|
|
|
|
|
|
|
// Methods on the default set
|
|
|
|
FromString = DefaultSet.FromString
|
2017-12-28 01:56:23 +00:00
|
|
|
FromBytes = DefaultSet.FromBytes
|
2015-07-30 15:43:22 +00:00
|
|
|
FromFile = DefaultSet.FromFile
|
|
|
|
FromCache = DefaultSet.FromCache
|
|
|
|
RenderTemplateString = DefaultSet.RenderTemplateString
|
|
|
|
RenderTemplateFile = DefaultSet.RenderTemplateFile
|
|
|
|
|
|
|
|
// Globals for the default set
|
|
|
|
Globals = DefaultSet.Globals
|
|
|
|
)
|