github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/config/defaultConfigProvider.go (about) 1 // Copyright 2021 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 config 15 16 import ( 17 "fmt" 18 "sort" 19 "strings" 20 "sync" 21 22 xmaps "golang.org/x/exp/maps" 23 24 "github.com/spf13/cast" 25 26 "github.com/gohugoio/hugo/common/maps" 27 ) 28 29 var ( 30 31 // ConfigRootKeysSet contains all of the config map root keys. 32 ConfigRootKeysSet = map[string]bool{ 33 "build": true, 34 "caches": true, 35 "cascade": true, 36 "frontmatter": true, 37 "languages": true, 38 "imaging": true, 39 "markup": true, 40 "mediatypes": true, 41 "menus": true, 42 "minify": true, 43 "module": true, 44 "outputformats": true, 45 "params": true, 46 "permalinks": true, 47 "related": true, 48 "sitemap": true, 49 "privacy": true, 50 "security": true, 51 "taxonomies": true, 52 } 53 54 // ConfigRootKeys is a sorted version of ConfigRootKeysSet. 55 ConfigRootKeys []string 56 ) 57 58 func init() { 59 for k := range ConfigRootKeysSet { 60 ConfigRootKeys = append(ConfigRootKeys, k) 61 } 62 sort.Strings(ConfigRootKeys) 63 } 64 65 // New creates a Provider backed by an empty maps.Params. 66 func New() Provider { 67 return &defaultConfigProvider{ 68 root: make(maps.Params), 69 } 70 } 71 72 // NewFrom creates a Provider backed by params. 73 func NewFrom(params maps.Params) Provider { 74 maps.PrepareParams(params) 75 return &defaultConfigProvider{ 76 root: params, 77 } 78 } 79 80 // defaultConfigProvider is a Provider backed by a map where all keys are lower case. 81 // All methods are thread safe. 82 type defaultConfigProvider struct { 83 mu sync.RWMutex 84 root maps.Params 85 86 keyCache sync.Map 87 } 88 89 func (c *defaultConfigProvider) Get(k string) any { 90 if k == "" { 91 return c.root 92 } 93 c.mu.RLock() 94 key, m := c.getNestedKeyAndMap(strings.ToLower(k), false) 95 if m == nil { 96 c.mu.RUnlock() 97 return nil 98 } 99 v := m[key] 100 c.mu.RUnlock() 101 return v 102 } 103 104 func (c *defaultConfigProvider) GetBool(k string) bool { 105 v := c.Get(k) 106 return cast.ToBool(v) 107 } 108 109 func (c *defaultConfigProvider) GetInt(k string) int { 110 v := c.Get(k) 111 return cast.ToInt(v) 112 } 113 114 func (c *defaultConfigProvider) IsSet(k string) bool { 115 var found bool 116 c.mu.RLock() 117 key, m := c.getNestedKeyAndMap(strings.ToLower(k), false) 118 if m != nil { 119 _, found = m[key] 120 } 121 c.mu.RUnlock() 122 return found 123 } 124 125 func (c *defaultConfigProvider) GetString(k string) string { 126 v := c.Get(k) 127 return cast.ToString(v) 128 } 129 130 func (c *defaultConfigProvider) GetParams(k string) maps.Params { 131 v := c.Get(k) 132 if v == nil { 133 return nil 134 } 135 return v.(maps.Params) 136 } 137 138 func (c *defaultConfigProvider) GetStringMap(k string) map[string]any { 139 v := c.Get(k) 140 return maps.ToStringMap(v) 141 } 142 143 func (c *defaultConfigProvider) GetStringMapString(k string) map[string]string { 144 v := c.Get(k) 145 return maps.ToStringMapString(v) 146 } 147 148 func (c *defaultConfigProvider) GetStringSlice(k string) []string { 149 v := c.Get(k) 150 return cast.ToStringSlice(v) 151 } 152 153 func (c *defaultConfigProvider) Set(k string, v any) { 154 c.mu.Lock() 155 defer c.mu.Unlock() 156 157 k = strings.ToLower(k) 158 159 if k == "" { 160 if p, err := maps.ToParamsAndPrepare(v); err == nil { 161 // Set the values directly in root. 162 maps.SetParams(c.root, p) 163 } else { 164 c.root[k] = v 165 } 166 167 return 168 } 169 170 switch vv := v.(type) { 171 case map[string]any, map[any]any, map[string]string: 172 p := maps.MustToParamsAndPrepare(vv) 173 v = p 174 } 175 176 key, m := c.getNestedKeyAndMap(k, true) 177 if m == nil { 178 return 179 } 180 181 if existing, found := m[key]; found { 182 if p1, ok := existing.(maps.Params); ok { 183 if p2, ok := v.(maps.Params); ok { 184 maps.SetParams(p1, p2) 185 return 186 } 187 } 188 } 189 190 m[key] = v 191 } 192 193 // SetDefaults will set values from params if not already set. 194 func (c *defaultConfigProvider) SetDefaults(params maps.Params) { 195 maps.PrepareParams(params) 196 for k, v := range params { 197 if _, found := c.root[k]; !found { 198 c.root[k] = v 199 } 200 } 201 } 202 203 func (c *defaultConfigProvider) Merge(k string, v any) { 204 c.mu.Lock() 205 defer c.mu.Unlock() 206 k = strings.ToLower(k) 207 208 if k == "" { 209 rs, f := c.root.GetMergeStrategy() 210 if f && rs == maps.ParamsMergeStrategyNone { 211 // The user has set a "no merge" strategy on this, 212 // nothing more to do. 213 return 214 } 215 216 if p, err := maps.ToParamsAndPrepare(v); err == nil { 217 // As there may be keys in p not in root, we need to handle 218 // those as a special case. 219 var keysToDelete []string 220 for kk, vv := range p { 221 if pp, ok := vv.(maps.Params); ok { 222 if pppi, ok := c.root[kk]; ok { 223 ppp := pppi.(maps.Params) 224 maps.MergeParamsWithStrategy("", ppp, pp) 225 } else { 226 // We need to use the default merge strategy for 227 // this key. 228 np := make(maps.Params) 229 strategy := c.determineMergeStrategy(maps.KeyParams{Key: "", Params: c.root}, maps.KeyParams{Key: kk, Params: np}) 230 np.SetMergeStrategy(strategy) 231 maps.MergeParamsWithStrategy("", np, pp) 232 c.root[kk] = np 233 if np.IsZero() { 234 // Just keep it until merge is done. 235 keysToDelete = append(keysToDelete, kk) 236 } 237 } 238 } 239 } 240 // Merge the rest. 241 maps.MergeParams(c.root, p) 242 for _, k := range keysToDelete { 243 delete(c.root, k) 244 } 245 } else { 246 panic(fmt.Sprintf("unsupported type %T received in Merge", v)) 247 } 248 249 return 250 } 251 252 switch vv := v.(type) { 253 case map[string]any, map[any]any, map[string]string: 254 p := maps.MustToParamsAndPrepare(vv) 255 v = p 256 } 257 258 key, m := c.getNestedKeyAndMap(k, true) 259 if m == nil { 260 return 261 } 262 263 if existing, found := m[key]; found { 264 if p1, ok := existing.(maps.Params); ok { 265 if p2, ok := v.(maps.Params); ok { 266 maps.MergeParamsWithStrategy("", p1, p2) 267 } 268 } 269 } else { 270 m[key] = v 271 } 272 } 273 274 func (c *defaultConfigProvider) Keys() []string { 275 c.mu.RLock() 276 defer c.mu.RUnlock() 277 return xmaps.Keys(c.root) 278 } 279 280 func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams) bool) { 281 var walk func(params ...maps.KeyParams) 282 walk = func(params ...maps.KeyParams) { 283 if walkFn(params...) { 284 return 285 } 286 p1 := params[len(params)-1] 287 i := len(params) 288 for k, v := range p1.Params { 289 if p2, ok := v.(maps.Params); ok { 290 paramsplus1 := make([]maps.KeyParams, i+1) 291 copy(paramsplus1, params) 292 paramsplus1[i] = maps.KeyParams{Key: k, Params: p2} 293 walk(paramsplus1...) 294 } 295 } 296 } 297 walk(maps.KeyParams{Key: "", Params: c.root}) 298 } 299 300 func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams) maps.ParamsMergeStrategy { 301 if len(params) == 0 { 302 return maps.ParamsMergeStrategyNone 303 } 304 305 var ( 306 strategy maps.ParamsMergeStrategy 307 prevIsRoot bool 308 curr = params[len(params)-1] 309 ) 310 311 if len(params) > 1 { 312 prev := params[len(params)-2] 313 prevIsRoot = prev.Key == "" 314 315 // Inherit from parent (but not from the root unless it's set by user). 316 s, found := prev.Params.GetMergeStrategy() 317 if !prevIsRoot && !found { 318 panic("invalid state, merge strategy not set on parent") 319 } 320 if found || !prevIsRoot { 321 strategy = s 322 } 323 } 324 325 switch curr.Key { 326 case "": 327 // Don't set a merge strategy on the root unless set by user. 328 // This will be handled as a special case. 329 case "params": 330 strategy = maps.ParamsMergeStrategyDeep 331 case "outputformats", "mediatypes": 332 if prevIsRoot { 333 strategy = maps.ParamsMergeStrategyShallow 334 } 335 case "menus": 336 isMenuKey := prevIsRoot 337 if !isMenuKey { 338 // Can also be set below languages. 339 // root > languages > en > menus 340 if len(params) == 4 && params[1].Key == "languages" { 341 isMenuKey = true 342 } 343 } 344 if isMenuKey { 345 strategy = maps.ParamsMergeStrategyShallow 346 } 347 default: 348 if strategy == "" { 349 strategy = maps.ParamsMergeStrategyNone 350 } 351 } 352 353 return strategy 354 } 355 356 func (c *defaultConfigProvider) SetDefaultMergeStrategy() { 357 c.WalkParams(func(params ...maps.KeyParams) bool { 358 if len(params) == 0 { 359 return false 360 } 361 p := params[len(params)-1].Params 362 var found bool 363 if _, found = p.GetMergeStrategy(); found { 364 // Set by user. 365 return false 366 } 367 strategy := c.determineMergeStrategy(params...) 368 if strategy != "" { 369 p.SetMergeStrategy(strategy) 370 } 371 return false 372 }) 373 374 } 375 376 func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, maps.Params) { 377 var parts []string 378 v, ok := c.keyCache.Load(key) 379 if ok { 380 parts = v.([]string) 381 } else { 382 parts = strings.Split(key, ".") 383 c.keyCache.Store(key, parts) 384 } 385 current := c.root 386 for i := 0; i < len(parts)-1; i++ { 387 next, found := current[parts[i]] 388 if !found { 389 if create { 390 next = make(maps.Params) 391 current[parts[i]] = next 392 } else { 393 return "", nil 394 } 395 } 396 var ok bool 397 current, ok = next.(maps.Params) 398 if !ok { 399 // E.g. a string, not a map that we can store values in. 400 return "", nil 401 } 402 } 403 return parts[len(parts)-1], current 404 }