tplimpl: Refactor imageConfig into a struct

Updates #2701
This commit is contained in:
Bjørn Erik Pedersen 2017-02-17 14:22:40 +01:00
parent c507e2717d
commit 77cbe4d60b
3 changed files with 34 additions and 79 deletions

View file

@ -190,8 +190,6 @@ func (h *HugoSites) reset() {
for i, s := range h.Sites { for i, s := range h.Sites {
h.Sites[i] = s.reset() h.Sites[i] = s.reset()
} }
tplimpl.ResetCaches()
} }
func (h *HugoSites) createSitesFromConfig() error { func (h *HugoSites) createSitesFromConfig() error {

View file

@ -40,6 +40,8 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/spf13/hugo/hugofs"
"github.com/bep/inflect" "github.com/bep/inflect"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/spf13/cast" "github.com/spf13/cast"
@ -57,6 +59,7 @@ import (
type templateFuncster struct { type templateFuncster struct {
funcMap template.FuncMap funcMap template.FuncMap
cachedPartials partialCache cachedPartials partialCache
image *imageHandler
*deps.Deps *deps.Deps
} }
@ -64,6 +67,7 @@ func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
return &templateFuncster{ return &templateFuncster{
Deps: deps, Deps: deps,
cachedPartials: partialCache{p: make(map[string]template.HTML)}, cachedPartials: partialCache{p: make(map[string]template.HTML)},
image: &imageHandler{fs: deps.Fs, imageConfigCache: map[string]image.Config{}},
} }
} }
@ -395,64 +399,43 @@ func intersect(l1, l2 interface{}) (interface{}, error) {
} }
} }
// ResetCaches resets all caches that might be used during build. type imageHandler struct {
// TODO(bep) globals move image config cache to funcster imageConfigCache map[string]image.Config
func ResetCaches() {
resetImageConfigCache()
}
// imageConfigCache is a lockable cache for image.Config objects. It must be
// locked before reading or writing to config.
type imageConfigCache struct {
config map[string]image.Config
sync.RWMutex sync.RWMutex
} fs *hugofs.Fs
var defaultImageConfigCache = imageConfigCache{
config: map[string]image.Config{},
}
// resetImageConfigCache initializes and resets the imageConfig cache for the
// imageConfig template function. This should be run once before every batch of
// template renderers so the cache is cleared for new data.
func resetImageConfigCache() {
defaultImageConfigCache.Lock()
defer defaultImageConfigCache.Unlock()
defaultImageConfigCache.config = map[string]image.Config{}
} }
// imageConfig returns the image.Config for the specified path relative to the // imageConfig returns the image.Config for the specified path relative to the
// working directory. resetImageConfigCache must be run beforehand. // working directory.
func (t *templateFuncster) imageConfig(path interface{}) (image.Config, error) { func (ic *imageHandler) config(path interface{}) (image.Config, error) {
filename, err := cast.ToStringE(path) filename, err := cast.ToStringE(path)
if err != nil { if err != nil {
return image.Config{}, err return image.Config{}, err
} }
if filename == "" { if filename == "" {
return image.Config{}, errors.New("imageConfig needs a filename") return image.Config{}, errors.New("config needs a filename")
} }
// Check cache for image config. // Check cache for image config.
defaultImageConfigCache.RLock() ic.RLock()
config, ok := defaultImageConfigCache.config[filename] config, ok := ic.imageConfigCache[filename]
defaultImageConfigCache.RUnlock() ic.RUnlock()
if ok { if ok {
return config, nil return config, nil
} }
f, err := t.Fs.WorkingDir.Open(filename) f, err := ic.fs.WorkingDir.Open(filename)
if err != nil { if err != nil {
return image.Config{}, err return image.Config{}, err
} }
config, _, err = image.DecodeConfig(f) config, _, err = image.DecodeConfig(f)
defaultImageConfigCache.Lock() ic.Lock()
defaultImageConfigCache.config[filename] = config ic.imageConfigCache[filename] = config
defaultImageConfigCache.Unlock() ic.Unlock()
return config, err return config, err
} }
@ -2144,7 +2127,7 @@ func (t *templateFuncster) initFuncMap() {
"htmlEscape": htmlEscape, "htmlEscape": htmlEscape,
"htmlUnescape": htmlUnescape, "htmlUnescape": htmlUnescape,
"humanize": humanize, "humanize": humanize,
"imageConfig": t.imageConfig, "imageConfig": t.image.config,
"in": in, "in": in,
"index": index, "index": index,
"int": func(v interface{}) (int, error) { return cast.ToIntE(v) }, "int": func(v interface{}) (int, error) { return cast.ToIntE(v) },

View file

@ -667,16 +667,13 @@ func TestImageConfig(t *testing.T) {
f := newTestFuncsterWithViper(v) f := newTestFuncsterWithViper(v)
for i, this := range []struct { for i, this := range []struct {
resetCache bool path string
path string input []byte
input []byte expected image.Config
expected image.Config
}{ }{
// Make sure that the cache is initialized by default.
{ {
resetCache: false, path: "a.png",
path: "a.png", input: blankImage(10, 10),
input: blankImage(10, 10),
expected: image.Config{ expected: image.Config{
Width: 10, Width: 10,
Height: 10, Height: 10,
@ -684,9 +681,8 @@ func TestImageConfig(t *testing.T) {
}, },
}, },
{ {
resetCache: true, path: "a.png",
path: "a.png", input: blankImage(10, 10),
input: blankImage(10, 10),
expected: image.Config{ expected: image.Config{
Width: 10, Width: 10,
Height: 10, Height: 10,
@ -694,9 +690,8 @@ func TestImageConfig(t *testing.T) {
}, },
}, },
{ {
resetCache: false, path: "b.png",
path: "b.png", input: blankImage(20, 15),
input: blankImage(20, 15),
expected: image.Config{ expected: image.Config{
Width: 20, Width: 20,
Height: 15, Height: 15,
@ -704,33 +699,18 @@ func TestImageConfig(t *testing.T) {
}, },
}, },
{ {
resetCache: false, path: "a.png",
path: "a.png", input: blankImage(20, 15),
input: blankImage(20, 15),
expected: image.Config{ expected: image.Config{
Width: 10, Width: 10,
Height: 10, Height: 10,
ColorModel: color.NRGBAModel, ColorModel: color.NRGBAModel,
}, },
}, },
{
resetCache: true,
path: "a.png",
input: blankImage(20, 15),
expected: image.Config{
Width: 20,
Height: 15,
ColorModel: color.NRGBAModel,
},
},
} { } {
afero.WriteFile(f.Fs.Source, filepath.Join(workingDir, this.path), this.input, 0755) afero.WriteFile(f.Fs.Source, filepath.Join(workingDir, this.path), this.input, 0755)
if this.resetCache { result, err := f.image.config(this.path)
resetImageConfigCache()
}
result, err := f.imageConfig(this.path)
if err != nil { if err != nil {
t.Errorf("imageConfig returned error: %s", err) t.Errorf("imageConfig returned error: %s", err)
} }
@ -739,29 +719,23 @@ func TestImageConfig(t *testing.T) {
t.Errorf("[%d] imageConfig: expected '%v', got '%v'", i, this.expected, result) t.Errorf("[%d] imageConfig: expected '%v', got '%v'", i, this.expected, result)
} }
if len(defaultImageConfigCache.config) == 0 { if len(f.image.imageConfigCache) == 0 {
t.Error("defaultImageConfigCache should have at least 1 item") t.Error("defaultImageConfigCache should have at least 1 item")
} }
} }
if _, err := f.imageConfig(t); err == nil { if _, err := f.image.config(t); err == nil {
t.Error("Expected error from imageConfig when passed invalid path") t.Error("Expected error from imageConfig when passed invalid path")
} }
if _, err := f.imageConfig("non-existent.png"); err == nil { if _, err := f.image.config("non-existent.png"); err == nil {
t.Error("Expected error from imageConfig when passed non-existent file") t.Error("Expected error from imageConfig when passed non-existent file")
} }
if _, err := f.imageConfig(""); err == nil { if _, err := f.image.config(""); err == nil {
t.Error("Expected error from imageConfig when passed empty path") t.Error("Expected error from imageConfig when passed empty path")
} }
// test cache clearing
ResetCaches()
if len(defaultImageConfigCache.config) != 0 {
t.Error("ResetCaches should have cleared defaultImageConfigCache")
}
} }
func TestIn(t *testing.T) { func TestIn(t *testing.T) {