transfer.sh/vendor/github.com/PuerkitoBio/ghost/templates/template.go
Remco cb6e5cb0c7 Major rewrite
* use dep for vendoring
* lets encrypt
* moved web to transfer.sh-web repo
* single command install
* added first tests
2017-03-22 18:09:21 +01:00

129 lines
3.3 KiB
Go

package templates
import (
"errors"
"io"
"net/http"
"os"
"path"
"path/filepath"
"sync"
"github.com/PuerkitoBio/ghost"
)
var (
ErrTemplateNotExist = errors.New("template does not exist")
ErrDirNotExist = errors.New("directory does not exist")
compilers = make(map[string]TemplateCompiler)
// The mutex guards the templaters map
mu sync.RWMutex
templaters = make(map[string]Templater)
)
// Defines the interface that the template compiler must return. The Go native
// templates implement this interface.
type Templater interface {
Execute(wr io.Writer, data interface{}) error
}
// The interface that a template engine must implement to be used by Ghost.
type TemplateCompiler interface {
Compile(fileName string) (Templater, error)
}
// TODO : How to manage Go nested templates?
// TODO : Support Go's port of the mustache template?
// Register a template compiler for the specified extension. Extensions are case-sensitive.
// The extension must start with a dot (it is compared to the result of path.Ext() on a
// given file name).
//
// Registering is not thread-safe. Compilers should be registered before the http server
// is started.
// Compiling templates, on the other hand, is thread-safe.
func Register(ext string, c TemplateCompiler) {
if c == nil {
panic("ghost: Register TemplateCompiler is nil")
}
if _, dup := compilers[ext]; dup {
panic("ghost: Register called twice for extension " + ext)
}
compilers[ext] = c
}
// Compile all templates that have a matching compiler (based on their extension) in the
// specified directory.
func CompileDir(dir string) error {
mu.Lock()
defer mu.Unlock()
return filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
if fi == nil {
return ErrDirNotExist
}
if !fi.IsDir() {
err = compileTemplate(path, dir)
if err != nil {
ghost.LogFn("ghost.templates : error compiling template %s : %s", path, err)
return err
}
}
return nil
})
}
// Compile a single template file, using the specified base directory. The base
// directory is used to set the name of the template (the part of the path relative to this
// base directory is used as the name of the template).
func Compile(path, base string) error {
mu.Lock()
defer mu.Unlock()
return compileTemplate(path, base)
}
// Compile the specified template file if there is a matching compiler.
func compileTemplate(p, base string) error {
ext := path.Ext(p)
c, ok := compilers[ext]
// Ignore file if no template compiler exist for this extension
if ok {
t, err := c.Compile(p)
if err != nil {
return err
}
key, err := filepath.Rel(base, p)
if err != nil {
return err
}
ghost.LogFn("ghost.templates : storing template for file %s", key)
templaters[key] = t
}
return nil
}
// Execute the template.
func Execute(tplName string, w io.Writer, data interface{}) error {
mu.RLock()
t, ok := templaters[tplName]
mu.RUnlock()
if !ok {
return ErrTemplateNotExist
}
return t.Execute(w, data)
}
// Render is the same as Execute, except that it takes a http.ResponseWriter
// instead of a generic io.Writer, and sets the Content-Type to text/html.
func Render(tplName string, w http.ResponseWriter, data interface{}) (err error) {
w.Header().Set("Content-Type", "text/html")
defer func() {
if err != nil {
w.Header().Del("Content-Type")
}
}()
return Execute(tplName, w, data)
}