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  }