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 }