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 }