hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

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 }