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