hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

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 }