mirror of https://github.com/k3s-io/k3s
306 lines
8.9 KiB
Go
306 lines
8.9 KiB
Go
|
package pongo2
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"log"
|
||
|
"os"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/juju/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 errors.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 errors.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 errors.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 errors.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
|
||
|
)
|