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 }