scratch.go (4311B)
1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package maps 15 16 import ( 17 "reflect" 18 "sort" 19 "sync" 20 21 "github.com/gohugoio/hugo/common/collections" 22 "github.com/gohugoio/hugo/common/math" 23 ) 24 25 // Scratch is a writable context used for stateful operations in Page/Node rendering. 26 type Scratch struct { 27 values map[string]any 28 mu sync.RWMutex 29 } 30 31 // Scratcher provides a scratching service. 32 type Scratcher interface { 33 Scratch() *Scratch 34 } 35 36 type scratcher struct { 37 s *Scratch 38 } 39 40 func (s scratcher) Scratch() *Scratch { 41 return s.s 42 } 43 44 // NewScratcher creates a new Scratcher. 45 func NewScratcher() Scratcher { 46 return scratcher{s: NewScratch()} 47 } 48 49 // Add will, for single values, add (using the + operator) the addend to the existing addend (if found). 50 // Supports numeric values and strings. 51 // 52 // If the first add for a key is an array or slice, then the next value(s) will be appended. 53 func (c *Scratch) Add(key string, newAddend any) (string, error) { 54 var newVal any 55 c.mu.RLock() 56 existingAddend, found := c.values[key] 57 c.mu.RUnlock() 58 if found { 59 var err error 60 61 addendV := reflect.TypeOf(existingAddend) 62 63 if addendV.Kind() == reflect.Slice || addendV.Kind() == reflect.Array { 64 newVal, err = collections.Append(existingAddend, newAddend) 65 if err != nil { 66 return "", err 67 } 68 } else { 69 newVal, err = math.DoArithmetic(existingAddend, newAddend, '+') 70 if err != nil { 71 return "", err 72 } 73 } 74 } else { 75 newVal = newAddend 76 } 77 c.mu.Lock() 78 c.values[key] = newVal 79 c.mu.Unlock() 80 return "", nil // have to return something to make it work with the Go templates 81 } 82 83 // Set stores a value with the given key in the Node context. 84 // This value can later be retrieved with Get. 85 func (c *Scratch) Set(key string, value any) string { 86 c.mu.Lock() 87 c.values[key] = value 88 c.mu.Unlock() 89 return "" 90 } 91 92 // Delete deletes the given key. 93 func (c *Scratch) Delete(key string) string { 94 c.mu.Lock() 95 delete(c.values, key) 96 c.mu.Unlock() 97 return "" 98 } 99 100 // Get returns a value previously set by Add or Set. 101 func (c *Scratch) Get(key string) any { 102 c.mu.RLock() 103 val := c.values[key] 104 c.mu.RUnlock() 105 106 return val 107 } 108 109 // Values returns the raw backing map. Note that you should just use 110 // this method on the locally scoped Scratch instances you obtain via newScratch, not 111 // .Page.Scratch etc., as that will lead to concurrency issues. 112 func (c *Scratch) Values() map[string]any { 113 c.mu.RLock() 114 defer c.mu.RUnlock() 115 return c.values 116 } 117 118 // SetInMap stores a value to a map with the given key in the Node context. 119 // This map can later be retrieved with GetSortedMapValues. 120 func (c *Scratch) SetInMap(key string, mapKey string, value any) string { 121 c.mu.Lock() 122 _, found := c.values[key] 123 if !found { 124 c.values[key] = make(map[string]any) 125 } 126 127 c.values[key].(map[string]any)[mapKey] = value 128 c.mu.Unlock() 129 return "" 130 } 131 132 // DeleteInMap deletes a value to a map with the given key in the Node context. 133 func (c *Scratch) DeleteInMap(key string, mapKey string) string { 134 c.mu.Lock() 135 _, found := c.values[key] 136 if found { 137 delete(c.values[key].(map[string]any), mapKey) 138 } 139 c.mu.Unlock() 140 return "" 141 } 142 143 // GetSortedMapValues returns a sorted map previously filled with SetInMap. 144 func (c *Scratch) GetSortedMapValues(key string) any { 145 c.mu.RLock() 146 147 if c.values[key] == nil { 148 c.mu.RUnlock() 149 return nil 150 } 151 152 unsortedMap := c.values[key].(map[string]any) 153 c.mu.RUnlock() 154 var keys []string 155 for mapKey := range unsortedMap { 156 keys = append(keys, mapKey) 157 } 158 159 sort.Strings(keys) 160 161 sortedArray := make([]any, len(unsortedMap)) 162 for i, mapKey := range keys { 163 sortedArray[i] = unsortedMap[mapKey] 164 } 165 166 return sortedArray 167 } 168 169 // NewScratch returns a new instance of Scratch. 170 func NewScratch() *Scratch { 171 return &Scratch{values: make(map[string]any)} 172 }