|
|
|
@ -1,91 +1,79 @@
|
|
|
|
|
package html |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"fmt" |
|
|
|
|
"errors" |
|
|
|
|
"html/template" |
|
|
|
|
"io" |
|
|
|
|
"io/ioutil" |
|
|
|
|
"net/http" |
|
|
|
|
"path/filepath" |
|
|
|
|
"sync" |
|
|
|
|
|
|
|
|
|
"github.com/oxtoacart/bpool" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
ErrLayoutNotFound = errors.New("layout not found") |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// TemplateService is a template/html based templating service
|
|
|
|
|
type TemplateService struct { |
|
|
|
|
templates map[string]*template.Template |
|
|
|
|
pool *bpool.BufferPool |
|
|
|
|
helpers template.FuncMap |
|
|
|
|
mutex sync.RWMutex |
|
|
|
|
blocks map[string]string |
|
|
|
|
layouts map[string]*template.Template |
|
|
|
|
loader Loader |
|
|
|
|
pool *bpool.BufferPool |
|
|
|
|
helpers template.FuncMap |
|
|
|
|
devMode bool |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (t *TemplateService) AddBlock(name string, blockContent string) { |
|
|
|
|
t.mutex.Lock() |
|
|
|
|
defer t.mutex.Unlock() |
|
|
|
|
|
|
|
|
|
t.blocks[name] = blockContent |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (t *TemplateService) LoadLayout(name string, layout string, blocks []string) error { |
|
|
|
|
func (t *TemplateService) LoadLayout(name string, layoutContent string) error { |
|
|
|
|
tmpl := template.New(name) |
|
|
|
|
tmpl.Funcs(t.helpers) |
|
|
|
|
|
|
|
|
|
for _, b := range blocks { |
|
|
|
|
t.mutex.RLock() |
|
|
|
|
for _, b := range t.blocks { |
|
|
|
|
if _, err := tmpl.Parse(b); err != nil { |
|
|
|
|
t.mutex.RUnlock() |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
t.mutex.RUnlock() |
|
|
|
|
|
|
|
|
|
tmpl, err := tmpl.Parse(layout) |
|
|
|
|
tmpl, err := tmpl.Parse(layoutContent) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
t.templates[name] = tmpl |
|
|
|
|
t.mutex.Lock() |
|
|
|
|
t.layouts[name] = tmpl |
|
|
|
|
t.mutex.Unlock() |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// LoadTemplatesDir loads the templates used by the service
|
|
|
|
|
func (t *TemplateService) LoadTemplatesDir(templatesDir string) error { |
|
|
|
|
layoutFiles, err := filepath.Glob(filepath.Join(templatesDir, "layouts", "*.tmpl")) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
blockFiles, err := filepath.Glob(filepath.Join(templatesDir, "blocks", "*.tmpl")) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
blockTemplates := make([]string, 0, len(blockFiles)) |
|
|
|
|
|
|
|
|
|
for _, f := range blockFiles { |
|
|
|
|
templateData, err := ioutil.ReadFile(f) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
blockTemplates = append(blockTemplates, string(templateData)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Generate our templates map from our layouts/ and blocks/ directories
|
|
|
|
|
for _, f := range layoutFiles { |
|
|
|
|
templateData, err := ioutil.ReadFile(f) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
templateName := filepath.Base(f) |
|
|
|
|
|
|
|
|
|
if err := t.LoadLayout(templateName, string(templateData), blockTemplates); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
|
|
// Load parses templates via the configured template loader.
|
|
|
|
|
func (t *TemplateService) Load() error { |
|
|
|
|
return t.loader.Load(t) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// RenderPage renders a template to the given http.ResponseWriter
|
|
|
|
|
func (t *TemplateService) RenderPage(w http.ResponseWriter, templateName string, data interface{}) error { |
|
|
|
|
if t.devMode { |
|
|
|
|
if err := t.loader.Load(t); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Ensure the template exists in the map.
|
|
|
|
|
tmpl, ok := t.templates[templateName] |
|
|
|
|
tmpl, ok := t.layouts[templateName] |
|
|
|
|
if !ok { |
|
|
|
|
return fmt.Errorf("the template '%s' does not exist", templateName) |
|
|
|
|
return ErrLayoutNotFound |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Create a buffer to temporarily write to and check if any errors were encountered.
|
|
|
|
@ -103,20 +91,25 @@ func (t *TemplateService) RenderPage(w http.ResponseWriter, templateName string,
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Render renders a template to the given io.Writer
|
|
|
|
|
func (t *TemplateService) Render(w io.Writer, templateName string, data interface{}) error { |
|
|
|
|
// Render renders a layout to the given io.Writer.
|
|
|
|
|
func (t *TemplateService) Render(w io.Writer, layoutName string, data interface{}) error { |
|
|
|
|
if t.devMode { |
|
|
|
|
if err := t.loader.Load(t); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Ensure the template exists in the map.
|
|
|
|
|
tmpl, ok := t.templates[templateName] |
|
|
|
|
tmpl, ok := t.layouts[layoutName] |
|
|
|
|
if !ok { |
|
|
|
|
return fmt.Errorf("the template '%s' does not exist", templateName) |
|
|
|
|
return ErrLayoutNotFound |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Create a buffer to temporarily write to and check if any errors were encountered.
|
|
|
|
|
buf := t.pool.Get() |
|
|
|
|
defer t.pool.Put(buf) |
|
|
|
|
|
|
|
|
|
if err := tmpl.ExecuteTemplate(buf, templateName, data); err != nil { |
|
|
|
|
if err := tmpl.ExecuteTemplate(buf, layoutName, data); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -126,14 +119,17 @@ func (t *TemplateService) Render(w io.Writer, templateName string, data interfac
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NewTemplateService returns a new Service
|
|
|
|
|
func NewTemplateService(funcs ...OptionFunc) *TemplateService { |
|
|
|
|
func NewTemplateService(loader Loader, funcs ...OptionFunc) *TemplateService { |
|
|
|
|
options := defaultOptions() |
|
|
|
|
for _, f := range funcs { |
|
|
|
|
f(options) |
|
|
|
|
} |
|
|
|
|
return &TemplateService{ |
|
|
|
|
templates: make(map[string]*template.Template), |
|
|
|
|
pool: bpool.NewBufferPool(options.PoolSize), |
|
|
|
|
helpers: options.Helpers, |
|
|
|
|
blocks: make(map[string]string), |
|
|
|
|
layouts: make(map[string]*template.Template), |
|
|
|
|
pool: bpool.NewBufferPool(options.PoolSize), |
|
|
|
|
helpers: options.Helpers, |
|
|
|
|
loader: loader, |
|
|
|
|
devMode: options.DevMode, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|