github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/tpl/collections/merge.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 collections
    15  
    16  import (
    17  	"reflect"
    18  	"strings"
    19  
    20  	"github.com/gohugoio/hugo/common/hreflect"
    21  	"github.com/gohugoio/hugo/common/maps"
    22  
    23  	"github.com/pkg/errors"
    24  )
    25  
    26  // Merge creates a copy of the final parameter and merges the preceding
    27  // parameters into it in reverse order.
    28  // Currently only maps are supported. Key handling is case insensitive.
    29  func (ns *Namespace) Merge(params ...interface{}) (interface{}, error) {
    30  	if len(params) < 2 {
    31  		return nil, errors.New("merge requires at least two parameters")
    32  	}
    33  
    34  	var err error
    35  	result := params[len(params)-1]
    36  
    37  	for i := len(params) - 2; i >= 0; i-- {
    38  		result, err = ns.merge(params[i], result)
    39  		if err != nil {
    40  			return nil, err
    41  		}
    42  	}
    43  
    44  	return result, nil
    45  }
    46  
    47  // merge creates a copy of dst and merges src into it.
    48  func (ns *Namespace) merge(src, dst interface{}) (interface{}, error) {
    49  	vdst, vsrc := reflect.ValueOf(dst), reflect.ValueOf(src)
    50  
    51  	if vdst.Kind() != reflect.Map {
    52  		return nil, errors.Errorf("destination must be a map, got %T", dst)
    53  	}
    54  
    55  	if !hreflect.IsTruthfulValue(vsrc) {
    56  		return dst, nil
    57  	}
    58  
    59  	if vsrc.Kind() != reflect.Map {
    60  		return nil, errors.Errorf("source must be a map, got %T", src)
    61  	}
    62  
    63  	if vsrc.Type().Key() != vdst.Type().Key() {
    64  		return nil, errors.Errorf("incompatible map types, got %T to %T", src, dst)
    65  	}
    66  
    67  	return mergeMap(vdst, vsrc).Interface(), nil
    68  }
    69  
    70  func caseInsensitiveLookup(m, k reflect.Value) (reflect.Value, bool) {
    71  	if m.Type().Key().Kind() != reflect.String || k.Kind() != reflect.String {
    72  		// Fall back to direct lookup.
    73  		v := m.MapIndex(k)
    74  		return v, hreflect.IsTruthfulValue(v)
    75  	}
    76  
    77  	for _, key := range m.MapKeys() {
    78  		if strings.EqualFold(k.String(), key.String()) {
    79  			return m.MapIndex(key), true
    80  		}
    81  	}
    82  
    83  	return reflect.Value{}, false
    84  }
    85  
    86  func mergeMap(dst, src reflect.Value) reflect.Value {
    87  	out := reflect.MakeMap(dst.Type())
    88  
    89  	// If the destination is Params, we must lower case all keys.
    90  	_, lowerCase := dst.Interface().(maps.Params)
    91  
    92  	// Copy the destination map.
    93  	for _, key := range dst.MapKeys() {
    94  		v := dst.MapIndex(key)
    95  		out.SetMapIndex(key, v)
    96  	}
    97  
    98  	// Add all keys in src not already in destination.
    99  	// Maps of the same type will be merged.
   100  	for _, key := range src.MapKeys() {
   101  		sv := src.MapIndex(key)
   102  		dv, found := caseInsensitiveLookup(dst, key)
   103  
   104  		if found {
   105  			// If both are the same map key type, merge.
   106  			dve := dv.Elem()
   107  			if dve.Kind() == reflect.Map {
   108  				sve := sv.Elem()
   109  				if sve.Kind() != reflect.Map {
   110  					continue
   111  				}
   112  
   113  				if dve.Type().Key() == sve.Type().Key() {
   114  					out.SetMapIndex(key, mergeMap(dve, sve))
   115  				}
   116  			}
   117  		} else {
   118  			if lowerCase && key.Kind() == reflect.String {
   119  				key = reflect.ValueOf(strings.ToLower(key.String()))
   120  			}
   121  			out.SetMapIndex(key, sv)
   122  		}
   123  	}
   124  
   125  	return out
   126  }