github.com/neohugo/neohugo@v0.123.8/common/maps/scratch.go (about) 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/neohugo/neohugo/common/collections" 22 "github.com/neohugo/neohugo/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 returns a "scratch pad" that can be used to store state. 34 Scratch() *Scratch 35 } 36 37 type scratcher struct { 38 s *Scratch 39 } 40 41 func (s scratcher) Scratch() *Scratch { 42 return s.s 43 } 44 45 // NewScratcher creates a new Scratcher. 46 func NewScratcher() Scratcher { 47 return scratcher{s: NewScratch()} 48 } 49 50 // Add will, for single values, add (using the + operator) the addend to the existing addend (if found). 51 // Supports numeric values and strings. 52 // 53 // If the first add for a key is an array or slice, then the next value(s) will be appended. 54 func (c *Scratch) Add(key string, newAddend any) (string, error) { 55 var newVal any 56 c.mu.RLock() 57 existingAddend, found := c.values[key] 58 c.mu.RUnlock() 59 if found { 60 var err error 61 62 addendV := reflect.TypeOf(existingAddend) 63 64 if addendV.Kind() == reflect.Slice || addendV.Kind() == reflect.Array { 65 newVal, err = collections.Append(existingAddend, newAddend) 66 if err != nil { 67 return "", err 68 } 69 } else { 70 newVal, err = math.DoArithmetic(existingAddend, newAddend, '+') 71 if err != nil { 72 return "", err 73 } 74 } 75 } else { 76 newVal = newAddend 77 } 78 c.mu.Lock() 79 c.values[key] = newVal 80 c.mu.Unlock() 81 return "", nil // have to return something to make it work with the Go templates 82 } 83 84 // Set stores a value with the given key in the Node context. 85 // This value can later be retrieved with Get. 86 func (c *Scratch) Set(key string, value any) string { 87 c.mu.Lock() 88 c.values[key] = value 89 c.mu.Unlock() 90 return "" 91 } 92 93 // Delete deletes the given key. 94 func (c *Scratch) Delete(key string) string { 95 c.mu.Lock() 96 delete(c.values, key) 97 c.mu.Unlock() 98 return "" 99 } 100 101 // Get returns a value previously set by Add or Set. 102 func (c *Scratch) Get(key string) any { 103 c.mu.RLock() 104 val := c.values[key] 105 c.mu.RUnlock() 106 107 return val 108 } 109 110 // Values returns the raw backing map. Note that you should just use 111 // this method on the locally scoped Scratch instances you obtain via newScratch, not 112 // .Page.Scratch etc., as that will lead to concurrency issues. 113 func (c *Scratch) Values() map[string]any { 114 c.mu.RLock() 115 defer c.mu.RUnlock() 116 return c.values 117 } 118 119 // SetInMap stores a value to a map with the given key in the Node context. 120 // This map can later be retrieved with GetSortedMapValues. 121 func (c *Scratch) SetInMap(key string, mapKey string, value any) string { 122 c.mu.Lock() 123 _, found := c.values[key] 124 if !found { 125 c.values[key] = make(map[string]any) 126 } 127 128 c.values[key].(map[string]any)[mapKey] = value 129 c.mu.Unlock() 130 return "" 131 } 132 133 // DeleteInMap deletes a value to a map with the given key in the Node context. 134 func (c *Scratch) DeleteInMap(key string, mapKey string) string { 135 c.mu.Lock() 136 _, found := c.values[key] 137 if found { 138 delete(c.values[key].(map[string]any), mapKey) 139 } 140 c.mu.Unlock() 141 return "" 142 } 143 144 // GetSortedMapValues returns a sorted map previously filled with SetInMap. 145 func (c *Scratch) GetSortedMapValues(key string) any { 146 c.mu.RLock() 147 148 if c.values[key] == nil { 149 c.mu.RUnlock() 150 return nil 151 } 152 153 unsortedMap := c.values[key].(map[string]any) 154 c.mu.RUnlock() 155 var keys []string 156 for mapKey := range unsortedMap { 157 keys = append(keys, mapKey) 158 } 159 160 sort.Strings(keys) 161 162 sortedArray := make([]any, len(unsortedMap)) 163 for i, mapKey := range keys { 164 sortedArray[i] = unsortedMap[mapKey] 165 } 166 167 return sortedArray 168 } 169 170 // NewScratch returns a new instance of Scratch. 171 func NewScratch() *Scratch { 172 return &Scratch{values: make(map[string]any)} 173 }