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  }