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