URI: 
       Add a newScratch template func - 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 2b8d907ab731627f4e2a30442cd729064516c8bb
   DIR parent 43338c3a99769eb7d0df0c12559b8b3d42b67dba
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Fri,  6 Jul 2018 14:12:10 +0200
       
       Add a newScratch template func
       
       Fixes #4685
       
       Diffstat:
         A common/maps/scratch.go              |     135 +++++++++++++++++++++++++++++++
         A common/maps/scratch_test.go         |     170 +++++++++++++++++++++++++++++++
         A common/math/math.go                 |     135 +++++++++++++++++++++++++++++++
         A common/math/math_test.go            |     109 +++++++++++++++++++++++++++++++
         M docs/content/en/functions/scratch.… |       3 +++
         M hugolib/page.go                     |       6 +++---
         M hugolib/page_test.go                |      27 +++++++++++++++++++++++++++
         D hugolib/scratch.go                  |     135 -------------------------------
         D hugolib/scratch_test.go             |     170 -------------------------------
         M hugolib/shortcode.go                |       7 ++++---
         M hugolib/site.go                     |       3 ++-
         M tpl/collections/collections.go      |       7 +++++++
         M tpl/collections/init.go             |       8 ++++++++
         M tpl/math/math.go                    |     127 ++-----------------------------
         M tpl/math/math_test.go               |      87 -------------------------------
         M tpl/tplimpl/template_funcs_test.go  |       1 -
       
       16 files changed, 609 insertions(+), 521 deletions(-)
       ---
   DIR diff --git a/common/maps/scratch.go b/common/maps/scratch.go
       @@ -0,0 +1,135 @@
       +// Copyright 2018 The Hugo Authors. All rights reserved.
       +//
       +// Licensed under the Apache License, Version 2.0 (the "License");
       +// you may not use this file except in compliance with the License.
       +// You may obtain a copy of the License at
       +// http://www.apache.org/licenses/LICENSE-2.0
       +//
       +// Unless required by applicable law or agreed to in writing, software
       +// distributed under the License is distributed on an "AS IS" BASIS,
       +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       +// See the License for the specific language governing permissions and
       +// limitations under the License.
       +
       +package maps
       +
       +import (
       +        "reflect"
       +        "sort"
       +        "sync"
       +
       +        "github.com/gohugoio/hugo/common/math"
       +)
       +
       +// Scratch is a writable context used for stateful operations in Page/Node rendering.
       +type Scratch struct {
       +        values map[string]interface{}
       +        mu     sync.RWMutex
       +}
       +
       +// Add will, for single values, add (using the + operator) the addend to the existing addend (if found).
       +// Supports numeric values and strings.
       +//
       +// If the first add for a key is an array or slice, then the next value(s) will be appended.
       +func (c *Scratch) Add(key string, newAddend interface{}) (string, error) {
       +
       +        var newVal interface{}
       +        c.mu.RLock()
       +        existingAddend, found := c.values[key]
       +        c.mu.RUnlock()
       +        if found {
       +                var err error
       +
       +                addendV := reflect.ValueOf(existingAddend)
       +
       +                if addendV.Kind() == reflect.Slice || addendV.Kind() == reflect.Array {
       +                        nav := reflect.ValueOf(newAddend)
       +                        if nav.Kind() == reflect.Slice || nav.Kind() == reflect.Array {
       +                                newVal = reflect.AppendSlice(addendV, nav).Interface()
       +                        } else {
       +                                newVal = reflect.Append(addendV, nav).Interface()
       +                        }
       +                } else {
       +                        newVal, err = math.DoArithmetic(existingAddend, newAddend, '+')
       +                        if err != nil {
       +                                return "", err
       +                        }
       +                }
       +        } else {
       +                newVal = newAddend
       +        }
       +        c.mu.Lock()
       +        c.values[key] = newVal
       +        c.mu.Unlock()
       +        return "", nil // have to return something to make it work with the Go templates
       +}
       +
       +// Set stores a value with the given key in the Node context.
       +// This value can later be retrieved with Get.
       +func (c *Scratch) Set(key string, value interface{}) string {
       +        c.mu.Lock()
       +        c.values[key] = value
       +        c.mu.Unlock()
       +        return ""
       +}
       +
       +// Reset deletes the given key
       +func (c *Scratch) Delete(key string) string {
       +        c.mu.Lock()
       +        delete(c.values, key)
       +        c.mu.Unlock()
       +        return ""
       +}
       +
       +// Get returns a value previously set by Add or Set
       +func (c *Scratch) Get(key string) interface{} {
       +        c.mu.RLock()
       +        val := c.values[key]
       +        c.mu.RUnlock()
       +
       +        return val
       +}
       +
       +// SetInMap stores a value to a map with the given key in the Node context.
       +// This map can later be retrieved with GetSortedMapValues.
       +func (c *Scratch) SetInMap(key string, mapKey string, value interface{}) string {
       +        c.mu.Lock()
       +        _, found := c.values[key]
       +        if !found {
       +                c.values[key] = make(map[string]interface{})
       +        }
       +
       +        c.values[key].(map[string]interface{})[mapKey] = value
       +        c.mu.Unlock()
       +        return ""
       +}
       +
       +// GetSortedMapValues returns a sorted map previously filled with SetInMap
       +func (c *Scratch) GetSortedMapValues(key string) interface{} {
       +        c.mu.RLock()
       +
       +        if c.values[key] == nil {
       +                c.mu.RUnlock()
       +                return nil
       +        }
       +
       +        unsortedMap := c.values[key].(map[string]interface{})
       +        c.mu.RUnlock()
       +        var keys []string
       +        for mapKey := range unsortedMap {
       +                keys = append(keys, mapKey)
       +        }
       +
       +        sort.Strings(keys)
       +
       +        sortedArray := make([]interface{}, len(unsortedMap))
       +        for i, mapKey := range keys {
       +                sortedArray[i] = unsortedMap[mapKey]
       +        }
       +
       +        return sortedArray
       +}
       +
       +func NewScratch() *Scratch {
       +        return &Scratch{values: make(map[string]interface{})}
       +}
   DIR diff --git a/common/maps/scratch_test.go b/common/maps/scratch_test.go
       @@ -0,0 +1,170 @@
       +// Copyright 2018 The Hugo Authors. All rights reserved.
       +//
       +// Licensed under the Apache License, Version 2.0 (the "License");
       +// you may not use this file except in compliance with the License.
       +// You may obtain a copy of the License at
       +// http://www.apache.org/licenses/LICENSE-2.0
       +//
       +// Unless required by applicable law or agreed to in writing, software
       +// distributed under the License is distributed on an "AS IS" BASIS,
       +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       +// See the License for the specific language governing permissions and
       +// limitations under the License.
       +
       +package maps
       +
       +import (
       +        "reflect"
       +        "sync"
       +        "testing"
       +
       +        "github.com/stretchr/testify/assert"
       +)
       +
       +func TestScratchAdd(t *testing.T) {
       +        t.Parallel()
       +        scratch := NewScratch()
       +        scratch.Add("int1", 10)
       +        scratch.Add("int1", 20)
       +        scratch.Add("int2", 20)
       +
       +        assert.Equal(t, int64(30), scratch.Get("int1"))
       +        assert.Equal(t, 20, scratch.Get("int2"))
       +
       +        scratch.Add("float1", float64(10.5))
       +        scratch.Add("float1", float64(20.1))
       +
       +        assert.Equal(t, float64(30.6), scratch.Get("float1"))
       +
       +        scratch.Add("string1", "Hello ")
       +        scratch.Add("string1", "big ")
       +        scratch.Add("string1", "World!")
       +
       +        assert.Equal(t, "Hello big World!", scratch.Get("string1"))
       +
       +        scratch.Add("scratch", scratch)
       +        _, err := scratch.Add("scratch", scratch)
       +
       +        if err == nil {
       +                t.Errorf("Expected error from invalid arithmetic")
       +        }
       +
       +}
       +
       +func TestScratchAddSlice(t *testing.T) {
       +        t.Parallel()
       +        scratch := NewScratch()
       +
       +        _, err := scratch.Add("intSlice", []int{1, 2})
       +        assert.Nil(t, err)
       +        _, err = scratch.Add("intSlice", 3)
       +        assert.Nil(t, err)
       +
       +        sl := scratch.Get("intSlice")
       +        expected := []int{1, 2, 3}
       +
       +        if !reflect.DeepEqual(expected, sl) {
       +                t.Errorf("Slice difference, go %q expected %q", sl, expected)
       +        }
       +
       +        _, err = scratch.Add("intSlice", []int{4, 5})
       +
       +        assert.Nil(t, err)
       +
       +        sl = scratch.Get("intSlice")
       +        expected = []int{1, 2, 3, 4, 5}
       +
       +        if !reflect.DeepEqual(expected, sl) {
       +                t.Errorf("Slice difference, go %q expected %q", sl, expected)
       +        }
       +
       +}
       +
       +func TestScratchSet(t *testing.T) {
       +        t.Parallel()
       +        scratch := NewScratch()
       +        scratch.Set("key", "val")
       +        assert.Equal(t, "val", scratch.Get("key"))
       +}
       +
       +func TestScratchDelete(t *testing.T) {
       +        t.Parallel()
       +        scratch := NewScratch()
       +        scratch.Set("key", "val")
       +        scratch.Delete("key")
       +        scratch.Add("key", "Lucy Parsons")
       +        assert.Equal(t, "Lucy Parsons", scratch.Get("key"))
       +}
       +
       +// Issue #2005
       +func TestScratchInParallel(t *testing.T) {
       +        var wg sync.WaitGroup
       +        scratch := NewScratch()
       +        key := "counter"
       +        scratch.Set(key, int64(1))
       +        for i := 1; i <= 10; i++ {
       +                wg.Add(1)
       +                go func(j int) {
       +                        for k := 0; k < 10; k++ {
       +                                newVal := int64(k + j)
       +
       +                                _, err := scratch.Add(key, newVal)
       +                                if err != nil {
       +                                        t.Errorf("Got err %s", err)
       +                                }
       +
       +                                scratch.Set(key, newVal)
       +
       +                                val := scratch.Get(key)
       +
       +                                if counter, ok := val.(int64); ok {
       +                                        if counter < 1 {
       +                                                t.Errorf("Got %d", counter)
       +                                        }
       +                                } else {
       +                                        t.Errorf("Got %T", val)
       +                                }
       +                        }
       +                        wg.Done()
       +                }(i)
       +        }
       +        wg.Wait()
       +}
       +
       +func TestScratchGet(t *testing.T) {
       +        t.Parallel()
       +        scratch := NewScratch()
       +        nothing := scratch.Get("nothing")
       +        if nothing != nil {
       +                t.Errorf("Should not return anything, but got %v", nothing)
       +        }
       +}
       +
       +func TestScratchSetInMap(t *testing.T) {
       +        t.Parallel()
       +        scratch := NewScratch()
       +        scratch.SetInMap("key", "lux", "Lux")
       +        scratch.SetInMap("key", "abc", "Abc")
       +        scratch.SetInMap("key", "zyx", "Zyx")
       +        scratch.SetInMap("key", "abc", "Abc (updated)")
       +        scratch.SetInMap("key", "def", "Def")
       +        assert.Equal(t, []interface{}{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"}, scratch.GetSortedMapValues("key"))
       +}
       +
       +func TestScratchGetSortedMapValues(t *testing.T) {
       +        t.Parallel()
       +        scratch := NewScratch()
       +        nothing := scratch.GetSortedMapValues("nothing")
       +        if nothing != nil {
       +                t.Errorf("Should not return anything, but got %v", nothing)
       +        }
       +}
       +
       +func BenchmarkScratchGet(b *testing.B) {
       +        scratch := NewScratch()
       +        scratch.Add("A", 1)
       +        b.ResetTimer()
       +        for i := 0; i < b.N; i++ {
       +                scratch.Get("A")
       +        }
       +}
   DIR diff --git a/common/math/math.go b/common/math/math.go
       @@ -0,0 +1,135 @@
       +// Copyright 2018 The Hugo Authors. All rights reserved.
       +//
       +// Licensed under the Apache License, Version 2.0 (the "License");
       +// you may not use this file except in compliance with the License.
       +// You may obtain a copy of the License at
       +// http://www.apache.org/licenses/LICENSE-2.0
       +//
       +// Unless required by applicable law or agreed to in writing, software
       +// distributed under the License is distributed on an "AS IS" BASIS,
       +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       +// See the License for the specific language governing permissions and
       +// limitations under the License.
       +
       +package math
       +
       +import (
       +        "errors"
       +        "reflect"
       +)
       +
       +// DoArithmetic performs arithmetic operations (+,-,*,/) using reflection to
       +// determine the type of the two terms.
       +func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
       +        av := reflect.ValueOf(a)
       +        bv := reflect.ValueOf(b)
       +        var ai, bi int64
       +        var af, bf float64
       +        var au, bu uint64
       +        switch av.Kind() {
       +        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
       +                ai = av.Int()
       +                switch bv.Kind() {
       +                case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
       +                        bi = bv.Int()
       +                case reflect.Float32, reflect.Float64:
       +                        af = float64(ai) // may overflow
       +                        ai = 0
       +                        bf = bv.Float()
       +                case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
       +                        bu = bv.Uint()
       +                        if ai >= 0 {
       +                                au = uint64(ai)
       +                                ai = 0
       +                        } else {
       +                                bi = int64(bu) // may overflow
       +                                bu = 0
       +                        }
       +                default:
       +                        return nil, errors.New("Can't apply the operator to the values")
       +                }
       +        case reflect.Float32, reflect.Float64:
       +                af = av.Float()
       +                switch bv.Kind() {
       +                case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
       +                        bf = float64(bv.Int()) // may overflow
       +                case reflect.Float32, reflect.Float64:
       +                        bf = bv.Float()
       +                case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
       +                        bf = float64(bv.Uint()) // may overflow
       +                default:
       +                        return nil, errors.New("Can't apply the operator to the values")
       +                }
       +        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
       +                au = av.Uint()
       +                switch bv.Kind() {
       +                case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
       +                        bi = bv.Int()
       +                        if bi >= 0 {
       +                                bu = uint64(bi)
       +                                bi = 0
       +                        } else {
       +                                ai = int64(au) // may overflow
       +                                au = 0
       +                        }
       +                case reflect.Float32, reflect.Float64:
       +                        af = float64(au) // may overflow
       +                        au = 0
       +                        bf = bv.Float()
       +                case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
       +                        bu = bv.Uint()
       +                default:
       +                        return nil, errors.New("Can't apply the operator to the values")
       +                }
       +        case reflect.String:
       +                as := av.String()
       +                if bv.Kind() == reflect.String && op == '+' {
       +                        bs := bv.String()
       +                        return as + bs, nil
       +                }
       +                return nil, errors.New("Can't apply the operator to the values")
       +        default:
       +                return nil, errors.New("Can't apply the operator to the values")
       +        }
       +
       +        switch op {
       +        case '+':
       +                if ai != 0 || bi != 0 {
       +                        return ai + bi, nil
       +                } else if af != 0 || bf != 0 {
       +                        return af + bf, nil
       +                } else if au != 0 || bu != 0 {
       +                        return au + bu, nil
       +                }
       +                return 0, nil
       +        case '-':
       +                if ai != 0 || bi != 0 {
       +                        return ai - bi, nil
       +                } else if af != 0 || bf != 0 {
       +                        return af - bf, nil
       +                } else if au != 0 || bu != 0 {
       +                        return au - bu, nil
       +                }
       +                return 0, nil
       +        case '*':
       +                if ai != 0 || bi != 0 {
       +                        return ai * bi, nil
       +                } else if af != 0 || bf != 0 {
       +                        return af * bf, nil
       +                } else if au != 0 || bu != 0 {
       +                        return au * bu, nil
       +                }
       +                return 0, nil
       +        case '/':
       +                if bi != 0 {
       +                        return ai / bi, nil
       +                } else if bf != 0 {
       +                        return af / bf, nil
       +                } else if bu != 0 {
       +                        return au / bu, nil
       +                }
       +                return nil, errors.New("Can't divide the value by 0")
       +        default:
       +                return nil, errors.New("There is no such an operation")
       +        }
       +}
   DIR diff --git a/common/math/math_test.go b/common/math/math_test.go
       @@ -0,0 +1,109 @@
       +// Copyright 2018 The Hugo Authors. All rights reserved.
       +//
       +// Licensed under the Apache License, Version 2.0 (the "License");
       +// you may not use this file except in compliance with the License.
       +// You may obtain a copy of the License at
       +// http://www.apache.org/licenses/LICENSE-2.0
       +//
       +// Unless required by applicable law or agreed to in writing, software
       +// distributed under the License is distributed on an "AS IS" BASIS,
       +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       +// See the License for the specific language governing permissions and
       +// limitations under the License.
       +
       +package math
       +
       +import (
       +        "fmt"
       +        "testing"
       +
       +        "github.com/alecthomas/assert"
       +        "github.com/stretchr/testify/require"
       +)
       +
       +func TestDoArithmetic(t *testing.T) {
       +        t.Parallel()
       +
       +        for i, test := range []struct {
       +                a      interface{}
       +                b      interface{}
       +                op     rune
       +                expect interface{}
       +        }{
       +                {3, 2, '+', int64(5)},
       +                {3, 2, '-', int64(1)},
       +                {3, 2, '*', int64(6)},
       +                {3, 2, '/', int64(1)},
       +                {3.0, 2, '+', float64(5)},
       +                {3.0, 2, '-', float64(1)},
       +                {3.0, 2, '*', float64(6)},
       +                {3.0, 2, '/', float64(1.5)},
       +                {3, 2.0, '+', float64(5)},
       +                {3, 2.0, '-', float64(1)},
       +                {3, 2.0, '*', float64(6)},
       +                {3, 2.0, '/', float64(1.5)},
       +                {3.0, 2.0, '+', float64(5)},
       +                {3.0, 2.0, '-', float64(1)},
       +                {3.0, 2.0, '*', float64(6)},
       +                {3.0, 2.0, '/', float64(1.5)},
       +                {uint(3), uint(2), '+', uint64(5)},
       +                {uint(3), uint(2), '-', uint64(1)},
       +                {uint(3), uint(2), '*', uint64(6)},
       +                {uint(3), uint(2), '/', uint64(1)},
       +                {uint(3), 2, '+', uint64(5)},
       +                {uint(3), 2, '-', uint64(1)},
       +                {uint(3), 2, '*', uint64(6)},
       +                {uint(3), 2, '/', uint64(1)},
       +                {3, uint(2), '+', uint64(5)},
       +                {3, uint(2), '-', uint64(1)},
       +                {3, uint(2), '*', uint64(6)},
       +                {3, uint(2), '/', uint64(1)},
       +                {uint(3), -2, '+', int64(1)},
       +                {uint(3), -2, '-', int64(5)},
       +                {uint(3), -2, '*', int64(-6)},
       +                {uint(3), -2, '/', int64(-1)},
       +                {-3, uint(2), '+', int64(-1)},
       +                {-3, uint(2), '-', int64(-5)},
       +                {-3, uint(2), '*', int64(-6)},
       +                {-3, uint(2), '/', int64(-1)},
       +                {uint(3), 2.0, '+', float64(5)},
       +                {uint(3), 2.0, '-', float64(1)},
       +                {uint(3), 2.0, '*', float64(6)},
       +                {uint(3), 2.0, '/', float64(1.5)},
       +                {3.0, uint(2), '+', float64(5)},
       +                {3.0, uint(2), '-', float64(1)},
       +                {3.0, uint(2), '*', float64(6)},
       +                {3.0, uint(2), '/', float64(1.5)},
       +                {0, 0, '+', 0},
       +                {0, 0, '-', 0},
       +                {0, 0, '*', 0},
       +                {"foo", "bar", '+', "foobar"},
       +                {3, 0, '/', false},
       +                {3.0, 0, '/', false},
       +                {3, 0.0, '/', false},
       +                {uint(3), uint(0), '/', false},
       +                {3, uint(0), '/', false},
       +                {-3, uint(0), '/', false},
       +                {uint(3), 0, '/', false},
       +                {3.0, uint(0), '/', false},
       +                {uint(3), 0.0, '/', false},
       +                {3, "foo", '+', false},
       +                {3.0, "foo", '+', false},
       +                {uint(3), "foo", '+', false},
       +                {"foo", 3, '+', false},
       +                {"foo", "bar", '-', false},
       +                {3, 2, '%', false},
       +        } {
       +                errMsg := fmt.Sprintf("[%d] %v", i, test)
       +
       +                result, err := DoArithmetic(test.a, test.b, test.op)
       +
       +                if b, ok := test.expect.(bool); ok && !b {
       +                        require.Error(t, err, errMsg)
       +                        continue
       +                }
       +
       +                require.NoError(t, err, errMsg)
       +                assert.Equal(t, test.expect, result, errMsg)
       +        }
       +}
   DIR diff --git a/docs/content/en/functions/scratch.md b/docs/content/en/functions/scratch.md
       @@ -22,6 +22,9 @@ aliases: [/extras/scratch/,/doc/scratch/]
        
        In most cases you can do okay without `Scratch`, but due to scoping issues, there are many use cases that aren't solvable in Go Templates without `Scratch`'s help.
        
       +`.Scratch` is available as methods on `Page` and `Shortcode`. Since Hugo 0.43 you can also create a locally scoped `Scratch` using the template func `newScratch`.
       +
       +
        {{% note %}}
        See [this Go issue](https://github.com/golang/go/issues/10608) for the main motivation behind Scratch.
        {{% /note %}}
   DIR diff --git a/hugolib/page.go b/hugolib/page.go
       @@ -257,7 +257,7 @@ type Page struct {
        
                layoutDescriptor output.LayoutDescriptor
        
       -        scratch *Scratch
       +        scratch *maps.Scratch
        
                // It would be tempting to use the language set on the Site, but in they way we do
                // multi-site processing, these values may differ during the initial page processing.
       @@ -2052,9 +2052,9 @@ func (p *Page) String() string {
        }
        
        // Scratch returns the writable context associated with this Page.
       -func (p *Page) Scratch() *Scratch {
       +func (p *Page) Scratch() *maps.Scratch {
                if p.scratch == nil {
       -                p.scratch = newScratch()
       +                p.scratch = maps.NewScratch()
                }
                return p.scratch
        }
   DIR diff --git a/hugolib/page_test.go b/hugolib/page_test.go
       @@ -1830,6 +1830,33 @@ Summary: In Chinese, 好 means good.
        
        }
        
       +func TestScratchSite(t *testing.T) {
       +        t.Parallel()
       +
       +        b := newTestSitesBuilder(t)
       +        b.WithSimpleConfigFile().WithTemplatesAdded("index.html", `
       +{{ .Scratch.Set "b" "bv" }}
       +B: {{ .Scratch.Get "b" }}
       +`,
       +                "shortcodes/scratch.html", `
       +{{ .Scratch.Set "c" "cv" }}
       +C: {{ .Scratch.Get "c" }}
       +`,
       +        )
       +
       +        b.WithContentAdded("scratchme.md", `
       +---
       +title: Scratch Me!
       +---
       +
       +{{< scratch >}}
       +`)
       +        b.Build(BuildCfg{})
       +
       +        b.AssertFileContent("public/index.html", "B: bv")
       +        b.AssertFileContent("public/scratchme/index.html", "C: cv")
       +}
       +
        func BenchmarkParsePage(b *testing.B) {
                s := newTestSite(b)
                f, _ := os.Open("testdata/redis.cn.md")
   DIR diff --git a/hugolib/scratch.go b/hugolib/scratch.go
       @@ -1,135 +0,0 @@
       -// Copyright 2015 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package hugolib
       -
       -import (
       -        "reflect"
       -        "sort"
       -        "sync"
       -
       -        "github.com/gohugoio/hugo/tpl/math"
       -)
       -
       -// Scratch is a writable context used for stateful operations in Page/Node rendering.
       -type Scratch struct {
       -        values map[string]interface{}
       -        mu     sync.RWMutex
       -}
       -
       -// Add will, for single values, add (using the + operator) the addend to the existing addend (if found).
       -// Supports numeric values and strings.
       -//
       -// If the first add for a key is an array or slice, then the next value(s) will be appended.
       -func (c *Scratch) Add(key string, newAddend interface{}) (string, error) {
       -
       -        var newVal interface{}
       -        c.mu.RLock()
       -        existingAddend, found := c.values[key]
       -        c.mu.RUnlock()
       -        if found {
       -                var err error
       -
       -                addendV := reflect.ValueOf(existingAddend)
       -
       -                if addendV.Kind() == reflect.Slice || addendV.Kind() == reflect.Array {
       -                        nav := reflect.ValueOf(newAddend)
       -                        if nav.Kind() == reflect.Slice || nav.Kind() == reflect.Array {
       -                                newVal = reflect.AppendSlice(addendV, nav).Interface()
       -                        } else {
       -                                newVal = reflect.Append(addendV, nav).Interface()
       -                        }
       -                } else {
       -                        newVal, err = math.DoArithmetic(existingAddend, newAddend, '+')
       -                        if err != nil {
       -                                return "", err
       -                        }
       -                }
       -        } else {
       -                newVal = newAddend
       -        }
       -        c.mu.Lock()
       -        c.values[key] = newVal
       -        c.mu.Unlock()
       -        return "", nil // have to return something to make it work with the Go templates
       -}
       -
       -// Set stores a value with the given key in the Node context.
       -// This value can later be retrieved with Get.
       -func (c *Scratch) Set(key string, value interface{}) string {
       -        c.mu.Lock()
       -        c.values[key] = value
       -        c.mu.Unlock()
       -        return ""
       -}
       -
       -// Reset deletes the given key
       -func (c *Scratch) Delete(key string) string {
       -        c.mu.Lock()
       -        delete(c.values, key)
       -        c.mu.Unlock()
       -        return ""
       -}
       -
       -// Get returns a value previously set by Add or Set
       -func (c *Scratch) Get(key string) interface{} {
       -        c.mu.RLock()
       -        val := c.values[key]
       -        c.mu.RUnlock()
       -
       -        return val
       -}
       -
       -// SetInMap stores a value to a map with the given key in the Node context.
       -// This map can later be retrieved with GetSortedMapValues.
       -func (c *Scratch) SetInMap(key string, mapKey string, value interface{}) string {
       -        c.mu.Lock()
       -        _, found := c.values[key]
       -        if !found {
       -                c.values[key] = make(map[string]interface{})
       -        }
       -
       -        c.values[key].(map[string]interface{})[mapKey] = value
       -        c.mu.Unlock()
       -        return ""
       -}
       -
       -// GetSortedMapValues returns a sorted map previously filled with SetInMap
       -func (c *Scratch) GetSortedMapValues(key string) interface{} {
       -        c.mu.RLock()
       -
       -        if c.values[key] == nil {
       -                c.mu.RUnlock()
       -                return nil
       -        }
       -
       -        unsortedMap := c.values[key].(map[string]interface{})
       -        c.mu.RUnlock()
       -        var keys []string
       -        for mapKey := range unsortedMap {
       -                keys = append(keys, mapKey)
       -        }
       -
       -        sort.Strings(keys)
       -
       -        sortedArray := make([]interface{}, len(unsortedMap))
       -        for i, mapKey := range keys {
       -                sortedArray[i] = unsortedMap[mapKey]
       -        }
       -
       -        return sortedArray
       -}
       -
       -func newScratch() *Scratch {
       -        return &Scratch{values: make(map[string]interface{})}
       -}
   DIR diff --git a/hugolib/scratch_test.go b/hugolib/scratch_test.go
       @@ -1,170 +0,0 @@
       -// Copyright 2015 The Hugo Authors. All rights reserved.
       -//
       -// Licensed under the Apache License, Version 2.0 (the "License");
       -// you may not use this file except in compliance with the License.
       -// You may obtain a copy of the License at
       -// http://www.apache.org/licenses/LICENSE-2.0
       -//
       -// Unless required by applicable law or agreed to in writing, software
       -// distributed under the License is distributed on an "AS IS" BASIS,
       -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       -// See the License for the specific language governing permissions and
       -// limitations under the License.
       -
       -package hugolib
       -
       -import (
       -        "reflect"
       -        "sync"
       -        "testing"
       -
       -        "github.com/stretchr/testify/assert"
       -)
       -
       -func TestScratchAdd(t *testing.T) {
       -        t.Parallel()
       -        scratch := newScratch()
       -        scratch.Add("int1", 10)
       -        scratch.Add("int1", 20)
       -        scratch.Add("int2", 20)
       -
       -        assert.Equal(t, int64(30), scratch.Get("int1"))
       -        assert.Equal(t, 20, scratch.Get("int2"))
       -
       -        scratch.Add("float1", float64(10.5))
       -        scratch.Add("float1", float64(20.1))
       -
       -        assert.Equal(t, float64(30.6), scratch.Get("float1"))
       -
       -        scratch.Add("string1", "Hello ")
       -        scratch.Add("string1", "big ")
       -        scratch.Add("string1", "World!")
       -
       -        assert.Equal(t, "Hello big World!", scratch.Get("string1"))
       -
       -        scratch.Add("scratch", scratch)
       -        _, err := scratch.Add("scratch", scratch)
       -
       -        if err == nil {
       -                t.Errorf("Expected error from invalid arithmetic")
       -        }
       -
       -}
       -
       -func TestScratchAddSlice(t *testing.T) {
       -        t.Parallel()
       -        scratch := newScratch()
       -
       -        _, err := scratch.Add("intSlice", []int{1, 2})
       -        assert.Nil(t, err)
       -        _, err = scratch.Add("intSlice", 3)
       -        assert.Nil(t, err)
       -
       -        sl := scratch.Get("intSlice")
       -        expected := []int{1, 2, 3}
       -
       -        if !reflect.DeepEqual(expected, sl) {
       -                t.Errorf("Slice difference, go %q expected %q", sl, expected)
       -        }
       -
       -        _, err = scratch.Add("intSlice", []int{4, 5})
       -
       -        assert.Nil(t, err)
       -
       -        sl = scratch.Get("intSlice")
       -        expected = []int{1, 2, 3, 4, 5}
       -
       -        if !reflect.DeepEqual(expected, sl) {
       -                t.Errorf("Slice difference, go %q expected %q", sl, expected)
       -        }
       -
       -}
       -
       -func TestScratchSet(t *testing.T) {
       -        t.Parallel()
       -        scratch := newScratch()
       -        scratch.Set("key", "val")
       -        assert.Equal(t, "val", scratch.Get("key"))
       -}
       -
       -func TestScratchDelete(t *testing.T) {
       -        t.Parallel()
       -        scratch := newScratch()
       -        scratch.Set("key", "val")
       -        scratch.Delete("key")
       -        scratch.Add("key", "Lucy Parsons")
       -        assert.Equal(t, "Lucy Parsons", scratch.Get("key"))
       -}
       -
       -// Issue #2005
       -func TestScratchInParallel(t *testing.T) {
       -        var wg sync.WaitGroup
       -        scratch := newScratch()
       -        key := "counter"
       -        scratch.Set(key, int64(1))
       -        for i := 1; i <= 10; i++ {
       -                wg.Add(1)
       -                go func(j int) {
       -                        for k := 0; k < 10; k++ {
       -                                newVal := int64(k + j)
       -
       -                                _, err := scratch.Add(key, newVal)
       -                                if err != nil {
       -                                        t.Errorf("Got err %s", err)
       -                                }
       -
       -                                scratch.Set(key, newVal)
       -
       -                                val := scratch.Get(key)
       -
       -                                if counter, ok := val.(int64); ok {
       -                                        if counter < 1 {
       -                                                t.Errorf("Got %d", counter)
       -                                        }
       -                                } else {
       -                                        t.Errorf("Got %T", val)
       -                                }
       -                        }
       -                        wg.Done()
       -                }(i)
       -        }
       -        wg.Wait()
       -}
       -
       -func TestScratchGet(t *testing.T) {
       -        t.Parallel()
       -        scratch := newScratch()
       -        nothing := scratch.Get("nothing")
       -        if nothing != nil {
       -                t.Errorf("Should not return anything, but got %v", nothing)
       -        }
       -}
       -
       -func TestScratchSetInMap(t *testing.T) {
       -        t.Parallel()
       -        scratch := newScratch()
       -        scratch.SetInMap("key", "lux", "Lux")
       -        scratch.SetInMap("key", "abc", "Abc")
       -        scratch.SetInMap("key", "zyx", "Zyx")
       -        scratch.SetInMap("key", "abc", "Abc (updated)")
       -        scratch.SetInMap("key", "def", "Def")
       -        assert.Equal(t, []interface{}{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"}, scratch.GetSortedMapValues("key"))
       -}
       -
       -func TestScratchGetSortedMapValues(t *testing.T) {
       -        t.Parallel()
       -        scratch := newScratch()
       -        nothing := scratch.GetSortedMapValues("nothing")
       -        if nothing != nil {
       -                t.Errorf("Should not return anything, but got %v", nothing)
       -        }
       -}
       -
       -func BenchmarkScratchGet(b *testing.B) {
       -        scratch := newScratch()
       -        scratch.Add("A", 1)
       -        b.ResetTimer()
       -        for i := 0; i < b.N; i++ {
       -                scratch.Get("A")
       -        }
       -}
   DIR diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
       @@ -24,6 +24,7 @@ import (
                "strings"
                "sync"
        
       +        "github.com/gohugoio/hugo/common/maps"
                "github.com/gohugoio/hugo/output"
        
                "github.com/gohugoio/hugo/media"
       @@ -45,7 +46,7 @@ type ShortcodeWithPage struct {
                // this ordinal will represent the position of this shortcode in the page content.
                Ordinal int
        
       -        scratch *Scratch
       +        scratch *maps.Scratch
        }
        
        // Site returns information about the current site.
       @@ -65,9 +66,9 @@ func (scp *ShortcodeWithPage) RelRef(ref string) (string, error) {
        
        // Scratch returns a scratch-pad scoped for this shortcode. This can be used
        // as a temporary storage for variables, counters etc.
       -func (scp *ShortcodeWithPage) Scratch() *Scratch {
       +func (scp *ShortcodeWithPage) Scratch() *maps.Scratch {
                if scp.scratch == nil {
       -                scp.scratch = newScratch()
       +                scp.scratch = maps.NewScratch()
                }
                return scp.scratch
        }
   DIR diff --git a/hugolib/site.go b/hugolib/site.go
       @@ -27,6 +27,7 @@ import (
                "strings"
                "time"
        
       +        "github.com/gohugoio/hugo/common/maps"
                "github.com/gohugoio/hugo/resource"
        
                "github.com/gohugoio/hugo/langs"
       @@ -1509,7 +1510,7 @@ func (s *Site) resetBuildState() {
                for _, p := range s.rawAllPages {
                        p.subSections = Pages{}
                        p.parent = nil
       -                p.scratch = newScratch()
       +                p.scratch = maps.NewScratch()
                        p.mainPageOutput = nil
                }
        }
   DIR diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go
       @@ -23,6 +23,7 @@ import (
                "strings"
                "time"
        
       +        "github.com/gohugoio/hugo/common/maps"
                "github.com/gohugoio/hugo/common/types"
                "github.com/gohugoio/hugo/deps"
                "github.com/gohugoio/hugo/helpers"
       @@ -650,3 +651,9 @@ func (ns *Namespace) Uniq(l interface{}) (interface{}, error) {
        func (ns *Namespace) KeyVals(key interface{}, vals ...interface{}) (types.KeyValues, error) {
                return types.KeyValues{Key: key, Values: vals}, nil
        }
       +
       +// NewScratch creates a new Scratch which can be used to store values in a
       +// thread safe way.
       +func (ns *Namespace) NewScratch() *maps.Scratch {
       +        return maps.NewScratch()
       +}
   DIR diff --git a/tpl/collections/init.go b/tpl/collections/init.go
       @@ -144,6 +144,14 @@ func init() {
                                        {`{{ seq 3 }}`, `[1 2 3]`},
                                },
                        )
       +
       +                ns.AddMethodMapping(ctx.NewScratch,
       +                        []string{"newScratch"},
       +                        [][2]string{
       +                                {`{{ $scratch := newScratch }}{{ $scratch.Add "b" 2 }}{{ $scratch.Add "b" 2 }}{{ $scratch.Get "b" }}`, `4`},
       +                        },
       +                )
       +
                        ns.AddMethodMapping(ctx.Uniq,
                                []string{"uniq"},
                                [][2]string{
   DIR diff --git a/tpl/math/math.go b/tpl/math/math.go
       @@ -16,7 +16,8 @@ package math
        import (
                "errors"
                "math"
       -        "reflect"
       +
       +        _math "github.com/gohugoio/hugo/common/math"
        
                "github.com/spf13/cast"
        )
       @@ -31,7 +32,7 @@ type Namespace struct{}
        
        // Add adds two numbers.
        func (ns *Namespace) Add(a, b interface{}) (interface{}, error) {
       -        return DoArithmetic(a, b, '+')
       +        return _math.DoArithmetic(a, b, '+')
        }
        
        // Ceil returns the least integer value greater than or equal to x.
       @@ -46,7 +47,7 @@ func (ns *Namespace) Ceil(x interface{}) (float64, error) {
        
        // Div divides two numbers.
        func (ns *Namespace) Div(a, b interface{}) (interface{}, error) {
       -        return DoArithmetic(a, b, '/')
       +        return _math.DoArithmetic(a, b, '/')
        }
        
        // Floor returns the greatest integer value less than or equal to x.
       @@ -98,7 +99,7 @@ func (ns *Namespace) ModBool(a, b interface{}) (bool, error) {
        
        // Mul multiplies two numbers.
        func (ns *Namespace) Mul(a, b interface{}) (interface{}, error) {
       -        return DoArithmetic(a, b, '*')
       +        return _math.DoArithmetic(a, b, '*')
        }
        
        // Round returns the nearest integer, rounding half away from zero.
       @@ -113,121 +114,5 @@ func (ns *Namespace) Round(x interface{}) (float64, error) {
        
        // Sub subtracts two numbers.
        func (ns *Namespace) Sub(a, b interface{}) (interface{}, error) {
       -        return DoArithmetic(a, b, '-')
       -}
       -
       -// DoArithmetic performs arithmetic operations (+,-,*,/) using reflection to
       -// determine the type of the two terms.
       -func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
       -        av := reflect.ValueOf(a)
       -        bv := reflect.ValueOf(b)
       -        var ai, bi int64
       -        var af, bf float64
       -        var au, bu uint64
       -        switch av.Kind() {
       -        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
       -                ai = av.Int()
       -                switch bv.Kind() {
       -                case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
       -                        bi = bv.Int()
       -                case reflect.Float32, reflect.Float64:
       -                        af = float64(ai) // may overflow
       -                        ai = 0
       -                        bf = bv.Float()
       -                case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
       -                        bu = bv.Uint()
       -                        if ai >= 0 {
       -                                au = uint64(ai)
       -                                ai = 0
       -                        } else {
       -                                bi = int64(bu) // may overflow
       -                                bu = 0
       -                        }
       -                default:
       -                        return nil, errors.New("Can't apply the operator to the values")
       -                }
       -        case reflect.Float32, reflect.Float64:
       -                af = av.Float()
       -                switch bv.Kind() {
       -                case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
       -                        bf = float64(bv.Int()) // may overflow
       -                case reflect.Float32, reflect.Float64:
       -                        bf = bv.Float()
       -                case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
       -                        bf = float64(bv.Uint()) // may overflow
       -                default:
       -                        return nil, errors.New("Can't apply the operator to the values")
       -                }
       -        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
       -                au = av.Uint()
       -                switch bv.Kind() {
       -                case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
       -                        bi = bv.Int()
       -                        if bi >= 0 {
       -                                bu = uint64(bi)
       -                                bi = 0
       -                        } else {
       -                                ai = int64(au) // may overflow
       -                                au = 0
       -                        }
       -                case reflect.Float32, reflect.Float64:
       -                        af = float64(au) // may overflow
       -                        au = 0
       -                        bf = bv.Float()
       -                case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
       -                        bu = bv.Uint()
       -                default:
       -                        return nil, errors.New("Can't apply the operator to the values")
       -                }
       -        case reflect.String:
       -                as := av.String()
       -                if bv.Kind() == reflect.String && op == '+' {
       -                        bs := bv.String()
       -                        return as + bs, nil
       -                }
       -                return nil, errors.New("Can't apply the operator to the values")
       -        default:
       -                return nil, errors.New("Can't apply the operator to the values")
       -        }
       -
       -        switch op {
       -        case '+':
       -                if ai != 0 || bi != 0 {
       -                        return ai + bi, nil
       -                } else if af != 0 || bf != 0 {
       -                        return af + bf, nil
       -                } else if au != 0 || bu != 0 {
       -                        return au + bu, nil
       -                }
       -                return 0, nil
       -        case '-':
       -                if ai != 0 || bi != 0 {
       -                        return ai - bi, nil
       -                } else if af != 0 || bf != 0 {
       -                        return af - bf, nil
       -                } else if au != 0 || bu != 0 {
       -                        return au - bu, nil
       -                }
       -                return 0, nil
       -        case '*':
       -                if ai != 0 || bi != 0 {
       -                        return ai * bi, nil
       -                } else if af != 0 || bf != 0 {
       -                        return af * bf, nil
       -                } else if au != 0 || bu != 0 {
       -                        return au * bu, nil
       -                }
       -                return 0, nil
       -        case '/':
       -                if bi != 0 {
       -                        return ai / bi, nil
       -                } else if bf != 0 {
       -                        return af / bf, nil
       -                } else if bu != 0 {
       -                        return au / bu, nil
       -                }
       -                return nil, errors.New("Can't divide the value by 0")
       -        default:
       -                return nil, errors.New("There is no such an operation")
       -        }
       +        return _math.DoArithmetic(a, b, '-')
        }
   DIR diff --git a/tpl/math/math_test.go b/tpl/math/math_test.go
       @@ -56,93 +56,6 @@ func TestBasicNSArithmetic(t *testing.T) {
                }
        }
        
       -func TestDoArithmetic(t *testing.T) {
       -        t.Parallel()
       -
       -        for i, test := range []struct {
       -                a      interface{}
       -                b      interface{}
       -                op     rune
       -                expect interface{}
       -        }{
       -                {3, 2, '+', int64(5)},
       -                {3, 2, '-', int64(1)},
       -                {3, 2, '*', int64(6)},
       -                {3, 2, '/', int64(1)},
       -                {3.0, 2, '+', float64(5)},
       -                {3.0, 2, '-', float64(1)},
       -                {3.0, 2, '*', float64(6)},
       -                {3.0, 2, '/', float64(1.5)},
       -                {3, 2.0, '+', float64(5)},
       -                {3, 2.0, '-', float64(1)},
       -                {3, 2.0, '*', float64(6)},
       -                {3, 2.0, '/', float64(1.5)},
       -                {3.0, 2.0, '+', float64(5)},
       -                {3.0, 2.0, '-', float64(1)},
       -                {3.0, 2.0, '*', float64(6)},
       -                {3.0, 2.0, '/', float64(1.5)},
       -                {uint(3), uint(2), '+', uint64(5)},
       -                {uint(3), uint(2), '-', uint64(1)},
       -                {uint(3), uint(2), '*', uint64(6)},
       -                {uint(3), uint(2), '/', uint64(1)},
       -                {uint(3), 2, '+', uint64(5)},
       -                {uint(3), 2, '-', uint64(1)},
       -                {uint(3), 2, '*', uint64(6)},
       -                {uint(3), 2, '/', uint64(1)},
       -                {3, uint(2), '+', uint64(5)},
       -                {3, uint(2), '-', uint64(1)},
       -                {3, uint(2), '*', uint64(6)},
       -                {3, uint(2), '/', uint64(1)},
       -                {uint(3), -2, '+', int64(1)},
       -                {uint(3), -2, '-', int64(5)},
       -                {uint(3), -2, '*', int64(-6)},
       -                {uint(3), -2, '/', int64(-1)},
       -                {-3, uint(2), '+', int64(-1)},
       -                {-3, uint(2), '-', int64(-5)},
       -                {-3, uint(2), '*', int64(-6)},
       -                {-3, uint(2), '/', int64(-1)},
       -                {uint(3), 2.0, '+', float64(5)},
       -                {uint(3), 2.0, '-', float64(1)},
       -                {uint(3), 2.0, '*', float64(6)},
       -                {uint(3), 2.0, '/', float64(1.5)},
       -                {3.0, uint(2), '+', float64(5)},
       -                {3.0, uint(2), '-', float64(1)},
       -                {3.0, uint(2), '*', float64(6)},
       -                {3.0, uint(2), '/', float64(1.5)},
       -                {0, 0, '+', 0},
       -                {0, 0, '-', 0},
       -                {0, 0, '*', 0},
       -                {"foo", "bar", '+', "foobar"},
       -                {3, 0, '/', false},
       -                {3.0, 0, '/', false},
       -                {3, 0.0, '/', false},
       -                {uint(3), uint(0), '/', false},
       -                {3, uint(0), '/', false},
       -                {-3, uint(0), '/', false},
       -                {uint(3), 0, '/', false},
       -                {3.0, uint(0), '/', false},
       -                {uint(3), 0.0, '/', false},
       -                {3, "foo", '+', false},
       -                {3.0, "foo", '+', false},
       -                {uint(3), "foo", '+', false},
       -                {"foo", 3, '+', false},
       -                {"foo", "bar", '-', false},
       -                {3, 2, '%', false},
       -        } {
       -                errMsg := fmt.Sprintf("[%d] %v", i, test)
       -
       -                result, err := DoArithmetic(test.a, test.b, test.op)
       -
       -                if b, ok := test.expect.(bool); ok && !b {
       -                        require.Error(t, err, errMsg)
       -                        continue
       -                }
       -
       -                require.NoError(t, err, errMsg)
       -                assert.Equal(t, test.expect, result, errMsg)
       -        }
       -}
       -
        func TestCeil(t *testing.T) {
                t.Parallel()
        
   DIR diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go
       @@ -125,7 +125,6 @@ func TestTemplateFuncsExamples(t *testing.T) {
                                }
                        }
                }
       -
        }
        
        // TODO(bep) it would be dandy to put this one into the partials package, but