github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/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/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]interface{}
    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 interface{}) (string, error) {
    54  	var newVal interface{}
    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 interface{}) 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) interface{} {
   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]interface{} {
   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 interface{}) string {
   121  	c.mu.Lock()
   122  	_, found := c.values[key]
   123  	if !found {
   124  		c.values[key] = make(map[string]interface{})
   125  	}
   126  
   127  	c.values[key].(map[string]interface{})[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]interface{}), 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) interface{} {
   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]interface{})
   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([]interface{}, 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]interface{})}
   172  }