URI: 
       tpl: Add imageConfig function - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit a49f838cd03f89aa9eb61584c510177fa5cbd3bd
   DIR parent 950034db5cca6df75a2cb39c5e2c2e66dc41c534
  HTML Author: Tristan Rice <rice@fn.lc>
       Date:   Wed, 16 Nov 2016 04:00:45 -0800
       
       tpl: Add imageConfig function
       
       Add imageConfig function which calls image.DecodeConfig and returns the height, width and color mode of the image. (#2677)
       
       This allows for more advanced image shortcodes and templates such as those required by AMP.
       
       layouts/shortcodes/amp-img.html
       ```
       {{ $src := .Get "src" }}
       {{ $config := imageConfig (printf "/static/%s" $src) }}
       
       <amp-img src="{{$src}}"
                  height="{{$config.Height}}"
                  width="{{$config.Width}}"
                  layout="responsive">
       </amp-img>
       ```
       Diffstat:
         M docs/content/templates/functions.md |      12 +++++++++++-
         M hugolib/hugo_sites.go               |       4 +++-
         M tpl/template_funcs.go               |      64 +++++++++++++++++++++++++++++++
         M tpl/template_funcs_test.go          |     106 ++++++++++++++++++++++++++++++
       
       4 files changed, 184 insertions(+), 2 deletions(-)
       ---
   DIR diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md
       @@ -356,7 +356,7 @@ e.g.
               {{ .Content }}
            {{ end }}
        
       -## Files    
       +## Files
        
        ### readDir
        
       @@ -372,6 +372,16 @@ Reads a file from disk and converts it into a string. Note that the filename mus
        
         `{{readFile "README.txt"}}` → `"Hugo Rocks!"`
        
       +### imageConfig
       +Parses the image and returns the height, width and color model.
       +
       +e.g.
       +```
       +{{ with (imageConfig "favicon.ico") }}
       +favicon.ico: {{.Width}} x {{.Height}}
       +{{ end }}
       +```
       +
        ## Math
        
        <table class="table table-bordered">
   DIR diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
       @@ -120,12 +120,14 @@ func (h *HugoSites) getNodes(nodeID string) Nodes {
                return Nodes{}
        }
        
       -// Reset resets the sites, making it ready for a full rebuild.
       +// Reset resets the sites and template caches, making it ready for a full rebuild.
        func (h *HugoSites) reset() {
                h.nodeMap = make(map[string]Nodes)
                for i, s := range h.Sites {
                        h.Sites[i] = s.reset()
                }
       +
       +        tpl.ResetCaches()
        }
        
        func (h *HugoSites) reCreateFromConfig() error {
   DIR diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go
       @@ -26,6 +26,7 @@ import (
                "fmt"
                "html"
                "html/template"
       +        "image"
                "math/rand"
                "net/url"
                "os"
       @@ -45,6 +46,11 @@ import (
                "github.com/spf13/hugo/hugofs"
                jww "github.com/spf13/jwalterweatherman"
                "github.com/spf13/viper"
       +
       +        // Importing image codecs for image.DecodeConfig
       +        _ "image/gif"
       +        _ "image/jpeg"
       +        _ "image/png"
        )
        
        var (
       @@ -364,6 +370,63 @@ func intersect(l1, l2 interface{}) (interface{}, error) {
                }
        }
        
       +// ResetCaches resets all caches that might be used during build.
       +func ResetCaches() {
       +        resetImageConfigCache()
       +}
       +
       +// imageConfigCache is a lockable cache for image.Config objects. It must be
       +// locked before reading or writing to config.
       +var imageConfigCache struct {
       +        sync.RWMutex
       +        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() {
       +        imageConfigCache.Lock()
       +        defer imageConfigCache.Unlock()
       +
       +        imageConfigCache.config = map[string]image.Config{}
       +}
       +
       +// imageConfig returns the image.Config for the specified path relative to the
       +// working directory. resetImageConfigCache must be run beforehand.
       +func imageConfig(path interface{}) (image.Config, error) {
       +        filename, err := cast.ToStringE(path)
       +        if err != nil {
       +                return image.Config{}, err
       +        }
       +
       +        if filename == "" {
       +                return image.Config{}, errors.New("imageConfig needs a filename")
       +        }
       +
       +        // Check cache for image config.
       +        imageConfigCache.RLock()
       +        config, ok := imageConfigCache.config[filename]
       +        imageConfigCache.RUnlock()
       +
       +        if ok {
       +                return config, nil
       +        }
       +
       +        f, err := hugofs.WorkingDir().Open(filename)
       +        if err != nil {
       +                return image.Config{}, err
       +        }
       +
       +        config, _, err = image.DecodeConfig(f)
       +
       +        imageConfigCache.Lock()
       +        imageConfigCache.config[filename] = config
       +        imageConfigCache.Unlock()
       +
       +        return config, err
       +}
       +
        // in returns whether v is in the set l.  l may be an array or slice.
        func in(l interface{}, v interface{}) bool {
                lv := reflect.ValueOf(l)
       @@ -1991,6 +2054,7 @@ func initFuncMap() {
                        "htmlEscape":    htmlEscape,
                        "htmlUnescape":  htmlUnescape,
                        "humanize":      humanize,
       +                "imageConfig":   imageConfig,
                        "in":            in,
                        "index":         index,
                        "int":           func(v interface{}) (int, error) { return cast.ToIntE(v) },
   DIR diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go
       @@ -19,6 +19,9 @@ import (
                "errors"
                "fmt"
                "html/template"
       +        "image"
       +        "image/color"
       +        "image/png"
                "math/rand"
                "path"
                "path/filepath"
       @@ -596,6 +599,109 @@ func TestDictionary(t *testing.T) {
                }
        }
        
       +func blankImage(width, height int) []byte {
       +        var buf bytes.Buffer
       +        img := image.NewRGBA(image.Rect(0, 0, width, height))
       +        if err := png.Encode(&buf, img); err != nil {
       +                panic(err)
       +        }
       +        return buf.Bytes()
       +}
       +
       +func TestImageConfig(t *testing.T) {
       +        viper.Reset()
       +        defer viper.Reset()
       +
       +        workingDir := "/home/hugo"
       +
       +        viper.Set("workingDir", workingDir)
       +
       +        fs := &afero.MemMapFs{}
       +        hugofs.InitFs(fs)
       +
       +        for i, this := range []struct {
       +                resetCache bool
       +                path       string
       +                input      []byte
       +                expected   image.Config
       +        }{
       +                {
       +                        resetCache: true,
       +                        path:       "a.png",
       +                        input:      blankImage(10, 10),
       +                        expected: image.Config{
       +                                Width:      10,
       +                                Height:     10,
       +                                ColorModel: color.NRGBAModel,
       +                        },
       +                },
       +                {
       +                        resetCache: false,
       +                        path:       "b.png",
       +                        input:      blankImage(20, 15),
       +                        expected: image.Config{
       +                                Width:      20,
       +                                Height:     15,
       +                                ColorModel: color.NRGBAModel,
       +                        },
       +                },
       +                {
       +                        resetCache: false,
       +                        path:       "a.png",
       +                        input:      blankImage(20, 15),
       +                        expected: image.Config{
       +                                Width:      10,
       +                                Height:     10,
       +                                ColorModel: color.NRGBAModel,
       +                        },
       +                },
       +                {
       +                        resetCache: true,
       +                        path:       "a.png",
       +                        input:      blankImage(20, 15),
       +                        expected: image.Config{
       +                                Width:      20,
       +                                Height:     15,
       +                                ColorModel: color.NRGBAModel,
       +                        },
       +                },
       +        } {
       +                afero.WriteFile(fs, filepath.Join(workingDir, this.path), this.input, 0755)
       +
       +                if this.resetCache {
       +                        resetImageConfigCache()
       +                }
       +
       +                result, err := imageConfig(this.path)
       +                if err != nil {
       +                        t.Errorf("imageConfig returned error: %s", err)
       +                }
       +
       +                if !reflect.DeepEqual(result, this.expected) {
       +                        t.Errorf("[%d] imageConfig: expected '%v', got '%v'", i, this.expected, result)
       +                }
       +
       +                if len(imageConfigCache.config) == 0 {
       +                        t.Error("imageConfigCache should have at least 1 item")
       +                }
       +        }
       +
       +        if _, err := imageConfig(t); err == nil {
       +                t.Error("Expected error from imageConfig when passed invalid path")
       +        }
       +
       +        if _, err := imageConfig("non-existant.png"); err == nil {
       +                t.Error("Expected error from imageConfig when passed non-existant file")
       +        }
       +
       +        // test cache clearing
       +        ResetCaches()
       +
       +        if len(imageConfigCache.config) != 0 {
       +                t.Error("ResetCaches should have cleared imageConfigCache")
       +        }
       +}
       +
        func TestIn(t *testing.T) {
                for i, this := range []struct {
                        v1     interface{}