Reorganized directories and packages.
This commit is contained in:
parent
1018d82d13
commit
72ce41c828
61 changed files with 83 additions and 83 deletions
233
pkg/services/template_renderer.go
Normal file
233
pkg/services/template_renderer.go
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/mikestefanello/pagoda/config"
|
||||
"github.com/mikestefanello/pagoda/pkg/funcmap"
|
||||
)
|
||||
|
||||
type (
|
||||
// TemplateRenderer provides a flexible and easy to use method of rendering simple templates or complex sets of
|
||||
// templates while also providing caching and/or hot-reloading depending on your current environment
|
||||
TemplateRenderer struct {
|
||||
// templateCache stores a cache of parsed page templates
|
||||
templateCache sync.Map
|
||||
|
||||
// funcMap stores the template function map
|
||||
funcMap template.FuncMap
|
||||
|
||||
// templatePath stores the complete path to the templates directory
|
||||
templatesPath string
|
||||
|
||||
// config stores application configuration
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
// TemplateParsed is a wrapper around parsed templates which are stored in the TemplateRenderer cache
|
||||
TemplateParsed struct {
|
||||
// Template is the parsed template
|
||||
Template *template.Template
|
||||
|
||||
// build stores the build data used to parse the template
|
||||
build *templateBuild
|
||||
}
|
||||
|
||||
// templateBuild stores the build data used to parse a template
|
||||
templateBuild struct {
|
||||
group string
|
||||
key string
|
||||
base string
|
||||
files []string
|
||||
directories []string
|
||||
}
|
||||
|
||||
// templateBuilder handles chaining a template parse operation
|
||||
templateBuilder struct {
|
||||
build *templateBuild
|
||||
renderer *TemplateRenderer
|
||||
}
|
||||
)
|
||||
|
||||
// NewTemplateRenderer creates a new TemplateRenderer
|
||||
func NewTemplateRenderer(cfg *config.Config) *TemplateRenderer {
|
||||
t := &TemplateRenderer{
|
||||
templateCache: sync.Map{},
|
||||
funcMap: funcmap.GetFuncMap(),
|
||||
config: cfg,
|
||||
}
|
||||
|
||||
// Gets the complete templates directory path
|
||||
// This is needed in case this is called from a package outside of main, such as within tests
|
||||
_, b, _, _ := runtime.Caller(0)
|
||||
d := path.Join(path.Dir(b))
|
||||
t.templatesPath = filepath.Join(filepath.Dir(d), config.TemplateDir)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Parse creates a template build operation
|
||||
func (t *TemplateRenderer) Parse() *templateBuilder {
|
||||
return &templateBuilder{
|
||||
renderer: t,
|
||||
build: &templateBuild{},
|
||||
}
|
||||
}
|
||||
|
||||
// GetTemplatesPath gets the complete path to the templates directory
|
||||
func (t *TemplateRenderer) GetTemplatesPath() string {
|
||||
return t.templatesPath
|
||||
}
|
||||
|
||||
// getCacheKey gets a cache key for a given group and ID
|
||||
func (t *TemplateRenderer) getCacheKey(group, key string) string {
|
||||
if group != "" {
|
||||
return fmt.Sprintf("%s:%s", group, key)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// parse parses a set of templates and caches them for quick execution
|
||||
// If the application environment is set to local, the cache will be bypassed and templates will be
|
||||
// parsed upon each request so hot-reloading is possible without restarts.
|
||||
// Also included will be the function map provided by the funcmap package.
|
||||
func (t *TemplateRenderer) parse(build *templateBuild) (*TemplateParsed, error) {
|
||||
var tp *TemplateParsed
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case build.key == "":
|
||||
return nil, errors.New("cannot parse template without key")
|
||||
case len(build.files) == 0 && len(build.directories) == 0:
|
||||
return nil, errors.New("cannot parse template without files or directories")
|
||||
case build.base == "":
|
||||
return nil, errors.New("cannot parse template without base")
|
||||
}
|
||||
|
||||
// Generate the cache key
|
||||
cacheKey := t.getCacheKey(build.group, build.key)
|
||||
|
||||
// Check if the template has not yet been parsed or if the app environment is local, so that
|
||||
// templates reflect changes without having the restart the server
|
||||
if tp, err = t.Load(build.group, build.key); err != nil || t.config.App.Environment == config.EnvLocal {
|
||||
// Initialize the parsed template with the function map
|
||||
parsed := template.New(build.base + config.TemplateExt).
|
||||
Funcs(t.funcMap)
|
||||
|
||||
// Parse all files provided
|
||||
if len(build.files) > 0 {
|
||||
for k, v := range build.files {
|
||||
build.files[k] = fmt.Sprintf("%s/%s%s", t.templatesPath, v, config.TemplateExt)
|
||||
}
|
||||
|
||||
parsed, err = parsed.ParseFiles(build.files...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Parse all templates within the provided directories
|
||||
for _, dir := range build.directories {
|
||||
dir = fmt.Sprintf("%s/%s/*%s", t.templatesPath, dir, config.TemplateExt)
|
||||
parsed, err = parsed.ParseGlob(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Store the template so this process only happens once
|
||||
tp = &TemplateParsed{
|
||||
Template: parsed,
|
||||
build: build,
|
||||
}
|
||||
t.templateCache.Store(cacheKey, tp)
|
||||
}
|
||||
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
// Load loads a template from the cache
|
||||
func (t *TemplateRenderer) Load(group, key string) (*TemplateParsed, error) {
|
||||
load, ok := t.templateCache.Load(t.getCacheKey(group, key))
|
||||
if !ok {
|
||||
return nil, errors.New("uncached page template requested")
|
||||
}
|
||||
|
||||
tmpl, ok := load.(*TemplateParsed)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to cast cached template")
|
||||
}
|
||||
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
// Execute executes a template with the given data and provides the output
|
||||
func (t *TemplateParsed) Execute(data interface{}) (*bytes.Buffer, error) {
|
||||
if t.Template == nil {
|
||||
return nil, errors.New("cannot execute template: template not initialized")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err := t.Template.ExecuteTemplate(buf, t.build.base+config.TemplateExt, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Group sets the cache group for the template being built
|
||||
func (t *templateBuilder) Group(group string) *templateBuilder {
|
||||
t.build.group = group
|
||||
return t
|
||||
}
|
||||
|
||||
// Key sets the cache key for the template being built
|
||||
func (t *templateBuilder) Key(key string) *templateBuilder {
|
||||
t.build.key = key
|
||||
return t
|
||||
}
|
||||
|
||||
// Base sets the name of the base template to be used during template parsing and execution.
|
||||
// This should be only the file name without a directory or extension.
|
||||
func (t *templateBuilder) Base(base string) *templateBuilder {
|
||||
t.build.base = base
|
||||
return t
|
||||
}
|
||||
|
||||
// Files sets a list of template files to include in the parse.
|
||||
// This should not include the file extension and the paths should be relative to the templates directory.
|
||||
func (t *templateBuilder) Files(files ...string) *templateBuilder {
|
||||
t.build.files = files
|
||||
return t
|
||||
}
|
||||
|
||||
// Directories sets a list of directories that all template files within will be parsed.
|
||||
// The paths should be relative to the templates directory.
|
||||
func (t *templateBuilder) Directories(directories ...string) *templateBuilder {
|
||||
t.build.directories = directories
|
||||
return t
|
||||
}
|
||||
|
||||
// Store parsed the templates and stores them in the cache
|
||||
func (t *templateBuilder) Store() (*TemplateParsed, error) {
|
||||
return t.renderer.parse(t.build)
|
||||
}
|
||||
|
||||
// Execute executes the template with the given data.
|
||||
// If the template has not already been cached, this will parse and cache the template
|
||||
func (t *templateBuilder) Execute(data interface{}) (*bytes.Buffer, error) {
|
||||
tp, err := t.Store()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tp.Execute(data)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue