params.go (6555B)
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 "fmt" 18 "strings" 19 20 "github.com/spf13/cast" 21 ) 22 23 // Params is a map where all keys are lower case. 24 type Params map[string]any 25 26 // Get does a lower case and nested search in this map. 27 // It will return nil if none found. 28 func (p Params) Get(indices ...string) any { 29 v, _, _ := getNested(p, indices) 30 return v 31 } 32 33 // Set overwrites values in p with values in pp for common or new keys. 34 // This is done recursively. 35 func (p Params) Set(pp Params) { 36 for k, v := range pp { 37 vv, found := p[k] 38 if !found { 39 p[k] = v 40 } else { 41 switch vvv := vv.(type) { 42 case Params: 43 if pv, ok := v.(Params); ok { 44 vvv.Set(pv) 45 } else { 46 p[k] = v 47 } 48 default: 49 p[k] = v 50 } 51 } 52 } 53 } 54 55 // IsZero returns true if p is considered empty. 56 func (p Params) IsZero() bool { 57 if p == nil || len(p) == 0 { 58 return true 59 } 60 61 if len(p) > 1 { 62 return false 63 } 64 65 for k, _ := range p { 66 return k == mergeStrategyKey 67 } 68 69 return false 70 71 } 72 73 // Merge transfers values from pp to p for new keys. 74 // This is done recursively. 75 func (p Params) Merge(pp Params) { 76 p.merge("", pp) 77 } 78 79 // MergeRoot transfers values from pp to p for new keys where p is the 80 // root of the tree. 81 // This is done recursively. 82 func (p Params) MergeRoot(pp Params) { 83 ms, _ := p.GetMergeStrategy() 84 p.merge(ms, pp) 85 } 86 87 func (p Params) merge(ps ParamsMergeStrategy, pp Params) { 88 ns, found := p.GetMergeStrategy() 89 90 var ms = ns 91 if !found && ps != "" { 92 ms = ps 93 } 94 95 noUpdate := ms == ParamsMergeStrategyNone 96 noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow) 97 98 for k, v := range pp { 99 100 if k == mergeStrategyKey { 101 continue 102 } 103 vv, found := p[k] 104 105 if found { 106 // Key matches, if both sides are Params, we try to merge. 107 if vvv, ok := vv.(Params); ok { 108 if pv, ok := v.(Params); ok { 109 vvv.merge(ms, pv) 110 } 111 } 112 } else if !noUpdate { 113 p[k] = v 114 } 115 116 } 117 } 118 119 func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) { 120 if v, found := p[mergeStrategyKey]; found { 121 if s, ok := v.(ParamsMergeStrategy); ok { 122 return s, true 123 } 124 } 125 return ParamsMergeStrategyShallow, false 126 } 127 128 func (p Params) DeleteMergeStrategy() bool { 129 if _, found := p[mergeStrategyKey]; found { 130 delete(p, mergeStrategyKey) 131 return true 132 } 133 return false 134 } 135 136 func (p Params) SetDefaultMergeStrategy(s ParamsMergeStrategy) { 137 switch s { 138 case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow: 139 default: 140 panic(fmt.Sprintf("invalid merge strategy %q", s)) 141 } 142 p[mergeStrategyKey] = s 143 } 144 145 func getNested(m map[string]any, indices []string) (any, string, map[string]any) { 146 if len(indices) == 0 { 147 return nil, "", nil 148 } 149 150 first := indices[0] 151 v, found := m[strings.ToLower(cast.ToString(first))] 152 if !found { 153 if len(indices) == 1 { 154 return nil, first, m 155 } 156 return nil, "", nil 157 158 } 159 160 if len(indices) == 1 { 161 return v, first, m 162 } 163 164 switch m2 := v.(type) { 165 case Params: 166 return getNested(m2, indices[1:]) 167 case map[string]any: 168 return getNested(m2, indices[1:]) 169 default: 170 return nil, "", nil 171 } 172 } 173 174 // GetNestedParam gets the first match of the keyStr in the candidates given. 175 // It will first try the exact match and then try to find it as a nested map value, 176 // using the given separator, e.g. "mymap.name". 177 // It assumes that all the maps given have lower cased keys. 178 func GetNestedParam(keyStr, separator string, candidates ...Params) (any, error) { 179 keyStr = strings.ToLower(keyStr) 180 181 // Try exact match first 182 for _, m := range candidates { 183 if v, ok := m[keyStr]; ok { 184 return v, nil 185 } 186 } 187 188 keySegments := strings.Split(keyStr, separator) 189 for _, m := range candidates { 190 if v := m.Get(keySegments...); v != nil { 191 return v, nil 192 } 193 } 194 195 return nil, nil 196 } 197 198 func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) any) (any, string, map[string]any, error) { 199 keySegments := strings.Split(keyStr, separator) 200 if len(keySegments) == 0 { 201 return nil, "", nil, nil 202 } 203 204 first := lookupFn(keySegments[0]) 205 if first == nil { 206 return nil, "", nil, nil 207 } 208 209 if len(keySegments) == 1 { 210 return first, keySegments[0], nil, nil 211 } 212 213 switch m := first.(type) { 214 case map[string]any: 215 v, key, owner := getNested(m, keySegments[1:]) 216 return v, key, owner, nil 217 case Params: 218 v, key, owner := getNested(m, keySegments[1:]) 219 return v, key, owner, nil 220 } 221 222 return nil, "", nil, nil 223 } 224 225 // ParamsMergeStrategy tells what strategy to use in Params.Merge. 226 type ParamsMergeStrategy string 227 228 const ( 229 // Do not merge. 230 ParamsMergeStrategyNone ParamsMergeStrategy = "none" 231 // Only add new keys. 232 ParamsMergeStrategyShallow ParamsMergeStrategy = "shallow" 233 // Add new keys, merge existing. 234 ParamsMergeStrategyDeep ParamsMergeStrategy = "deep" 235 236 mergeStrategyKey = "_merge" 237 ) 238 239 func toMergeStrategy(v any) ParamsMergeStrategy { 240 s := ParamsMergeStrategy(cast.ToString(v)) 241 switch s { 242 case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow: 243 return s 244 default: 245 return ParamsMergeStrategyDeep 246 } 247 } 248 249 // PrepareParams 250 // * makes all the keys in the given map lower cased and will do so 251 // * This will modify the map given. 252 // * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string will be converted to Params. 253 // * Any _merge value will be converted to proper type and value. 254 func PrepareParams(m Params) { 255 for k, v := range m { 256 var retyped bool 257 lKey := strings.ToLower(k) 258 if lKey == mergeStrategyKey { 259 v = toMergeStrategy(v) 260 retyped = true 261 } else { 262 switch vv := v.(type) { 263 case map[any]any: 264 var p Params = cast.ToStringMap(v) 265 v = p 266 PrepareParams(p) 267 retyped = true 268 case map[string]any: 269 var p Params = v.(map[string]any) 270 v = p 271 PrepareParams(p) 272 retyped = true 273 case map[string]string: 274 p := make(Params) 275 for k, v := range vv { 276 p[k] = v 277 } 278 v = p 279 PrepareParams(p) 280 retyped = true 281 } 282 } 283 284 if retyped || k != lKey { 285 delete(m, k) 286 m[lKey] = v 287 } 288 } 289 }