github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/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  		"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  }