package html import ( "errors" "html/template" "io" "net/http" "sync" "github.com/oxtoacart/bpool" ) var ( ErrLayoutNotFound = errors.New("layout not found") ) // TemplateService is a template/html based templating service type TemplateService struct { 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, layoutContent string) error { tmpl := template.New(name) tmpl.Funcs(t.helpers) 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(layoutContent) if err != nil { return err } t.mutex.Lock() t.layouts[name] = tmpl t.mutex.Unlock() 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.layouts[templateName] if !ok { 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 { return err } // Set the header and write the buffer to the http.ResponseWriter w.Header().Set("Content-Type", "text/html; charset=utf-8") _, err := buf.WriteTo(w) return err } // 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.layouts[layoutName] if !ok { 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, layoutName, data); err != nil { return err } _, err := buf.WriteTo(w) return err } // NewTemplateService returns a new Service func NewTemplateService(loader Loader, funcs ...OptionFunc) *TemplateService { options := defaultOptions() for _, f := range funcs { f(options) } return &TemplateService{ blocks: make(map[string]string), layouts: make(map[string]*template.Template), pool: bpool.NewBufferPool(options.PoolSize), helpers: options.Helpers, loader: loader, devMode: options.DevMode, } }