maps.go (4643B)
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, bool) { 47 if types.IsNil(in) { 48 return Params{}, true 49 } 50 m, err := ToStringMapE(in) 51 if err != nil { 52 return nil, false 53 } 54 PrepareParams(m) 55 return m, true 56 } 57 58 // MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails. 59 func MustToParamsAndPrepare(in any) Params { 60 if p, ok := ToParamsAndPrepare(in); ok { 61 return p 62 } else { 63 panic(fmt.Sprintf("cannot convert %T to maps.Params", in)) 64 } 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 []any: 100 var s []map[string]any 101 for _, entry := range v { 102 if vv, ok := entry.(map[string]any); ok { 103 s = append(s, vv) 104 } 105 } 106 return s, nil 107 default: 108 return nil, fmt.Errorf("unable to cast %#v of type %T to []map[string]interface{}", in, in) 109 } 110 } 111 112 // LookupEqualFold finds key in m with case insensitive equality checks. 113 func LookupEqualFold[T any | string](m map[string]T, key string) (T, bool) { 114 if v, found := m[key]; found { 115 return v, true 116 } 117 for k, v := range m { 118 if strings.EqualFold(k, key) { 119 return v, true 120 } 121 } 122 var s T 123 return s, false 124 } 125 126 type keyRename struct { 127 pattern glob.Glob 128 newKey string 129 } 130 131 // KeyRenamer supports renaming of keys in a map. 132 type KeyRenamer struct { 133 renames []keyRename 134 } 135 136 // NewKeyRenamer creates a new KeyRenamer given a list of pattern and new key 137 // value pairs. 138 func NewKeyRenamer(patternKeys ...string) (KeyRenamer, error) { 139 var renames []keyRename 140 for i := 0; i < len(patternKeys); i += 2 { 141 g, err := glob.Compile(strings.ToLower(patternKeys[i]), '/') 142 if err != nil { 143 return KeyRenamer{}, err 144 } 145 renames = append(renames, keyRename{pattern: g, newKey: patternKeys[i+1]}) 146 } 147 148 return KeyRenamer{renames: renames}, nil 149 } 150 151 func (r KeyRenamer) getNewKey(keyPath string) string { 152 for _, matcher := range r.renames { 153 if matcher.pattern.Match(keyPath) { 154 return matcher.newKey 155 } 156 } 157 158 return "" 159 } 160 161 // Rename renames the keys in the given map according 162 // to the patterns in the current KeyRenamer. 163 func (r KeyRenamer) Rename(m map[string]any) { 164 r.renamePath("", m) 165 } 166 167 func (KeyRenamer) keyPath(k1, k2 string) string { 168 k1, k2 = strings.ToLower(k1), strings.ToLower(k2) 169 if k1 == "" { 170 return k2 171 } 172 return k1 + "/" + k2 173 } 174 175 func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]any) { 176 for key, val := range m { 177 keyPath := r.keyPath(parentKeyPath, key) 178 switch val.(type) { 179 case map[any]any: 180 val = cast.ToStringMap(val) 181 r.renamePath(keyPath, val.(map[string]any)) 182 case map[string]any: 183 r.renamePath(keyPath, val.(map[string]any)) 184 } 185 186 newKey := r.getNewKey(keyPath) 187 188 if newKey != "" { 189 delete(m, key) 190 m[newKey] = val 191 } 192 } 193 }