github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/common/maps/params.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 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 // KeyParams is an utility struct for the WalkParams method. 27 type KeyParams struct { 28 Key string 29 Params Params 30 } 31 32 // GetNested does a lower case and nested search in this map. 33 // It will return nil if none found. 34 // Make all of these methods internal somehow. 35 func (p Params) GetNested(indices ...string) any { 36 v, _, _ := getNested(p, indices) 37 return v 38 } 39 40 // SetParams overwrites values in dst with values in src for common or new keys. 41 // This is done recursively. 42 func SetParams(dst, src Params) { 43 for k, v := range src { 44 vv, found := dst[k] 45 if !found { 46 dst[k] = v 47 } else { 48 switch vvv := vv.(type) { 49 case Params: 50 if pv, ok := v.(Params); ok { 51 SetParams(vvv, pv) 52 } else { 53 dst[k] = v 54 } 55 default: 56 dst[k] = v 57 } 58 } 59 } 60 } 61 62 // IsZero returns true if p is considered empty. 63 func (p Params) IsZero() bool { 64 if p == nil || len(p) == 0 { 65 return true 66 } 67 68 if len(p) > 1 { 69 return false 70 } 71 72 for k := range p { 73 return k == MergeStrategyKey 74 } 75 76 return false 77 78 } 79 80 // MergeParamsWithStrategy transfers values from src to dst for new keys using the merge strategy given. 81 // This is done recursively. 82 func MergeParamsWithStrategy(strategy string, dst, src Params) { 83 dst.merge(ParamsMergeStrategy(strategy), src) 84 } 85 86 // MergeParams transfers values from src to dst for new keys using the merge encoded in dst. 87 // This is done recursively. 88 func MergeParams(dst, src Params) { 89 ms, _ := dst.GetMergeStrategy() 90 dst.merge(ms, src) 91 } 92 93 func (p Params) merge(ps ParamsMergeStrategy, pp Params) { 94 ns, found := p.GetMergeStrategy() 95 96 var ms = ns 97 if !found && ps != "" { 98 ms = ps 99 } 100 101 noUpdate := ms == ParamsMergeStrategyNone 102 noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow) 103 104 for k, v := range pp { 105 106 if k == MergeStrategyKey { 107 continue 108 } 109 vv, found := p[k] 110 111 if found { 112 // Key matches, if both sides are Params, we try to merge. 113 if vvv, ok := vv.(Params); ok { 114 if pv, ok := v.(Params); ok { 115 vvv.merge(ms, pv) 116 } 117 } 118 } else if !noUpdate { 119 p[k] = v 120 } 121 122 } 123 } 124 125 // For internal use. 126 func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) { 127 if v, found := p[MergeStrategyKey]; found { 128 if s, ok := v.(ParamsMergeStrategy); ok { 129 return s, true 130 } 131 } 132 return ParamsMergeStrategyShallow, false 133 } 134 135 // For internal use. 136 func (p Params) DeleteMergeStrategy() bool { 137 if _, found := p[MergeStrategyKey]; found { 138 delete(p, MergeStrategyKey) 139 return true 140 } 141 return false 142 } 143 144 // For internal use. 145 func (p Params) SetMergeStrategy(s ParamsMergeStrategy) { 146 switch s { 147 case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow: 148 default: 149 panic(fmt.Sprintf("invalid merge strategy %q", s)) 150 } 151 p[MergeStrategyKey] = s 152 } 153 154 func getNested(m map[string]any, indices []string) (any, string, map[string]any) { 155 if len(indices) == 0 { 156 return nil, "", nil 157 } 158 159 first := indices[0] 160 v, found := m[strings.ToLower(cast.ToString(first))] 161 if !found { 162 if len(indices) == 1 { 163 return nil, first, m 164 } 165 return nil, "", nil 166 167 } 168 169 if len(indices) == 1 { 170 return v, first, m 171 } 172 173 switch m2 := v.(type) { 174 case Params: 175 return getNested(m2, indices[1:]) 176 case map[string]any: 177 return getNested(m2, indices[1:]) 178 default: 179 return nil, "", nil 180 } 181 } 182 183 // GetNestedParam gets the first match of the keyStr in the candidates given. 184 // It will first try the exact match and then try to find it as a nested map value, 185 // using the given separator, e.g. "mymap.name". 186 // It assumes that all the maps given have lower cased keys. 187 func GetNestedParam(keyStr, separator string, candidates ...Params) (any, error) { 188 keyStr = strings.ToLower(keyStr) 189 190 // Try exact match first 191 for _, m := range candidates { 192 if v, ok := m[keyStr]; ok { 193 return v, nil 194 } 195 } 196 197 keySegments := strings.Split(keyStr, separator) 198 for _, m := range candidates { 199 if v := m.GetNested(keySegments...); v != nil { 200 return v, nil 201 } 202 } 203 204 return nil, nil 205 } 206 207 func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) any) (any, string, map[string]any, error) { 208 keySegments := strings.Split(keyStr, separator) 209 if len(keySegments) == 0 { 210 return nil, "", nil, nil 211 } 212 213 first := lookupFn(keySegments[0]) 214 if first == nil { 215 return nil, "", nil, nil 216 } 217 218 if len(keySegments) == 1 { 219 return first, keySegments[0], nil, nil 220 } 221 222 switch m := first.(type) { 223 case map[string]any: 224 v, key, owner := getNested(m, keySegments[1:]) 225 return v, key, owner, nil 226 case Params: 227 v, key, owner := getNested(m, keySegments[1:]) 228 return v, key, owner, nil 229 } 230 231 return nil, "", nil, nil 232 } 233 234 // ParamsMergeStrategy tells what strategy to use in Params.Merge. 235 type ParamsMergeStrategy string 236 237 const ( 238 // Do not merge. 239 ParamsMergeStrategyNone ParamsMergeStrategy = "none" 240 // Only add new keys. 241 ParamsMergeStrategyShallow ParamsMergeStrategy = "shallow" 242 // Add new keys, merge existing. 243 ParamsMergeStrategyDeep ParamsMergeStrategy = "deep" 244 245 MergeStrategyKey = "_merge" 246 ) 247 248 // CleanConfigStringMapString removes any processing instructions from m, 249 // m will never be modified. 250 func CleanConfigStringMapString(m map[string]string) map[string]string { 251 if m == nil || len(m) == 0 { 252 return m 253 } 254 if _, found := m[MergeStrategyKey]; !found { 255 return m 256 } 257 // Create a new map and copy all the keys except the merge strategy key. 258 m2 := make(map[string]string, len(m)-1) 259 for k, v := range m { 260 if k != MergeStrategyKey { 261 m2[k] = v 262 } 263 } 264 return m2 265 } 266 267 // CleanConfigStringMap is the same as CleanConfigStringMapString but for 268 // map[string]any. 269 func CleanConfigStringMap(m map[string]any) map[string]any { 270 if m == nil || len(m) == 0 { 271 return m 272 } 273 if _, found := m[MergeStrategyKey]; !found { 274 return m 275 } 276 // Create a new map and copy all the keys except the merge strategy key. 277 m2 := make(map[string]any, len(m)-1) 278 for k, v := range m { 279 if k != MergeStrategyKey { 280 m2[k] = v 281 } 282 switch v2 := v.(type) { 283 case map[string]any: 284 m2[k] = CleanConfigStringMap(v2) 285 case Params: 286 var p Params = CleanConfigStringMap(v2) 287 m2[k] = p 288 case map[string]string: 289 m2[k] = CleanConfigStringMapString(v2) 290 } 291 292 } 293 return m2 294 295 } 296 297 func toMergeStrategy(v any) ParamsMergeStrategy { 298 s := ParamsMergeStrategy(cast.ToString(v)) 299 switch s { 300 case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow: 301 return s 302 default: 303 return ParamsMergeStrategyDeep 304 } 305 } 306 307 // PrepareParams 308 // * makes all the keys in the given map lower cased and will do so 309 // * This will modify the map given. 310 // * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string will be converted to Params. 311 // * Any _merge value will be converted to proper type and value. 312 func PrepareParams(m Params) { 313 for k, v := range m { 314 var retyped bool 315 lKey := strings.ToLower(k) 316 if lKey == MergeStrategyKey { 317 v = toMergeStrategy(v) 318 retyped = true 319 } else { 320 switch vv := v.(type) { 321 case map[any]any: 322 var p Params = cast.ToStringMap(v) 323 v = p 324 PrepareParams(p) 325 retyped = true 326 case map[string]any: 327 var p Params = v.(map[string]any) 328 v = p 329 PrepareParams(p) 330 retyped = true 331 case map[string]string: 332 p := make(Params) 333 for k, v := range vv { 334 p[k] = v 335 } 336 v = p 337 PrepareParams(p) 338 retyped = true 339 } 340 } 341 342 if retyped || k != lKey { 343 delete(m, k) 344 m[lKey] = v 345 } 346 } 347 }