github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/common/maps/maps.go (about)

     1  // Copyright 2018 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  	"fmt"
    18  	"strings"
    19  
    20  	"github.com/gohugoio/hugo/common/types"
    21  
    22  	"github.com/gobwas/glob"
    23  	"github.com/spf13/cast"
    24  )
    25  
    26  // ToStringMapE converts in to map[string]interface{}.
    27  func ToStringMapE(in any) (map[string]any, error) {
    28  	switch vv := in.(type) {
    29  	case Params:
    30  		return vv, nil
    31  	case map[string]string:
    32  		var m = map[string]any{}
    33  		for k, v := range vv {
    34  			m[k] = v
    35  		}
    36  		return m, nil
    37  
    38  	default:
    39  		return cast.ToStringMapE(in)
    40  	}
    41  }
    42  
    43  // ToParamsAndPrepare converts in to Params and prepares it for use.
    44  // If in is nil, an empty map is returned.
    45  // See PrepareParams.
    46  func ToParamsAndPrepare(in any) (Params, error) {
    47  	if types.IsNil(in) {
    48  		return Params{}, nil
    49  	}
    50  	m, err := ToStringMapE(in)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	PrepareParams(m)
    55  	return m, nil
    56  }
    57  
    58  // MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
    59  func MustToParamsAndPrepare(in any) Params {
    60  	p, err := ToParamsAndPrepare(in)
    61  	if err != nil {
    62  		panic(fmt.Sprintf("cannot convert %T to maps.Params: %s", in, err))
    63  	}
    64  	return p
    65  }
    66  
    67  // ToStringMap converts in to map[string]interface{}.
    68  func ToStringMap(in any) map[string]any {
    69  	m, _ := ToStringMapE(in)
    70  	return m
    71  }
    72  
    73  // ToStringMapStringE converts in to map[string]string.
    74  func ToStringMapStringE(in any) (map[string]string, error) {
    75  	m, err := ToStringMapE(in)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	return cast.ToStringMapStringE(m)
    80  }
    81  
    82  // ToStringMapString converts in to map[string]string.
    83  func ToStringMapString(in any) map[string]string {
    84  	m, _ := ToStringMapStringE(in)
    85  	return m
    86  }
    87  
    88  // ToStringMapBool converts in to bool.
    89  func ToStringMapBool(in any) map[string]bool {
    90  	m, _ := ToStringMapE(in)
    91  	return cast.ToStringMapBool(m)
    92  }
    93  
    94  // ToSliceStringMap converts in to []map[string]interface{}.
    95  func ToSliceStringMap(in any) ([]map[string]any, error) {
    96  	switch v := in.(type) {
    97  	case []map[string]any:
    98  		return v, nil
    99  	case Params:
   100  		return []map[string]any{v}, nil
   101  	case []any:
   102  		var s []map[string]any
   103  		for _, entry := range v {
   104  			if vv, ok := entry.(map[string]any); ok {
   105  				s = append(s, vv)
   106  			}
   107  		}
   108  		return s, nil
   109  	default:
   110  		return nil, fmt.Errorf("unable to cast %#v of type %T to []map[string]interface{}", in, in)
   111  	}
   112  }
   113  
   114  // LookupEqualFold finds key in m with case insensitive equality checks.
   115  func LookupEqualFold[T any | string](m map[string]T, key string) (T, bool) {
   116  	if v, found := m[key]; found {
   117  		return v, true
   118  	}
   119  	for k, v := range m {
   120  		if strings.EqualFold(k, key) {
   121  			return v, true
   122  		}
   123  	}
   124  	var s T
   125  	return s, false
   126  }
   127  
   128  // MergeShallow merges src into dst, but only if the key does not already exist in dst.
   129  // The keys are compared case insensitively.
   130  func MergeShallow(dst, src map[string]any) {
   131  	for k, v := range src {
   132  		found := false
   133  		for dk := range dst {
   134  			if strings.EqualFold(dk, k) {
   135  				found = true
   136  				break
   137  			}
   138  		}
   139  		if !found {
   140  			dst[k] = v
   141  		}
   142  	}
   143  }
   144  
   145  type keyRename struct {
   146  	pattern glob.Glob
   147  	newKey  string
   148  }
   149  
   150  // KeyRenamer supports renaming of keys in a map.
   151  type KeyRenamer struct {
   152  	renames []keyRename
   153  }
   154  
   155  // NewKeyRenamer creates a new KeyRenamer given a list of pattern and new key
   156  // value pairs.
   157  func NewKeyRenamer(patternKeys ...string) (KeyRenamer, error) {
   158  	var renames []keyRename
   159  	for i := 0; i < len(patternKeys); i += 2 {
   160  		g, err := glob.Compile(strings.ToLower(patternKeys[i]), '/')
   161  		if err != nil {
   162  			return KeyRenamer{}, err
   163  		}
   164  		renames = append(renames, keyRename{pattern: g, newKey: patternKeys[i+1]})
   165  	}
   166  
   167  	return KeyRenamer{renames: renames}, nil
   168  }
   169  
   170  func (r KeyRenamer) getNewKey(keyPath string) string {
   171  	for _, matcher := range r.renames {
   172  		if matcher.pattern.Match(keyPath) {
   173  			return matcher.newKey
   174  		}
   175  	}
   176  
   177  	return ""
   178  }
   179  
   180  // Rename renames the keys in the given map according
   181  // to the patterns in the current KeyRenamer.
   182  func (r KeyRenamer) Rename(m map[string]any) {
   183  	r.renamePath("", m)
   184  }
   185  
   186  func (KeyRenamer) keyPath(k1, k2 string) string {
   187  	k1, k2 = strings.ToLower(k1), strings.ToLower(k2)
   188  	if k1 == "" {
   189  		return k2
   190  	}
   191  	return k1 + "/" + k2
   192  }
   193  
   194  func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]any) {
   195  	for key, val := range m {
   196  		keyPath := r.keyPath(parentKeyPath, key)
   197  		switch val.(type) {
   198  		case map[any]any:
   199  			val = cast.ToStringMap(val)
   200  			r.renamePath(keyPath, val.(map[string]any))
   201  		case map[string]any:
   202  			r.renamePath(keyPath, val.(map[string]any))
   203  		}
   204  
   205  		newKey := r.getNewKey(keyPath)
   206  
   207  		if newKey != "" {
   208  			delete(m, key)
   209  			m[newKey] = val
   210  		}
   211  	}
   212  }
   213  
   214  // ConvertFloat64WithNoDecimalsToInt converts float64 values with no decimals to int recursively.
   215  func ConvertFloat64WithNoDecimalsToInt(m map[string]any) {
   216  	for k, v := range m {
   217  		switch vv := v.(type) {
   218  		case float64:
   219  			if v == float64(int64(vv)) {
   220  				m[k] = int64(vv)
   221  			}
   222  		case map[string]any:
   223  			ConvertFloat64WithNoDecimalsToInt(vv)
   224  		case []any:
   225  			for i, vvv := range vv {
   226  				switch vvvv := vvv.(type) {
   227  				case float64:
   228  					if vvv == float64(int64(vvvv)) {
   229  						vv[i] = int64(vvvv)
   230  					}
   231  				case map[string]any:
   232  					ConvertFloat64WithNoDecimalsToInt(vvvv)
   233  				}
   234  			}
   235  		}
   236  	}
   237  }