diff --git a/template/html/loader.go b/template/html/loader.go
new file mode 100644
index 0000000..3eff842
--- /dev/null
+++ b/template/html/loader.go
@@ -0,0 +1,56 @@
+package html
+
+import (
+ "io/ioutil"
+ "path/filepath"
+)
+
+type Loader interface {
+ Load(*TemplateService) error
+}
+
+type DirectoryLoader struct {
+ rootDir string
+}
+
+func (l *DirectoryLoader) Load(srv *TemplateService) error {
+ blockFiles, err := filepath.Glob(filepath.Join(l.rootDir, "blocks", "*.tmpl"))
+ if err != nil {
+ return err
+ }
+
+ for _, f := range blockFiles {
+ blockContent, err := ioutil.ReadFile(f)
+ if err != nil {
+ return err
+ }
+
+ blockName := filepath.Base(f)
+ srv.AddBlock(blockName, string(blockContent))
+ }
+
+ layoutFiles, err := filepath.Glob(filepath.Join(l.rootDir, "layouts", "*.tmpl"))
+ if err != nil {
+ return err
+ }
+
+ // 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 := srv.LoadLayout(templateName, string(templateData)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func NewDirectoryLoader(rootDir string) *DirectoryLoader {
+ return &DirectoryLoader{rootDir}
+}
diff --git a/template/html/option.go b/template/html/option.go
index 988fa3a..a6ad175 100644
--- a/template/html/option.go
+++ b/template/html/option.go
@@ -6,6 +6,7 @@ import "html/template"
type Options struct {
Helpers template.FuncMap
PoolSize int
+ DevMode bool
}
// OptionFunc configures options for the template service
@@ -48,6 +49,14 @@ func WithPoolSize(size int) OptionFunc {
}
}
+// WithDevMode configures the template service
+// to use the development mode (auto reload of templates).
+func WithDevMode(enabled bool) OptionFunc {
+ return func(opts *Options) {
+ opts.DevMode = enabled
+ }
+}
+
func defaultOptions() *Options {
options := &Options{}
funcs := []OptionFunc{
diff --git a/template/html/provider.go b/template/html/provider.go
index 92263a3..13c456d 100644
--- a/template/html/provider.go
+++ b/template/html/provider.go
@@ -4,9 +4,9 @@ import "gitlab.com/wpetit/goweb/service"
// ServiceProvider returns a service.Provider for the
// the HTML template service implementation
-func ServiceProvider(templateDir string, funcs ...OptionFunc) service.Provider {
- templateService := NewTemplateService(funcs...)
- err := templateService.LoadTemplatesDir(templateDir)
+func ServiceProvider(loader Loader, funcs ...OptionFunc) service.Provider {
+ templateService := NewTemplateService(loader, funcs...)
+ err := templateService.Load()
return func(container *service.Container) (interface{}, error) {
if err != nil {
return nil, err
diff --git a/template/html/service.go b/template/html/service.go
index f6497bc..3c32101 100644
--- a/template/html/service.go
+++ b/template/html/service.go
@@ -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) LoadLayout(name string, layout string, blocks []string) error {
+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, 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,
}
}