|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- 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)
- }
|